mirror of
https://github.com/stablyai/orca.git
synced 2026-10-06 08:02:28 +00:00
debug-github-rate-limit
11496
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a3a2c44edf |
Split browser pane (#14861)
* refactor: split BrowserPane.tsx under 400 lines * rm plan * refactor(browser-pane): reorganize into lifecycle folders Cut/paste + import rewrites only; no intentional behavior change. - annotate/, assemble-chrome/, host-guest/, navigate/, stream-remote/, describe-page/ (foundation sink, zero outgoing edges) - BrowserPane.tsx is now a pure re-export barrel; its component body moved verbatim to assemble-chrome/browser-workspace-pane.tsx so no dest file imports the barrel - browser-runtime.ts -> describe-page/live-browser-url-registry.ts (banned name; relocating the contract collapsed the host-guest/navigate mutual pair) - repath browser-pane test paths in config/reliability-gates.jsonc * refactor: sync addressBarValueRef with useEffect Move ref synchronization into useEffect hook with proper dependency tracking to ensure the ref updates are handled through React's lifecycle. Consolidate related imports from browser-page-types. * refactor(browser-pane): fix React lifecycle and external store patterns - Replace local state + effects with useSyncExternalStore for external subscriptions (draw hint, address bar, slot viewport) - Fix React StrictMode double-invoke issues in pointer handlers and state updates - Add keyboard navigation to context menu (arrows, Home, End, Escape) with focus management - Improve error handling for mobile driver reclaim and grab action IPC failures - Add test coverage for BrowserFind session flags, keyboard behavior, viewport lifecycle - Remove react-doctor/no-adjust-state-on-prop-change lint disables (root causes now fixed) * i18n: extract grab and download UI messages Move hardcoded toast notifications and error messages to translation system for both grab annotations and file drop handling. Also apply lazy initialization to address bar value and remove duplicate event recording. * fix(browser-pane): stop mutating refs during render React Doctor fails static analysis when refs are written in render. Mirror latest values in useLayoutEffect, and read the current page id from the latest grab callbacks. * fix(browser-pane): drop unused grab-mode exit dependency exit already reads the page id from a ref, so listing browserPageId trips the changed-code exhaustive-deps gate. * test(e2e): hide the window when Linux minimize is a no-op Xvfb has no window manager, so BrowserWindow.minimize() never sets isMinimized() on the frameless Linux CI window. Hide still occludes the guest compositor so restore coverage can run. |
||
|
|
39260d16c7 |
test: properly clean up in-flight checkpoints before disposal (#15010)
Release stalled operations, wait for pending checkpoint work to complete, and stop checkpoint timers before disposing the adapter. This prevents abandoned checkpoint tmp/rename operations from recreating files under the temp directory before it's deleted. |
||
|
|
2aebcfe288 |
Improve cmd j search keyword match (#15170)
* Implement multi-keyword palette matching with evidence-based ranking Replaces the single-match-per-field scoring with a comprehensive matcher that: - Validates token coverage across multiple query keywords - Normalizes Unicode text consistently across all sections - Classifies matches by quality for cross-section leadership - Supports evidence-based matching with hidden supporting fields - Includes typo matching for letter-only words - Performance-gated against a synthetic corpus of 800+ candidates Result structure now carries match ranges per field (not per row), quality class, and document rank so sections can compare relative strength. This enables worktree/open-tab/intent section ordering based on match intent rather than hardcoded defaults. * Improve cmd-j palette selection after deferred query commits Instead of clearing selection when the deferred query commits, intelligently select the next available item using the standard selection logic. Also remove unnecessary array index from React key generation to prevent spurious re-renders. |
||
|
|
0bedeea642 | fix(orchestration): expose unsupervised dispatch lanes (#15105) | ||
|
|
32ee3b0536 |
reland(browser): route every cookie-import write through CDP identities, and never clear what it will not write back (#15030)
* reland(browser): restore CDP-identity cookie-import writes (#14729) Reverts the revert |
||
|
|
a7f1653415 |
fix(worktrees): keep retirement tombstones across project and SSH target re-add (#14917)
* fix(worktrees): keep retirement tombstones across project and SSH target re-add Generated workspace names are retired so a name is never reissued onto a cwd that still holds another workspace's Claude/Codex history. Two re-add paths lost that record. STA-4449 (local): retirement was stored only under `repo.id`. Removing a project deletes that row and re-adding the same path mints a new id, so the new repo starts with an empty registry. The on-disk backfill normally re-seeds local repos, but it cannot recover a name whose only surviving evidence is a Codex rollout JSONL — those are deliberately not scanned — so a name spent under Codex with its workspace directory gone came back. STA-4491 (SSH): the second, path-derived copy embedded the SSH target row id. Row ids are minted fresh on every re-add, so `ssh:ssh-old:...` became `ssh:ssh-new:...` and `reassignSshTargetId` migrated other carrier state but not the retirement namespaces. Keying the store on the namespace instead of `repo.id` was rejected in `nestWorkspaces`, `worktreeBasePath` and `repo.path`, so a settings toggle would orphan every retirement at once. `repo.id` therefore stays primary and the path-derived namespace stays a mirror — a settings toggle loses the mirror but keeps the repo row, a re-add loses the repo row but keeps the mirror, and reads union both. - Mirror local repos into the namespace too, not just remote ones. - Key the namespace's host half on the SSH endpoint (host+port+username), the thing that actually decides which filesystem a path lands on, instead of the target row id. Reads also accept the pre-identity key so an upgrade keeps tombstones it already wrote, and `reassignSshTargetId` re-keys the rest. - Cap the namespace map, which by design outlives the repos that wrote it and so has nothing to prune it per repo. Endpoint identity is extracted from ssh-target-readoption.ts, which already compared these fields for exactly the same reason, so re-adoption and retirement cannot drift apart. * fix(worktrees): copy shared SSH endpoint retirements instead of moving them An endpoint identity is not owned by the target row that rotates: nothing dedupes SSH targets by host|port|username, so a second live target can still resolve to the same host. Moving the bucket stripped that target's tombstones and reissued a path whose agent history is still on disk. Row-id identities stay a move — reassignment leaves nothing pointing at them. * fix(worktrees): carry retirement mirror across in-place SSH endpoint edits Config sync rewrites host/port/username on the existing target row and a runtime-owned target takes a fresh address from every provision, both keeping the row id. No re-adoption runs, so nothing carried the endpoint-keyed mirror across and it stranded — strictly worse than the pre-change key, which was the row id and was invariant under these edits. Also bound the map after a migration: a retained source bucket grows it, so the cap has to be applied there too, and compare registries by membership rather than size so an uncompacted destination cannot trade a folded name for a new one and read as unchanged. * fix(worktrees): skip retirement migration for on-demand runtime targets An on-demand VM is discarded between provisions, so its fresh address reaches an empty filesystem and a reissued name collides with nothing. Migrating there would spend names against history that no longer exists, and because each provision mints another address it would add a namespace bucket per run, evicting the real tombstones of local and ordinary SSH repos. * fix(worktrees): stop the namespace cap evicting what a migration just wrote Two defects with one root cause. Assigning to an existing key leaves it in its original insertion slot, so a merged destination kept the oldest position and the trim deleted the bucket it had just enriched. Retained source buckets are older than the destinations a copy appends, so at the cap the trim removed exactly the sources the copy existed to keep — silently turning it back into a move. The trim now exempts the keys the migration wrote or deliberately kept. Also stop on-demand runtime workspaces writing namespace mirrors at all: each provision reaches a discarded filesystem under a fresh address, so the entry can never be read back and only spends a capped slot that a local or SSH project needs. The repo-id row still records the name for the live session. * fix(worktrees): re-insert migrated namespaces so the cap cannot undo a migration Exempting keys from the trim protected them for that one call and no other. A merged destination keeps its original insertion slot, so it sat at the front of the eviction queue and the next unrelated retirement write dropped it — losing both the migrated name and the name the destination already held, on a host that had just been re-added. Re-insert what the migration writes instead, the same discipline the ordinary writer already follows, so insertion order reflects use. That also removes the exemption, which could otherwise leave the map stuck at twice the cap until one later write evicted the whole excess at once. Corrects the runtime-gate comment as well: the mirror is unreadable after the next provision, not immediately, so a remove/re-add inside one provision is a real if narrow loss. * fix(worktrees): refresh a retained namespace source even when its merge adds nothing Replacing the trim exemption with re-insertion narrowed the protection: the exemption covered every retained source, the re-insertion only covered sources whose merge actually wrote. A copy whose destination already held the same names was then neither re-inserted nor exempt, so the migration's own trim evicted the shared source bucket ahead of hundreds of untouched ones — losing the tombstones of a live sibling target still on that endpoint, which is what copying exists to prevent. A move's destination gets the same treatment: deleting the source makes it the only remaining copy, so it has been used. Both are order-only and deliberately do not set the changed flag, keeping an import that moved nothing from scheduling a save. |
||
|
|
7b4e10b104 |
fix(mobile-ios): pin fastlane and gate the Fastfile in CI (#15092)
* fix(mobile-ios): pin fastlane and gate the Fastfile in CI The ios-distribute job failed on every run from 2026-08-10 to 2026-08-13 because distribute_testflight passed distribute_only without app_platform, so pilot fell through to an interactive platform prompt on ubuntu. No CI check loads the Fastfile, so external testers got nothing for six days. - Pin fastlane 2.238.0 and commit mobile/Gemfile.lock so ios-build (macos) and ios-distribute (ubuntu) cannot resolve different versions ~25 minutes apart. Fixes the Gemfile comment's dead mobile-build.yml reference. - Add a Fastfile smoke check (bundle exec fastlane lanes) plus a static contract test for the TestFlight lane arguments to Mobile Checks. - Set reject_build_waiting_for_review so a superseded same-train build in beta review stops blocking the submission. * fix(mobile-ios): install the pinned Gemfile.lock in frozen mode Without frozen, a lockfile that drifts from the Gemfile is silently re-resolved per job, which is the version split the pin exists to prevent. * test(mobile-ios): anchor the TestFlight argument contract against an empty selection * chore(mobile-ios): canonicalize the lockfile platforms Bundler's own normalization drops arm64-darwin-25 as redundant with the versionless arm64-darwin, and the ubuntu runners resolve x86_64-linux-gnu. |
||
|
|
4a6de51ad8 |
fix(native-chat): enforce each pending send's own boundary in glue matching (STA-4477) (#14935)
* fix(native-chat): enforce each pending send's own boundary in glue matching Glue matching filtered candidate rows against the OLDEST still-open send and then matched the entire open queue against them. A prompt queued after a glued row landed could therefore be judged "already delivered" by that older row and pruned — the queued prompt disappeared with no bubble and no transcript turn. Each send now carries its own transcript boundary into the match: `gluedCandidateRows` tags every candidate row with the set of pending indices it actually landed after, and the matcher stops a run at the first send the row predates rather than skipping over it (adjacency is what makes a row glue). Exact single matches still belong to the occurrence path, unchanged. `native-chat-pending.ts` sat at 299 of its 300 effective-line budget, so the slash-command marker cache — a separate rule that never took part in pending pruning — moves verbatim to `native-chat-command-marker.ts`. Pure move: no behavior change, imports only. (max-lines is never bumped or disabled.) Refs STA-4477. Original PR #14663. * test(native-chat): cover the glue adjacency break and unmask the render path The `break` on a send the row cannot represent is the fix's central semantic choice, and swapping it for `continue` was passing the whole suite: nothing exercised a queue whose middle send is unrepresentable. Add that case. The mixed-age case also asserted both call sites in one `it`, so a prune-path failure masked the render-path assertion — and the render path is the one that makes a queued bubble visually vanish. Split it. Skip the per-send boundary scans when fewer than two sends are open: the glue matcher already returns nothing there, so a lone queued echo was walking the transcript twice per render for a discarded result. * fix(native-chat): migrate the live-session benchmark off the renamed glue exports Renaming the glue matcher's exports left this caller behind, and it crashed at runtime after printing six result rows: TypeError: matchingNativeChatUserTexts is not a function No gate caught it. config/scripts/** is in no tsconfig include and the file is not a *.test.ts, so neither typecheck nor vitest ever loads it. The empty-pending arm passes no pending sends, so the matcher takes its empty-queue exit without ever reading the rows — which is also why the renderer skips candidate-row construction entirely in that case. Escaping the row scan directly keeps what this arm actually measures identical to before, rather than fabricating per-row boundary sets that no production path builds. |
||
|
|
60805f5c45 |
fix(agent-status): preserve restored child provenance (#15082)
* fix(agent-status): preserve restored child provenance * fix(agent-status): preserve restored completion context * fix(agent-status): retain child boundary across OSC |
||
|
|
646e9b692b | fix(mobile): render pairing QR at scanner-safe scale (#15058) | ||
|
|
a1cd7eaa7e |
refactor(terminal): redesign quick command dialog with expanded layout (#15011)
* refactor(terminal): redesign quick command dialog with expanded layout a - Enlarge dialog to 52rem width and add scrollable content area for improved editing experience - Restructure textarea with header (label, status badge) and footer (hints, controls) - Compact action toggle to use shorter labels with grid layout - Move append enter switch to textarea footer (compact mode) for terminal commands - Add scope summary to Advanced toggle when collapsed for quick reference - Update dialog description and add contextual hints for user guidance * fix(terminal): restore defaultAdvancedOpen on quick command dialog Settings still opens the Advanced section from settings; the redesign dropped the prop and failed typecheck. * Improve accessibility of terminal quick command dialog - Add inert attribute to prevent keyboard/screen reader access to hidden advanced section - Add aria-label to textarea for clearer form field labeling - Extract label text to variable to avoid duplication and ensure consistency * refactor: remove status badge from quick command header Simplifies the dialog header layout by removing the "sent to agent" / "runs in terminal" badge and its associated translation strings. |
||
|
|
b2612de157 | Update README downloads badge | ||
|
|
7ae6aedc02 |
fix(codex): stop a transient filesystem error from logging out the active account (#15046)
* fix(codex): stop a transient filesystem error from logging out the active account A single unreadable read of a managed Codex home's ownership marker cleared the user's active account selection, permanently. On Windows any exclusive lock — Defender real-time scanning, a backup agent, a sync client — makes every read of that marker fail with EBUSY, and the background rate-limit poll runs every 15 minutes plus once at every app start. Root cause: the ownership gate answered two very different questions through one channel. "This home is not ours" (a successful observation that failed a trust check) and "we could not read it" both surfaced as a throw, which the caller flattened to null, which three call sites took as proof the home was untrustworthy and wrote activeCodexManagedAccountId: null. Refusing to USE an unverified home is correct. Erasing the user's account selection because a file was briefly locked is not. The gate now returns a tri-state verdict. `untrusted` comes only from a proven trust failure or a definitive ENOENT/ENOTDIR where absence is itself the verdict; every other filesystem exception is `indeterminate`. Only `untrusted` may touch persisted state. Because `null` already meant "fall through to the system default" on both the launch and poll paths, not-clearing on its own would have run a DIFFERENT account behind a UI still showing the selected one. So the refusal needed real channels rather than a sentinel: - the poll returns an explicit skip; returning null would not have skipped at all, since the fetcher maps null to ~/.codex and would have spawned a token-refreshing app-server inside the user's real credential home - pane launch throws a typed temporary-unavailability error that both PTY implementations convert into a clean refusal with a retry message, including the re-resolution after the async auth-readiness wait - automatic session resume resolves the selected home eagerly, so an unreadable account can no longer be silently replaced by another one in the ranking - config-sync status reports a distinct managed-home-unavailable stall instead of "synced", with a bounded renderer retry so it clears on its own Also fixes the ticket's second symptom. The status bar's Sign in button called a re-auth that captured the selection before login and restored it after, so re-authenticating a deselected account restored `null` — a successful login that left the account inactive, with no success toast to distinguish it from failure. It now activates the account it just signed in, but only when the pre-login selection was empty, so it cannot silently switch accounts for multi-account users, and it runs the same restart prompt an explicit switch does. No retry or grace window inside the synchronous gate: it runs on the Electron main process in a loop over accounts, so a sleep there would freeze the UI. Recovery is simply the next readable evaluation. The WSL lane has the same class of defect, including one path that deletes a credential mirror. It is pre-existing, unreachable from these host code paths, and deliberately left for its own change; the host clearing sites cannot reach a WSL account because getSelfContainedManagedHostAccount excludes them. Fixes STA-4422 * test(codex): cover pending reset home ownership |
||
|
|
b0e27354b5 | fix(mobile): escalate continuous Relay outages (STA-4587) (#15071) | ||
|
|
be07b43a2b | fix(orchestration): enforce honest recipient routing (#14964) | ||
|
|
7fad71e448 |
fix(worktree): skip retirement backfill on every non-local host (#15023)
The backfill guard tested repo.connectionId, but a runtime-owned repo carries executionHostId with no connectionId, so it read as local. The scan then walked this machine's workspace and agent-transcript directories and filed the result under the runtime host's namespace — retiring names never used there while missing the ones that were. Guard on the execution host id instead. Ongoing retirement was already correct for these repos; only the one-time historical seed was wrong. |
||
|
|
1aa51f6914 |
fix(computer): preserve accessibility value types (#15031)
* fix(computer): preserve AX value types * fix(computer): preserve exact integer values * fix(computer): preserve exact integer exponent forms |
||
|
|
c7995a66ae |
fix(mobile-native-chat): reland glued pending retirement without the two revert causes (STA-4482, STA-4492) (#14936)
* fix(mobile-native-chat): reland glued pending retirement without the two revert causes Relands #14665 (reverted by #14819). #14665 retired mobile pending bubbles when two fast sends landed as one transcript row, but shipped two regressions; both are fixed here rather than re-applied and hoped for. 1. A rejected send restored a TRIMMED composer. #14665 reassigned `text` to `text.trimEnd()` at the top of `sendMessage` and then used that one value for both the bytes on the wire and the composer restore, so a rejection put back less than the user typed. The draft and the payload are now separate values: `draftText` is what the user typed and is what `clearDraftForSend` / `restoreRejectedDraft` see; only the transported `text` is trimmed. 2. Sends issued during hydration were stranded forever. #14665 persisted `glueBaselineTrusted: false` on any send captured while the transcript was still loading and never cleared it, so that send could never retire and stood as a permanent glue barrier for its neighbours. A hydration-time baseline is now a placeholder (`baselineResolved: false`) that the first authoritative read rebases onto real rows, ordinals included, instead of a permanent disqualification. That is STA-4492. The intended behavior is unchanged: one transcript user turn retires a run of 2+ adjacent text-only pending sends only when it exactly spells their normalized concatenation, every send is bounded by its OWN transcript tail, and exact landings, image echoes and unresolved tails stay barriers. No wire change: `baselineResolved` and the baseline tail are client-local React state in `pendingBySession` and are never exchanged with a host. The only client->host difference is trailing whitespace no longer being written onto the agent's input line, over the existing `terminal.send` params. Refs STA-4482, STA-4492. Original PR #14665, revert #14819. * fix(mobile-native-chat): let the untrimmed draft reach the send seam The composer sent `value.trimEnd()`, so the raw draft never reached `sendMessage` and a rejected send still handed back a trimmed composer — the split of `draftText` from the transported `text` had nothing to restore. Pass the draft through; the seam already owns the wire trim. Also pins the array-identity contract of `retireLandedMobileNativeChatPending`: the drafts effect early-outs on `next === current`, and nothing tested it. * docs(mobile-native-chat): name the hydration rebase's residual ambiguity * fix(mobile-native-chat): stop the hydration rebase stranding a send on its own echo Rebasing recounted the send's ordinal against the first authoritative read. That read can already carry the send's own echo — a re-subscribe after a tab switch or reconnect returns whatever exists now — so the ordinal landed one past anything the transcript could supply. The bubble never cleared, it stayed a live segment at the head of its run so no later pair could glue either, and `earlierOutstanding` carried the inflation onto the next send of the same text. Only the tail needs recovering; the ordinal was already counted against an empty transcript, which is right for "no history was known". A caption-less image echo keeps its captured tail, since it counts turns after it. `baselineResolved` also has to mean "captured against a settled read", not merely "not loading": a read that failed hands back an empty list that reads as an empty conversation, and the null tail then let any row the successful read finally brought glue-retire those sends. * test(mobile-native-chat): pin that a resolved hydration send leaves its run glue-capable A held send sits as a live segment at the head of its run, so the cursor can never reach a later pair — the stuck bubble takes the whole feature down with it. Goes red against the ordinal recount. * fix(mobile-native-chat): pin an image echo that captured no tail, and require the settled flag A caption-less image echo keeps its captured tail because it counts image turns after it — but a send issued before any history was known captured null, which counts from the top of the transcript. An old image turn then claimed the send and bound the user's fresh photo to it, leaving the just-sent turn with no preview. A null tail is not a boundary worth preserving, so pin those too. `transcriptSettled` was optional and defaulted to the gate it replaced, so any caller that omitted it silently got the pre-fix behaviour. Required now, and threaded through every harness. * fix(mobile-native-chat): stop an unbounded send claiming an image turn already in the read The image-preview pass runs before the rebase, so a send captured with no boundary matched any image turn the settled read carried — binding the user's freshly attached photo to an old one and retiring the bubble through landedImagePendingIds, which short-circuits the retirement path entirely. Pinning the tail in the rebase could not help: the claim was already made. Such an entry now waits one tick and claims against a real tail. * fix(mobile-native-chat): never move a boundary the send already captured An unsettled read still shows this session's own retained history — a reconnect or a failed read keeps the conversation on screen rather than blanking it — so sends made across one already own a correct tail. The rebase overwrote it with the tail of the read that followed, which sits at or after their own glued row, so `turn.index <= segment.tail` rejected every turn and the pair stayed queued for the session, blocking every later pair in the run. Pin only a send that captured no tail at all. A captioned image echo is now left alone entirely: it binds its preview by an ordinal counted over the whole transcript, so supplying a tail without recounting left it matching nothing, forever. * fix(mobile-native-chat): supply a boundary only to a text-bearing send An image echo reconciles by counting turns AFTER its tail and has no other retirement path, so the tail supplied from a read that already carried its own echo excluded the very row it was waiting for: the "Queued" photo bubble stuck for the life of the session and the transcript row rendered as bare marker text with no photo. A regression against main, and against the earlier revision of this fix that pinned only captioned echoes. The glue matcher is the only consumer a supplied tail helps. Everything that reconciles relative to its own tail keeps whatever it captured. * fix(mobile-native-chat): stop one unmatchable send freezing glue for the session The match cursor only advanced on a hit, so a head that could never match — a pair whose glued row arrived with the read, or a send the count pass claimed against an older row — froze the run behind it and every later rapid pair became permanently unretirable. Two cases previously disclosed as bounded were not bounded at all. Slide past a non-matching head, keeping the cursor monotonic so a later turn can never take a send an earlier one claimed. The slide widens the search, so a span cap keeps the work linear in the run length instead of quadratic; the existing budget test now asserts that bound rather than the old one it silently broke. Re-fuzzed at 250k seeds: the boundary guarantee still holds. Also corrects a comment that claimed the preview-pass filter made a photo claim against a real tail. It does not — an image echo keeps whatever tail it captured, so a caption-less photo can still bind to an older photo turn, as on main. * fix(mobile-native-chat): stop the span cap stranding a long glued run Capping each match attempt at 8 segments did not truncate a longer glue, it rejected it outright: a row spelling 9+ sends exhausted the loop without reaching the end of the text and returned zero, so none of the nine retired — and each stuck send then inflated `earlierOutstanding` for the next send of the same text. Nothing bounds how many sends pile onto the agent's input line; accumulation ends when the agent accepts input again, not at any fixed count. One inspection budget now covers the whole slide instead. The first attempt spans the entire run and always fits, so a genuine glue is never truncated; only a run of identical prefix-matching sends can exhaust the budget, which is exactly the case that should be cheap. The in-flight attempt may overshoot the remainder — that is what makes the guarantee hold — so the budget test asserts the real ceiling. Re-fuzzed at 250k seeds with runs past the budget. |
||
|
|
47a53b694b |
fix(ui): ignore IME composition Enter in the comment composer and replace field (#15057)
The keydown that exits a CJK composition carries isComposing: true (UI Events 3.6.5, row 5) and, when the IME is processing key input, keyCode 229 (7.3.1). Two handlers acted on it: - right-panel-comment-composer: Cmd/Ctrl+Enter posted the comment while the last syllable was still composing, so it went out truncated. - RichMarkdownSearchBar: Enter ran replace-current, mutating the document from a keystroke aimed at the candidate list; Escape closed the bar instead of letting the IME cancel the composition. Same in the find field. Guard all three with the existing isImeCompositionKeyDown helper, matching the rename and title inputs already on it. The held modifier does not change ownership: a composing Ctrl+Enter is still the IME's keydown, which is why useImeEnterGestureOwnership also owns it and only lets the post-compositionend redispatch chord through. Co-authored-by: BaeTab <bhwoo48@gmail.com> |
||
|
|
876e5b88a4 |
fix(ime): scope the composition route and deferred newline to owned sessions (#15056)
Two ways a terminal composition went wrong, both from state that was not scoped to the session it belonged to. The route called preventDefault() before checking whether it owned the session. The patched xterm treats that cancellation as "someone else will deliver this" and skips its own triggerDataEvent, so a route installed mid-composition — the connection effect re-runs, a reconnect swaps the transport, StrictMode remounts — suppressed the insertion and then returned without delivering anything. The commit vanished. Ownership is now decided first; preventDefault stays for sessions the route does own, including the ones it deliberately drops after a transport swap, since that drop is its decision to make. Pending-composition state was a bare per-element count, so a waiter could only ask "is anything composing", not "is what I was waiting for still composing". A composition the user starts after pressing Enter is behind that Enter, not in front of it, but it kept the count non-zero and held the newline anyway — `한` Enter `글` reaching the terminal as `한글\n`. The count is now reference-counted per session id, and the deferred send snapshots the sessions open when it starts and waits only for those. Reference counts rather than a set, so two overlapping routes owning the same session cannot clear each other's pending state. Co-authored-by: yeongjunyoo <47925973+yeongjunyoo@users.noreply.github.com> |
||
|
|
6387e5b8d3 |
fix(folder-workspaces): keep the broken-folder marker when a host sends a new reason (#15027)
FolderWorkspacePathStatus is cast, not decoded, off the runtime RPC wire -- runtime-rpc-envelope declares result: z.unknown(), so unwrapRuntimeRpcResult hands back whatever the host sent. Both title and description switch on status.reason with no runtime guard, so a newer host publishing a fifth reason matched nothing and returned undefined. FolderPathStatusIndicator's `!title` check then dropped the whole indicator, and a broken folder workspace rendered as healthy -- worse than the blank toast #15002 fixed, because there the warning was empty and here it is gone. Guard before each switch, the shape #15002 landed. A default: arm is not available: the type-aware config sets allowDefaultCaseForExhaustiveSwitch:false and rejects one with switch-exhaustiveness-check. Extract that guard into isHandledWireDiscriminant instead of hand-writing a third and fourth copy, and move #15002's two bespoke guards onto it. It takes unknown and checks typeof before Object.hasOwn -- hasOwn coerces its key, so a host that widened the field to an array sends ['missing'], which a hasOwn-only guard admits before the switch drops it straight back out. That was the P1 found in review on #15002; one implementation makes it structural instead of tribal. An unrecognized reason gets its own copy rather than reusing 'unavailable'. The unavailable remedy -- "Check the runtime or SSH connection and try again" -- is a false lead here: the host did check and reported the folder unusable, so retrying and inspecting a healthy connection wastes the user's time. Update Orca is the real remedy. Adding a fifth reason still fails typecheck in two places: TS2741 on the Record and TS2366 plus switch-exhaustiveness-check on both switches. |
||
|
|
7c798907c5 | fix(skills): harden cross-host bundle installs (#15000) | ||
|
|
04e7f5c805 |
fix(cli): relativize absolute POSIX paths against UNC worktree roots in WSL (#11406)
* chore: ignore worktrees directory * fix(cli): relativize absolute POSIX paths against UNC worktree roots in WSL * fix(cli): prevent double-prefixing UNC paths in WSL path normalization * fix(cli): gate the WSL path rewrite on a UNC worktree root WSL_DISTRO_NAME is also set for a plain Linux CLI inside the distro, where worktree roots are POSIX; prefixing there stranded every absolute path. Rewrite only when the root really is a WSL UNC path, and cover the legacy wsl$ alias, cross-distro paths, and the non-WSL case. * test(cli): pin WSL_DISTRO_NAME absent for the whole file-path suite Contributors run this suite inside WSL, where the inherited distro name would flip the rewrite on for every POSIX-root case. * fix(cli): never rewrite a Linux path that contains a backslash Backslash is a legal Linux filename character but a separator once the path reads as UNC, so `a\b.ts` relativized to `a/b.ts` — a different file. Such a path has no UNC spelling; let it fail the match instead. * test(cli): pin the WSL rewrite's negative space Sibling-prefix roots, Linux-tail case sensitivity, and Windows drive-letter workspaces all passed only by construction. * test(cli): pin the distro-case fold from the CLI side The negative-only case passed identically with the fold broken. * fix(cli): name the WSL distro from the invocation cwd when the env is absent WSL_DISTRO_NAME only reaches the CLI if interop forwards it across the PowerShell bridge, which nothing in the launcher guarantees. ORCA_CLI_CWD is set explicitly and its UNC form already names the distro. * test(cli): match the launcher's real cwd spelling and fix an over-claim wslpath -w emits a backslash UNC path; the fallback test now uses that shape. The aliasing test's comment described a state the || guard makes unreachable. * refactor(cli): spell the WSL rewrite with the shared toWindowsWslPath helper src/shared/wsl-paths.ts already owns "absolute Linux path in a known distro -> its Windows form" and has five production callers; the handler hand-rolled a fourth copy of the UNC template. Behavior is identical under the UNC-root guard, and passing distro as a real argument makes the null check a compile error rather than an untested branch. * test(cli): drop a WSL case that pins the guard shape and kills no mutant Deleting the distro null check left the case green — it asserts the same passthrough as 'does not rewrite when the CLI is not running under WSL'. The check is now enforced by the compiler instead. * chore: keep WSL path fix scoped --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
30b76e6edb | fix(orchestration): enforce task dispatch state invariant (#14961) | ||
|
|
453237cc57 |
fix(terminal): render the row tail the IME preedit overlay covers (#15014)
* fix(terminal): render the covered row tail inside the IME preedit overlay Closes #12545. Composing mid-line hid the character at the cursor for the whole composition. The preedit overlay is an opaque box anchored to the cursor cell, and nothing reaches the pty while composing, so those cells still held their characters — the box simply covered them. `CompositionHelper` now draws the rest of the row after the preedit inside the view, so the composition reads as inserted text pushing the tail right. Four details come with it: - The view is start-anchored while it carries a tail, so the preedit stays put and the pushed tail clips at the right edge; alone, `rtl` still keeps a long preedit's end in view. - It is themed from `options.theme` instead of the stock `#000`/`#FFF`, with any alpha dropped — the view masks the cells it draws over, so a see-through background would re-expose the very characters the tail stands in for. - The helper textarea syncs to the preedit's own bounds, so IME candidate dialogs anchor to the composing text rather than past the rendered tail. - A TUI can repaint the row under an open composition, so `updateCompositionElements` — which already runs on every render — re-reads the remainder and re-renders on change. A string compare adds no layout read. The tail is read with an explicit end column: the cacheable form of `translateToString` arms the line string cache's self-renewing idle-clear timer, and the composition path must own no timers. Geometry is not the cause. Two mature reference terminal implementations compose marked text into the grid rather than into a floating box, and both still blank the cells under it — one of them literally substitutes the marked characters into the row's character array before rasterizing. Moving off the overlay would not have fixed this report; rendering the covered tail is what does. The e2e arm asserts the invariant an opaque overlay owes the grid: it must render every committed cell its bounding rect covers. That is measured from the real rect against the real cell grid, so it fails on the unfixed build with `covers "하" / renders "가"`. Known limitation: the rendered tail is plain-styled while composing (theme foreground on theme background, no per-cell colors); colors return on commit. This is inherent to the overlay, and drawing the preedit into the cell renderer instead would be a far larger change. Co-authored-by: rayim <rayim@fxy.global> * test(e2e): assert the occlusion invariant, not the runner's cell width CI covered four columns where this machine covers two — 34.4px over an 8.43px grid against 12.3px over an 8px grid — so pinning the covered text verbatim pinned the font metrics rather than the behaviour. Assert instead that every committed cell the overlay covers appears in what it draws, which is the actual invariant and holds at any cell width. Still fails against main: covers "하" / renders "가". * fix(terminal): keep the rendered tail's spacing on the grid The composition view is white-space: nowrap, which collapses runs of spaces exactly like normal — it only suppresses wrapping. So a committed tail carrying padding drew its trailing glyph cells left of where the grid has them: measured in Chromium with xterm's own rule, twenty spaces plus a border rendered two cells wide instead of twenty-one. The visible case is Orca's most common IME context — composing inside an agent TUI input box, where the row is a prompt, padding, then a real border glyph the trim cannot drop. A stray border appeared a cell after the preedit while the real one stayed put. xterm sets white-space: pre on its grid rows for this reason; the view was only nowrap-safe while it held preedit text alone. The existing fixtures are all space-free, and the e2e invariant is that the overlay renders everything it covers — collapsing makes it cover less, so both stayed green. Pinned with a padded-row fixture. --------- Co-authored-by: rayim <rayim@fxy.global> |
||
|
|
8e9b5c908c |
fix(github): fail closed instead of running client git against a remote repoPath when the SSH provider is unregistered (#14945)
* fix(github): fail closed when the SSH git provider is gone getCurrentHeadOid and probeTrackedUpstreamBranches only routed through the SSH provider when one was registered. With connectionId set but the provider unregistered (dropped connection, not yet reattached) they fell through to client-side git with cwd pointing at the remote repoPath — on a machine with a same-named local path that silently answers for the wrong repository. getCurrentHeadOid feeds shouldHideMergedImplicitPR, so a wrong OID changes which PR the UI attributes to a worktree. Both now take their existing unknown path (null / probeFailed) instead, matching repo-default-branch.ts. Local and WSL routing is unchanged. * fix(github): preserve PR state when SSH probes fail * fix(github): keep failed SSH discovery unverifiable * fix(github): propagate SSH identity failures * fix(github): scope verified SSH identity probes * test(github): preserve tolerant resolver calls * fix(github): preserve indeterminate auth discovery * fix(github): isolate SSH repository probe generations * test(github): expose SSH probe generation in mocks |
||
|
|
7afce2ea41 |
fix(ssh): stop reporting a confirmed kill when the SSH provider is gone (#14977)
* fix(ssh): stop reporting a confirmed kill when the SSH provider is gone A detached relay PTY is designed to outlive the provider that addressed it (it ignores SIGHUP and ships with an unlimited grace), so "the SSH provider is no longer registered" is lost contact, never evidence the remote process stopped. Both stop primitives in the PTY controller returned `true` from that branch, and every caller downstream reported the fabricated success: the CLI printed "PTY killed.", worker-stop settled the dispatch as stopped, and — because the stop "succeeded" — the unstopped-PTY gate never ran, so worktree removal walked straight past a live remote agent. `kill`/`stopAndWait` now still tombstone the local lease but report an unconfirmed stop and record why, using the three-verdict vocabulary the worktree teardown gate already spoke (`live` / `unverifiable` / `exited`), promoted out of that module into `src/shared/pty-liveness-verdict.ts`. The close receipt, the CLI wording, worker-stop and the removal gate all read that verdict instead of inferring an exit from silence. The same rule fixes the mirror-image defect: the aggregate inventory only enumerates registered providers, so a dropped relay clears `connected` for every remote PTY at once. The sweep now separates the provider answering "absent" (an exit) from no provider being able to answer (lost contact), so worker-stop stops claiming `exited` from a disconnect. The `connected` wire field is unchanged in meaning and shape. * fix(orchestration): apply the same honesty to the federation stop path The federation host runs its own copy of the worker observation and stop logic, with the same two defects: `inspectRemoteAttachment` read a dropped relay's `connected: false` as `exited`, and `federationStop` settled the dispatch as stopped from a close it never confirmed — relaying a fabricated success all the way home to the coordinator. Two guards also had to move so the honest verdict does not become a new refusal. `federationRead` gated on `status !== 'running'`, which would have rejected a connected terminal the moment a stop lost contact with it; it now gates on `status === 'exited'`, which is equivalent for every pre-existing status given the two guards beside it. Local `workerStop` likewise still attempts the close when the verdict is `unverifiable` — losing contact is a reason to report the outcome honestly, never a reason to stop trying. The show observations now carry the reason alongside the status, so a bare `unverifiable` is actionable. Both are new optional fields. * fix(ssh): preserve unconfirmed stop verdicts across consumers * fix(ssh): use canonical live verdict wording * fix(ssh): refuse wrong-host teardown verification * test(orchestration): confirm worker release teardown * fix(orchestration): negotiate honest worker stop receipts * fix(agent-teams): fence uncertain teammate respawns * fix(ssh): avoid duplicate missing-provider teardown * fix(orchestration): preserve archives across release retries * fix(ssh): preserve verdicts across synthetic kill exits * fix(ssh): preserve liveness evidence across teardown * fix(agent-teams): replace panes only after confirmed stop * fix(ssh): distinguish host exits from relay loss * fix(ssh): narrow concurrent inventory verdicts * fix(orchestration): serve archives after uncertain release * fix(orchestration): expose unverifiable read liveness * test(ssh): align liveness assertions with verdicts * fix(ssh): preserve host scope across inventory failures |
||
|
|
b279f66c96 |
Include descendants in Pinned section when parent is pinned (#15035)
Descendants of pinned parents now appear in the Pinned section without individual pins. When an ancestor's pin state changes, reveal the active descendant to maintain focus and visibility. |
||
|
|
66b599399f |
fix(mobile): decide terminal preedit from the marked-text range, not a script table (#15007)
* fix(mobile): decide terminal preedit from the marked-text range, not a script table The live terminal capture field decided what to withhold from the PTY with a Unicode-block allowlist (Hangul jamo and syllables) and held exactly one trailing code point. Kana and kanji are not in the table, so a Japanese reading streamed to the PTY one fragment at a time and was repaired afterwards with DEL bytes (#7427). A code-point table cannot work, and the counterexample is not exotic: Chinese pinyin preedit is plain ASCII, and a Japanese romaji reading is one code point on the first keystroke and three on the fourth. Preedit is a property of the FIELD, not of the characters in it, so the only signal that identifies it is the text system's marked-text range. That is what a reference terminal implementation uses on every platform it supports - `hasMarkedText` there, the input-method context's composing state elsewhere - and neither one classifies code points anywhere in the input path. So the mirror now takes the marked-text report per change and holds the whole preedit region, whatever its length or script: - Subscribe the capture field to `onChange`, not `onChangeText`; only the raw native event carries the report at all. - A reported preedit is held entire and is never committed by the settle timer, because preedit is not text yet. Explicit boundaries still flush it. - `isTerminalLiveHangulCodePoint` and its four ranges are deleted. iOS reports the range but React Native drops it before JS, so the pinned patch forwards `markedTextRange` into the change payload. It is three hunks and it compiles because the app already sets `buildReactNativeFromSource` for iOS. The same idea was proposed in #11450, which is where the patch comes from. Android has no marked-text report in React Native at all, and a Kotlin patch would not help: Android consumes the prebuilt react-android artifact, so node_modules sources are never compiled. Until the report exists there, the fallback holds the trailing non-ASCII run. It enumerates nothing, it covers kana, kanji and Hangul, and ASCII keeps its zero-latency echo - but it cannot see an ASCII preedit, so Chinese pinyin on Android still leaks its reading. Only a report fixes that. Not-tested: no physical device or emulator was available, so no real IME drove this path. Japanese, Chinese and Korean composition are covered at the model and hook level only, and the iOS patch has not been compiled. Co-authored-by: Brennan Benson <brennanb2025@users.noreply.github.com> * fix(mobile): bound the fallback hold to text the pty has not received The no-report branch walked the trailing non-ASCII run over the whole field and ignored stableLength, unlike the reported branch directly above it. So after a settle-timer commit the next keystroke re-held everything already delivered and the caller erased it with DEL and retyped it — a nine-character Cyrillic word cost a DEL per already-sent character, and for the 300ms before the re-send the held text was the only copy, so a blur or reconnect destroyed characters the pty already had. Bound it the way the reported branch is bounded. Pinned by a test that drives a settle commit between every keystroke and asserts no DEL reaches the wire. --------- Co-authored-by: Brennan Benson <brennanb2025@users.noreply.github.com> |
||
|
|
3bb87ff93b |
reland(shell): one portable Unix startup dialect, with both revert causes fixed (#15018)
* reland: portable startup-shell dialect, with the two revert causes fixed Relands #14863 (reverted by #14975) with fixes for both regressions the revert cited. 1. History GC deleted folder-workspace shell history. The live set was built from `getAllWorktreeMeta()` alone, but a folder workspace's PTY carries `folder:<id>` as its worktree id, so every live folder workspace looked orphaned. `getKnownWorktreeIdsForHistoryGc` now unions in `getFolderWorkspaces()`. Both consumers — the history-directory prune and the fish-history sweep — read that one set, so the fix covers bash, zsh and fish history alike. The directory prune had this gap since #1524; #14863 only widened its blast radius to fish files. 2. A copied Codex resume command aborted under `set -u`. Its leading clear statement has to test `$fish_pid`, and that unbound expansion takes the whole line — including the agent launch — down with it. Copied text runs in a shell Orca never spawned, so nothing can seed that variable first. The removal now rides on the agent itself as `env -u`, which needs no shell syntax and no expansion. Verified byte-identical under `set -u` in sh, bash, zsh, dash, ksh and fish. `env` cannot run the `cd` builtin, and a child `cd` would not move the agent, so the prefix is placed on the agent rather than on the whole `cd … && agent` chain. cmd and PowerShell have no nounset hazard and keep their clear ahead of the `cd`, which preserves `cd … && agent` — a failed `cd` still cannot launch the agent in the wrong directory. * fix(history-gc): stop three more paths from deleting live shell history Found by adversarial review of the reland. All three are the same class as the bug that caused the revert: a live set that is missing a category of real workspace, so the GC reads it as orphaned. 1. Profiles. The history root is `userData/terminal-history`, which has no profile segment, but the Store the GC consults is per-profile. So after a profile switch the live set condemned every other profile's history — and fish history, which lands in the user's own fish data dir, is shared by every profile on the machine. The live set now unions in the inactive profiles' worktrees and folder workspaces, read from their data files. A profile whose ids cannot be read reports the empty set rather than one that condemns real history. 2. No empty-set guard on the tree scan. `sweepOrphanedFishHistoryFiles` refuses an empty live set because it cannot be told apart from a store that failed to hydrate; the directory scan, which deletes more, had no such guard. A store that fell back to default state would have taken every worktree's bash and zsh history with it, across all roots including WSL. Four existing tests passed `new Set()` and relied on "empty means everything is orphaned" — exactly the behavior being removed — so they now pass a real live set. 3. Relay fish history. The relay isolates its history tree under its own root but wrote fish history into the shared fish data dir under the desktop naming, keyed by the CLIENT's worktree ids. On a machine running both Orca and a relay host, the desktop sweep deleted remote sessions' history once it went stale. Relay files are now `orca_relay_<hash>`, which the sweep's pattern deliberately does not match; the relay still deletes them by exact name when the worktree goes away. * fix(resume): enforce the env-removal invariants instead of documenting them Both found by adversarial review; both were unreachable from today's callers and silent if reached, which is exactly how they would survive to a caller that does reach them. - A pinned CODEX_HOME and the removal named the same variable, and `env -u` strips what the assignment just set — so the agent would have resumed against the real home and not found the session. The removal list now excludes any name the prefix pins, keeping the assignment authoritative as the old `clear…; CODEX_HOME=x agent` ordering did. Same fix in the git-bash twin. The PowerShell branch already clears before it assigns, so it was never affected. - Placement was keyed on the platform while the grammar it selects is keyed on the shell, so `platform: 'linux'` with `shell: 'powershell'` emitted POSIX `env -u` into a PowerShell line. PowerShell now routes to the PowerShell builder whatever the host, and the POSIX/cmd split below asks the shell rather than the platform. |
||
|
|
77ef6bb9ee | fix(terminal): verify agent prompt submission (#14962) | ||
|
|
cc74436b3c |
fix(skills): keep an unanswered skill root's last known skills instead of reporting none (#15015)
* fix(skills): keep an unanswered skill root's last known skills instead of reporting none
An aborted or shed root scan degraded to `{skills: [], unavailable: true}`. Every
consumer derives "installed" from the skill list, so an unreadable root read as
proof the skill was gone and an already-installed skill offered Install again.
- discovery: retain the last answered scan per root (5 min, LRU-bounded, dropped
on install/update invalidation and when a root answers as absent) and serve it
when a later scan goes unavailable.
- source inventory: an unanswered root reports `exists: true`, so check
`skippedReason` first rather than presenting it as a successful scan.
- useInstalledAgentSkills: when an in-scope root did not answer and the skill was
not found, say the status may be incomplete instead of a bare "Not installed".
* fix(skills): do not let install verification accept an unanswered root's retained scan
Retention makes discovery serve a stalled root's last completed scan, which is the
state before an install wrote. Verification read that as proof, so reinstalling a
skill into a root that stalled could report success without reading what it wrote —
turning a retryable false negative into a false positive.
A match now counts only when a root that actually answered reached the skill, so a
symlinked placement co-owned by a healthy root still verifies.
|
||
|
|
2fdaa10fd1 |
fix(terminal): give the composing-chord deferral an owner and a ceiling (#15017)
The chord held for a live composition waits with `fallbackMs: null`, so nothing but compositionend can end it. `sendTerminalInputAfterComposition` returned void, so nobody could stop it either: the two listeners it puts on the terminal element outlived the pane, and a later composition on that element flushed the stale chord. Return a disposer from the helper and put a sender in front of it that owns every pending chord, so blur and pane teardown drop them the way the Enter path already clears its state. The sender also bounds the wait — generous enough for a conversion candidate window, and it discards rather than sends, because a chord arriving mid-preedit is the corruption the wait exists to prevent. The Enter path needs none of this: its 200 ms fallback always runs, so its listeners cannot outlive it. Pinned so that stays true. Fixes STA-4476 |
||
|
|
81d7f9b24e |
refactor: split db.ts under 400 lines (#14979)
* refactor: split db.ts under 400 lines * rm plan * fix(orchestration-db): add safety guards to database operations Add status guards to UPDATE statements to prevent late operations from overwriting changes made by concurrent requests. Validate mutation results to surface silent no-ops. Extract circuit-break threshold, add transaction wrapping, and sanitize untrusted input. Bump schema version to v28. * Add transactional safety to dispatch and message operations Wrap dispatch failures and batched message updates with SAVEPOINTs to ensure atomicity and idempotency: - Dispatch failures now check status guards and roll back if the related task update fails, preventing partial state corruption - Message batches (across multiple 500-id chunks) roll back entirely if any batch fails, avoiding partial mutations - Add tests verifying idempotency and atomicity under failure conditions * Handle concurrent writes and improve transactional safety - Remote question answering: add classification check before and after UPDATE to safely detect concurrent modifications. Prevents false success when the UPDATE loses a race. - Transaction rollback: wrap in try-catch to prevent errors from masking the original failure. - Question thread reset: use status update instead of deletion to preserve message references. * test: add answer replay and race condition edge case coverage Add test cases for answer replay idempotency, conflict detection, and a race condition between concurrent answer updates in federation relay. Also verify local question state transitions during orchestration reset. |
||
|
|
d21f63e6fc |
fix(browser): keep cookie-import warnings readable from newer hosts (#15002)
* fix(browser): keep cookie-import warnings readable from newer hosts The import summary is cast, not decoded, coming off the runtime RPC wire, so a newer host can publish a warning code or undecryptable reason this build has never seen. Both switches then matched nothing and returned undefined, which rendered as a blank toast.warning(undefined). #14683 replaced the absorbing default: arm with case 'unknown' to satisfy switch-exhaustiveness-check, which forbids default: on an exhaustive switch. Guard before each switch instead: narrow the wire value against the handled set and route anything else to a generic message. The handled sets are keyed by the unions themselves, so a fourth member still fails typecheck here. * fix(browser): reject non-string cookie-import warning discriminants Object.hasOwn coerces its key, so the membership guards admitted any value whose toString() matched a handled variant. A host that widened reason to an array sends ['unknown'], which passed the guard and then fell straight back out of the switch -- the same undefined-to-toast.warning blank the guards exist to prevent. Take unknown and check typeof first, matching isTopLevelView and isTuiAgent. The Record membership sets still fail typecheck on a new union member. |
||
|
|
f660aaab0d |
Remap SSH leases per execution host, not globally (#15009)
* Remap SSH leases per execution host, not globally SSH lease leaf IDs are now remapped within their execution host partition, preventing silently empty tabs when the same tab name exists on multiple machines after restart. Also preserve folder workspace paths exactly as provided without trimming whitespace. * rm review file --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
08bf209e40 |
fix(ci): run PR LoC scripts from the default branch, not PR head (#15016)
The PR test LoC job fetched .github/scripts/pr-test-loc-*.mjs from pull/<n>/head and ran them with node while holding a GITHUB_TOKEN scoped pull-requests: write, so PR-authored code executed under a write token. Pin the fetch to the repository default branch. base.sha is not enough: for stacked PRs it is an unreviewed feature-branch commit any collaborator can push to, while main is gated by branch protection. Also pass event data via env instead of shell interpolation, and add set -euo pipefail so a failed download cannot leave a truncated script. |
||
|
|
8ca4ed945e |
feat(terminal): report execution host and listing scope in terminal list (#14973)
* feat(terminal): report execution host and listing scope in terminal list `orca terminal list` returned rows with no host identity and no statement of what the listing covered, so a scoped listing that saw nothing read as "nothing exists anywhere" — an agent reported a live remote worker dead. Each row now carries an optional `executionHostId` derived from the PTY id (SSH and paired-runtime ids embed their owner), and the result carries an optional `hostScope` naming the hosts covered and the known hosts skipped. Both are surfaced in `--json` and in the human-readable CLI output, where an absent field renders as `unknown` rather than `local`. Both row builders route through one resolver, so the rule lives in one place. * fix(terminal): preserve unverifiable host scope * fix(terminal): fail closed on unverifiable hosts * test(terminal): name unverifiable scope explicitly * perf(terminal): keep graph hydration host scans narrow * fix(terminal): reject blank foreign host owners * fix(terminal): validate inferred inventory hosts * fix(terminal): preserve paired folder host scope * fix(terminal): keep inventory host inference typed * fix(terminal): disclose paired folder hosts |
||
|
|
b36456007e |
fix(repos): never probe the client filesystem for a remote repo icon (#14947)
detectRepoFileIcon fell back to a LOCAL read whenever the SSH filesystem provider was absent, so a disconnected/not-yet-reattached remote repo whose path also exists on the client picked up the wrong repository's icon. Thread the connection identity through and fail closed, matching the rule already stated in connection-context.ts and repo-default-branch.ts. |
||
|
|
0ac2e77db1 |
fix(agent-hooks): default-form managed hook vars so a static precheck cannot reject them (#14994)
The managed hook command embedded a bare $SYSTEMROOT. Grok loads Claude's
settings.json hooks and statically prechecks env vars across the whole command
string, so the reference inside the never-taken Windows branch made it refuse
the hook on macOS on every event:
hook not executed: required env var(s) not set: ${SYSTEMROOT}
Grok fails these open, and Orca installs Grok's native hook separately, so no
status was lost -- the symptom is a swallowed failure line per tool call.
$VAR and ${VAR-} expand identically in POSIX shells absent set -u, so this has
no execution-time effect; only the static precheck observes it. Verified in Git
Bash on Windows that both guard forms resolve identically ($SYSTEMROOT is set
there and uppercase is the correct spelling -- $SystemRoot is undefined).
Also converts the three bare $HOME references so the regression test can assert
zero bare variable references with no exemption. A $SYSTEMROOT-specific check
would not have caught this class of bug being introduced elsewhere.
|
||
|
|
a9f93172cf |
Revert "fix(crash-reporting): give the parking census a retained breadcrumb slot without starving memory highwaters (#14867)" (#15005)
This reverts commit
|
||
|
|
fed6a7d4fd |
Rethrow update errors after lineage recovery attempt (#15008)
Previously silent failures are now rethrown after recovery attempts. Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
84784f5393 |
Split pull request page (#14853)
* refactor: split PullRequestPage.tsx under 400 lines Move the 5888-line PR page into nested domain modules under src/renderer/src/components/pull-request-page/ and leave a thin public barrel. No intentional behavior change. * rm plan * Improve React stability and remove manual ref caching - Stabilize React keys in CheckDetailsPanel using content fields instead of array indices to prevent unnecessary remounting - Remove manual ref-based entries cache in PRFilesCombinedDiffViewer, rely on useMemo dependency (diffEntrySignature) instead - Move sectionsRef assignment to useLayoutEffect to avoid render-phase ref writes - Refactor usePRFileSectionLoader to destructure args for readability * Improve PR page stability: add error handling and fix race conditions - Add error handling with user feedback (toast notifications) for comment submission, review comments, diff loading, and file view syncing - Internationalize hardcoded strings for PR state labels and error messages - Fix race condition in reviewer submission by using a ref-based guard instead of render-time state - Fix scroll restoration to avoid overwriting target positions with intermediate clamp values - Add effectiveRepoId parameter for proper repo context in review operations - Disable reviewer picker during submission to prevent concurrent requests * Improve PR page stability: add timeout and stable list keys - Add 45s timeout for diff loading to prevent indefinite hangs - Fix React list key generation for annotations/jobs using content-based keys with occurrence tracking - Refactor scroll position caching to properly handle mid-restore teardown - Replace interpolated error messages with full locale-specific strings for close/reopen actions * Fix PR diff viewer cache isolation and list key collisions - Changed list key generation from string concatenation to JSON serialization to avoid collisions with actual content keys - Added host-aware scoping to diff view caches so local and remote execution don't share entries - Optimized virtualizer keys to use lightweight revision counter instead of full serialized signature * Extract PR file state into entry-scoped hooks Replace manual state resets with custom hooks that automatically clear section heights and active section when switching PR entries. This prevents state leakage between files and simplifies the diff viewer component. Also validates the active section key exists before passing it to child components. * Improve PR page error messages, accessibility, and stability - Show actual error messages from failed operations instead of generic fallbacks - Add aria attributes for combobox/listbox patterns and proper option identifiers - Consolidate duplicate label/assignee update logic and fix event listener passive mode - Memoize GitHub source runtime to prevent stale closure in checks callbacks - Extract filled state badge tone for reuse and fix workspace attachment type - Guard textarea shortcuts against concurrent saves and add mention query test * Add explicit PR file content cache eviction Extract PRFileContentRequestArgs type and create evictPRFileContentRequest function to handle cache eviction explicitly. Call eviction on load timeout so retries fetch fresh content. Adds tests for cache behavior. |
||
|
|
71bbab72e1 |
fix(commit-message): keep Windows paths intact in agent command overrides (#14984)
* fix(commit-message): keep Windows paths intact in agent command overrides `tokenizeCustomCommandTemplate` applies POSIX backslash-escape rules on every platform. On Windows `\` is the path separator, so a native absolute path in an agent command override is silently destroyed: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -> C:WindowsSystem32WindowsPowerShellv1.0powershell.exe which is then reported as not found on PATH. The agent *startup* path already routes Windows shells to the Windows tokenizer, but the commit-message AI path still calls the generic tokenizer directly, so overrides, extra CLI args and custom commands there are all affected. The tokenizer gains an explicit `'escape' | 'literal'` mode rather than reading `process.platform`, because the same template can be parsed on one host and executed on another. `'escape'` stays the default, so POSIX behaviour — where `foo\ bar` is deliberately one token — is unchanged. `'literal'` is selected only where the command provably runs on native Windows: a LOCAL target, on win32, with no WSL distro. A WSL target runs a Linux binary inside the distro, and a remote target runs on a host whose platform this process cannot see; both keep POSIX escaping. Fixes #11375 * test: pin the platform decision for literal-backslash parsing commandBackslashMode is the only place that reads the platform, so it is where this can be wrong in the direction that matters — applying Windows rules to a command that will actually run under a POSIX shell. WSL and remote targets are pinned explicitly; both were previously untested. |
||
|
|
80b0ab16ff |
test(crash-reporting): keep ambiguous whole-tree kills reportable (#14667)
* test(crash-reporting): keep ambiguous tree kills reportable * test(crash-reporting): drop the stale sibling-settle deferral comment |
||
|
|
88b1a69824 | Fix Windows horizontal computer-use scroll (#14727) | ||
|
|
8b6d0231b2 | feat(i18n): localize Automations settings and navigation to Korean (#15004) | ||
|
|
226cf88ba6 |
fix(terminal): inset the grid inside the xterm surface (#14583)
* fix(terminal): inset the grid inside the xterm surface (#13252) Padding X/Y was applied as start-edge container margin, so the cell grid stayed flush on the trailing edges and a fractional background opacity stacked a darker gutter around the viewport. Put the setting on .xterm so FitAddon insets both axes and the themed background fills the pad. * fix(terminal): normalize padding before fit * fix(terminal): align stored and fitted padding * test(terminal): lock padding before fit * fix terminal padding opacity compositing * fix live terminal padding backgrounds * fix(terminal): preserve source-over alpha blending * fix(terminal): restore WebGL alpha blending * test(terminal): complete hidden retention pane fixture |
||
|
|
02ba70a847 |
fix(agent-hooks): make the Windows managed hook survive Claude-hooks-compat consumers (#14825)
* fix(agent-hooks): make the Windows managed hook survive Claude-hooks-compat consumers `~/.claude/settings.json` is not read only by Claude Code. Third-party Claude-hooks-compat layers (cursor-agent, Devin) import the same file and reimplement hook execution, so Orca's entry has to survive consumers that support strictly less than the documented schema. Three separate defects came from assuming otherwise. 1. The entry depended on `args`, which a compat consumer ignores. `args` is valid Claude Code syntax, but cursor-agent spawns `command` alone -- so `conhost.exe` ran bare, which opens an interactive console that never closes. Hook payloads were typed into those stranded shells (#14815). The entry is now one self-contained `command` string that depends on nothing optional. 2. `conhost.exe --headless` never relayed anything. It implements the ConPTY server protocol, not a generic no-window wrapper: it does not wait for the hosted process and relays neither exit code nor stdout. Measured directly -- `conhost --headless cmd /c "echo X& exit /b 42"` yields empty stdout and no exit code, while the replacement returns both and waits. So every hook was fire-and-forget, and whatever it printed was discarded. Replaced with `-WindowStyle Hidden`, which suppresses the window and keeps wait/exit-code/stdout intact. 3. The hook never wrote anything to stdout. Guards exited silently and curl's output went to nul. Claude Code documents empty stdout as "no decision", but cursor-agent treats PreToolUse as a permission gate, fails to parse empty stdout as JSON, and blocks the tool call -- so every shell command in every cursor-agent session on Windows failed (#14818). The script now writes `{}` first, on both the Windows and POSIX branches, which is documented to be identical to writing nothing for real Claude Code. Gemini and Antigravity already did this. Defects 2 and 3 are causally linked: `{}` cannot reach any consumer while conhost is swallowing stdout, so neither fix works without the other. Also fixed while establishing the contract: - The launcher's own missing-script fallback returned empty stdout, reproducing #14818 whenever `~/.orca` was cleaned or an install was half-finished. It now emits `{}` too. - PowerShell serializes progress records to stderr as CLIXML when stderr is redirected; a consumer merging stderr into stdout would see those bytes before the JSON. Every encoded payload now silences progress. - `runtime-home-hook-command.ts` built its own launcher without window suppression -- exactly the drift #14815 asks to prevent. All launcher construction now goes through `windows-powershell-hook-launcher.ts`, so the switch list cannot be present in one installer and missing in another. - Renamed `usesWindowsHeadlessHook` to `usesWindowsPowerShellLauncher`; nothing is headless anymore, and the flag selects a launcher. Testing: the new regression test asserts the effect a consumer observes -- it runs the exact `command` string from settings.json through both cmd.exe and Git Bash, across the guard-exit, reached-curl, and missing-script paths, and parses stdout. Verified it fails when `conhost --headless` is reintroduced. The previous tests all asserted installer intent, which is why they passed through all three defects. * fix(agent-hooks): close hook launcher review gaps --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
5e9e38fa75 |
fix(agent-status): announce Claude turn complete while background work runs (#14580)
* fix(agent-status): announce Claude turn complete while background work runs Lead Stop/StopFailure already ends the turn, but resolveClaudePaneState keeps the pane working for subagents, background shells, and session crons. That erases the working→done edge that mints the completion banner: subagent turns notify late with a stale body, and shells/crons never notify at all. Stamp turnCompletedAt on the gated lead Stop, announce immediately from that row, and pair the later all-clear done to the same end time so it cannot double-fire or collapse consecutive turns onto the pinned stateStartedAt. Fixes #13245 * fix(agent-status): suppress stamped turn replays * fix(agent-status): notify paired clients at turn end * Fix late-paired completion notification arming * test(agent-status): make the notification-id test fail on the pre-fix ordering The stored row inherited the helper's default codex agentType while the event named claude, so agentSnapshotMatchesExplicitTitle dropped it and freshStoredAgentStatus was undefined — the assertion held under either side of the `??`. Name the stored row's agent so the pinned working row survives and the snapshot-first precedence is what the test actually pins. * Suppress stamped completion tail replays * Prevent cross-coordinator title replays * Preserve stamped tails across fallback signals * Keep remount replay state while sibling lives * Scope OSC turn stamp preservation * Forward paired host completion stamps * Deduplicate paired completion tails * Bind paired completion tails to their turn * Preserve paired completion tail ownership * Keep paired tail replay state across remounts * Seed paired recovery without replaying completions * Seed startup replay and release stale fallback dedupe * fix(notifications): preserve stamped OSC repaints * fix(notifications): retain paired client turn boundary * fix notifications module import safety * chore: keep main integration focused * fix: preserve completion re-enable boundary |