Repository navigation
Sync upstream main (4ed095d328, 206 commits) - #6
Merged
Merged
Conversation
…cket, not TCP loopback (stablyai#31256) * test(daemon): run the stalled-reader backpressure test over a Unix socket, not TCP loopback Linux autotunes TCP loopback buffers to several MiB, so the kernel absorbed most of the 8 MiB flood, write callbacks completed, and the producer never reached the 4 MiB high-water mark on some CI runners. The daemon stream runs over a Unix socket or named pipe, whose small fixed kernel buffers keep the backlog in the daemon's own accounting. * test(daemon): bound Unix socket fixture paths
…30419) * fix(native-chat): a chat's visuals folder lives as long as the chat A chat's scratch folder was deleted once the chat had gone 30 days unused, so an old chat's inline visuals turned into "Visualization unavailable" while the chat and its history stayed. The common pattern keeps a chat's files for as long as the chat and never deletes them on an "unused for N days" timer. The sweep now removes only folders no chat record maps to, plus leftover .expired folders older builds parked. Everything that existed only to make the age rule safe goes with it: the per-folder save lock and its expiry candidates, the session-lane idle check, the journal last-activity query, and the migration's fresh-window timestamp. The visuals skill no longer tells agents the folder is temporary. * fix(native-chat): an image sent in a local chat lives as long as the chat A local chat's pasted images lived in one paste folder that deleted every file older than 30 days, sent or not, so an old chat's images turned into label chips. Paired-server chats already keep pastes in the host's attachment store, where the send that names a file claims it for its chat and only unclaimed files are ever swept: the common pattern. A local composer paste now goes into this machine's own attachment store (starting the local chat host first), so its send claims it the same way. The old paste folder is no longer written or swept: messages sent before this change may name its files. Restored drafts still read it. An unclaimed upload now waits 30 days instead of one before the sweep takes it. A draft names its image by this path, so the window is how long an unsent draft keeps its image; 30 days is what local drafts had, and it outlasts the host's send-id window so a resent draft still finds its image. A chat folder an older build set aside as unused (<hash>.expired) goes back to its chat instead of being deleted. * fix(native-chat): a terminal-backed chat's pastes are never swept A terminal-backed chat has no journal, so no send claims its pastes and nothing can tell a sent one from an abandoned one. Its pastes therefore stay in the paste folder, which is no longer swept, rather than in the attachment store, whose sweep would take them after 30 days unclaimed. Only a local structured chat's paste (forStructuredChat) goes to the store, where its send claims it. * fix(native-chat): keep the attachment owner's literal kind for structured local chats * fix(native-chat): a screenshot dragged into a chat lives as long as the chat On macOS a screenshot dragged from its thumbnail is copied into $TMPDIR/orca-drops, and that copy was deleted 7 days after the drop, so a sent dropped screenshot turned into a label chip after a week. The common pattern keeps a file sent in a chat as long as the chat. A local structured chat's composer now moves each such copy into this machine's chat attachment store, where the send that names it claims it for the chat. Every other copy (terminal panes, terminal-backed chats, editors, startup prompts) has no owner Orca can tell, so copies are no longer swept at all. The copy budget now bounds one drop instead of every copy still on disk: without a sweep, counting earlier copies would refuse every later drop. * style: wrap the drop-copy call * test: drop an unused import * test(ipc): expect the local chat store option on the clipboard registrar * test(ipc): mock the drop-copy registrar in the core handler test * Store fresh chat drops before publishing their paths * Type the drop preparation fixture with its request contract * Capture immutable drop batches before attachment publication * Set explicit buffers for cross-volume attachment imports * Control the deadline in the drop abandonment test * Return void from the drop test timer cleanup * Use the captured attachment destination for queued drops * Register the paired runtime in the queued drop test
…ist (stablyai#30641) * refactor(protocol): each area owns its published runtime capability list RUNTIME_CAPABILITIES now spreads one array per area instead of a flat hand-appended list, and runtime-capability-constants.ts is split into the areas that own each constant. The published set is unchanged and pinned by an exact sorted test. * test: preserve capability baseline while allowing additions
…nd (stablyai#30645) * refactor(native-chat): remove three chat mechanisms nothing drives - The adapter's optional startAnswered hook lost its only implementer in stablyai#27925; it was still forwarded through four layers and always read as answered. A start that never answered is read from the child's phase, as the exit already does. - The saved lease's journalCheckpoint is only ever written as null and read only by validation. Records an older build saved with it still load: it joins the retired lease fields dropped at load, so the next write sheds it. - The record store's setConversationCommand had no production caller. The tests that stood in for an older build with it now seed the saved row and reopen the store; three that only re-pinned another test are removed. * test(native-chat): an older build's null journal checkpoint also loads * Keep startAnswered cleanup in its owning PR * Keep historical chat fixtures aligned with their readers
…ontroller (stablyai#30654) * fix(github): PR conflict previews fetch the base branch through the fetch controller The conflict summary on a PR card spawned its own `git fetch origin <base>`, outside RuntimeRemoteFetchController: it refetched a base a worktree create had just refreshed (and vice versa), ran git's auto-maintenance in the background, and never armed the idle loose-ref sweep. It now asks the runtime's controller for the exact-base refresh creates already use (same argv, per-remote queue, dedupe and freshness), waiting at most the old 10 s before falling back to the local ref. The runtime registers its controller at construction, since the IPC and poller paths have no runtime handle. Also corrects the controller comment that claimed every fetch skips auto-maintenance: full fetches keep it on purpose (stablyai#8039). * test(github): read the mocked fetch's WSL option without a type assertion * test(github): give the conflicting fallback-lookup test a git mock for its conflict summary * test(github): check WSL routing without duplicating fetch policy * fix(git): bound preview fetch ownership without cancelling create * Preserve repository identity after preview cancellation
…st (stablyai#30632) tsconfig.cli.json listed 284 src/main files because composite made any unlisted import fail the parallel typecheck with TS6307. Nothing references it as a project, so drop composite and keep only the CLI's roots plus the backup worker entry, which is loaded by path. The Electron import ratchet now also walks the CLI entry, replacing the fence the list implied.
…ai#31294) Co-authored-by: Claude <noreply@anthropic.com>
…aning the grandchild (stablyai#29501) * fix(process-host): let a slow Windows taskkill finish instead of orphaning the grandchild Co-Authored-By: Claude <noreply@anthropic.com> * fix(process-host): abandon taskkill shortly after the root exits so it cannot walk a reissued pid Co-Authored-By: Claude <noreply@anthropic.com> * test(process-host): log the taskkill outcome and caller settle time under load Co-Authored-By: Claude <noreply@anthropic.com> * ci: register the late-taskkill Windows test in the package-windows path list Co-Authored-By: Claude <noreply@anthropic.com> * fix(process-host): release the git admission slot on root close again; cap the background taskkill at 30 s Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…t into state, evidence and monitor (stablyai#29506) * test(wsl-hosts): pin every add-flow branch and each liveness building block * test(wsl-hosts): tighten add-flow and liveness characterization per review; pin main's release on a failed connect * refactor(wsl-hosts): move the add flow to wsl-host-registration; split liveness into state, evidence and monitor
* docs: add test anti-pattern reference * docs: note real-time test waits * docs: qualify redundant test guidance * docs: fix agent guide formatting
…1b-1, unused) (stablyai#31272) * feat(editor): unused editor-buffer identity key (phase 1b-1) Adds `editorBufferIdentityOf`, not consumed yet: [environment, host, pairing root, path key] with exact case, normalized Windows separators and drive letter, WSL alias folding only for a local Windows client, SSH keyed with its runtime environment, and a detached record id for retired hosts. Splits `resolveEditorModelOwner` out of the owner-key function; the owner key is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> * fix(editor): editor-buffer identity keys can't falsely merge files (phase 1b-1 review) - A folded WSL alias is a tagged tuple, so a real `\\wsl\<distro>` share can't collide with it. - `//` is ambiguous between POSIX and UNC, so it folds only for a known-Windows (local) host; drive and `\\` paths still fold separators and the drive letter on any host. - A proven UNC path folds its server name, matching the Monaco URI authority today. - SSH identity includes the target's endpoint incarnation: a target id edited to point at another machine gets a new key, a reconnect keeps it; an unknown endpoint never shares. - Documents the live pairing resolution and per-event retirement ids. Co-Authored-By: Claude <noreply@anthropic.com> * fix(editor): identity takes the tab's captured SSH endpoint and a verified host path flavor - The SSH endpoint is the one the tab captured at open, passed in by the caller; a live lookup would match an old tab to whatever machine the target id names now. Missing means never share. - Remote hosts accept a verified path flavor (`windows` | `posix` | `unknown`), so a Windows host's `//SERVER/share` folds like `\\SERVER\share`, as the Monaco URI authority does today. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
… has no terminal yet (dark) (stablyai#31307) * wip(layout): applyDefaultTabs command in the layout model (dark) * wip(layout): applyDefaultTabs takes the window's proposed ids; main refuses a taken one * wip(layout): main closes or keeps an exited split pane by the window's rule (dark) * wip(layout): a create or split sent again after its close is refused (dark) * wip: typed test doubles * wip(layout): main writes a worktree's default tabs in one command and starts them, window or none (dark; twin e) * wip(layout): main writes a worktree's default tabs in one command and starts them, window or none (dark; twin e) * wip(layout): main provisions setup and default tabs even when the agent fails to start; no window takes over (dark) * wip(layout): pane start status in frames; a failed start keeps its pane; a terminal close stands after a failed write (dark) * wip: lint fixes (max-lines, test throws) * test(layout): a terminal close stands after a real failed profile write; switch off still refuses * fix(layout): checked types in the close-failure rule and its tests * fix(layout): a failed start keeps its pane whatever failed; default tabs start once; a tab's last pane keeps its card (dark) * test(layout): the agent's kept pane counts as the workspace's terminal; main starts it whatever failed * fix(layout): the switch-off create path gains no await * fix(layout): a failed start never writes back a pane the user closed (dark) * fix(layout): a failed start's pane goes through main's create rules, so a closed tab stays closed (dark) * test(layout): D1a's switch-on close tests assert the intent main receives (both builds) * test(layout): the close fixture answers main's command without naming the method * fix(layout): a re-created pane binds as designed; the id-only ratchet reads a shared import (dark) * fix(layout): a pane's start status may sit on a pane still bound to its old terminal (dark) * fix(layout): a pane's start status ends when its pane binds again by any path (dark) * test(layout): same-id restore ends a status; a replayed split brings back a closed pane by design * test(layout): terminal reply fixtures captured from the window-owned build, and main's switch-on proofs * wip(layout): main's terminal surface index feeds the runtime graph (dark) * wip(layout): main's panes stand in for the window's terminal list (dark) * test(layout): the window sends main-listed panes as mount facts (dark) * test(layout): surface proofs use public runtime calls * chore: lint * feat(layout): close by handle closes in main's layout; the close-time relist goes (dark) * feat(layout): main's binding outranks a caller's pane claim on the terminal record (dark) * test(layout): window-list tests stay on the rollback build; switch-on mount-fact cases (dark) * wip(layout): main's terminal index reads E1's shared projection and follows its operation ends (dark) * refactor(layout): the main-surfaces host lives beside the session-tabs readers, keeping the graph file under its line cap (dark) * fix(layout): main's panes skip the window-era membership fence, follow workspace routing, and list a terminal when it registers; window close only hands the graph back (dark) * refactor(runtime): explicit-close PTY stops move unchanged to their own module, for the file's line cap * test(layout): the cold-restore table runs in a layout-owner build too: one push per registration * test(layout): the parked and automation pane tests assert the same window report in both builds (mount facts with the switch on) * chore: one layout-owner switch import after the restack on D1a * feat(layout): main's terminal.list rows carry the host's verdict; a pane with a status is never live (dark) * chore(layout): fit main's surfaces under the lint gates after the D1a restack * feat(layout): every pane status D1a publishes keeps a row from reading live; remote host answers pass through (dark) * feat(layout): a drag-out moves main's own binding and refuses the home copy's last pane; the window's terminal claim is not consulted (L127, dark) * fix(layout): a tab close by a starting pane's handle stops the tab's terminals; a starting pane is never exited on the inventory alone; terminal.list lists every saved pane main owns; the pane claim lookup leaves the output path (dark) * fix(terminal): keep shipped exit and default-tab behaviour with the layout switch on An exited pane closes through the legacy retirement unless the keep rule holds; the default tab's title and color still reach the window and the phone's live tabs. * fix(terminal): one id guard for every pane write; keep panes on an unvouched -1 Main's taken and closed-id guard now runs where every pane write goes, so a runtime create replayed after its tab closed is refused before it spawns. An exit code nothing vouches for keeps the pane, as the window does; a kept pane sends the phone no retirement. Converts the switch-on tests that asserted the deleted window hand-off and title bridge. * fix(terminal): keep the closed-tab guard on every pane write, held-id checks on client commands A paired host starts each pane of a client's tab as a create under the tab's id, so a held tab id is refused only for a client's own layout command. * fix(terminal): a provider-reported -1 keeps the pane, as the window does Only a requested stop or a host-confirmed death closes a pane on a negative exit code. Pins that the closed-tab guard never fires in the shipped build. * chore(terminal): satisfy the exhaustive-switch and test typing checks * test(terminal): give the replay test's live process a cwd * fix(layout): main's index reads routing only for a workspace two partitions hold, the fence reads ownership once, a legacy pane keeps its claim; switch-on tests assert main's answers after a window closes (dark) * refactor(runtime): a worktree stop's terminal collection moves unchanged to its own module, for the file's line cap * fix(layout): main's index routes every workspace as the layout stream does, re-reading routing only on a write or a terminal registration; a worktree stop names every terminal main binds; a starting pane is unverifiable (dark) * feat(layout): every pane status main publishes keeps a row from reading live; a resuming pane is unverifiable like a starting one (dark) * fix(runtime): a CLI send, wait or read on a saved pane main holds starts it once first (switch-on builds only) * fix(runtime): the caller's handle names the terminal a saved pane starts; terminal.send starts it before its checks (switch-on builds only) * fix(runtime): act on the started terminal's handle until main binds the pane; bound the start by the caller's abort and wait deadline (switch-on builds only) * fix(runtime): a restored pane's start leaves the rest of a wait's deadline and cleans its bounds on first settle; tighter A1c tests * fix(runtime): a restored pane's start is capped by the pending-terminal create bound for every CLI entry point (runtime_timeout) * fix(runtime): only a handle whose saved pane needs a start goes async; every other send, wait and read stays synchronous as before --------- Co-authored-by: E2E Test <e2e@test.local>
…s update, never drawing them itself (dark, layout-owner build only) (stablyai#31257) * The window shows each layout records frame record by record, without reading its session back (dark, layout-owner build only) A records frame no longer re-serializes the window's whole session and runs the Loader. Panes' terminal bindings and live titles apply alone, to the tabs the frame names; any other record builds only its own workspace's slices, with the view and facts read from the store. Only a workspace the store does not show from frames yet (startup, a reopened stream, a first visit, one sent whole) takes the full build. Tests install an apply check that, after every flush, compares the store with the full build for the same cache and pre-flush store; the shared layout-apply transport now sends main's records frames and fails a flush that differs. * A frame of only tab rows shows without reading the window's session back or running the Loader (test, layout-owner build only) * The account-restart test's fake binding runtime carries per-pane binding generations, so it passes with the switch on (test only) * The account-restart test types its store writes, so its five old e2e type errors are gone (test only) * A workspace build reads the editor selection and groups through the save's own writers, as the full build does (dark, layout-owner build only) * A tab released from the window's hold shows the binding main wrote meanwhile (test, layout-owner build only) * The frame-apply check still checks a flush's other workspaces when it builds one in full, and the shared transport keeps a test's own check (test only) * The frame-apply check is proven to catch a mismatch beside a full build, and the shared transport restores only the check it installed (test only) * A tab move shows from main's frame alone; the window no longer re-applies it or adopts main's group id (dark, layout-owner build only) On main's answer the window only selects the moved tab in its new group, a view choice the frame does not carry. While main's stream is down it still moves the tab itself, as the frame sync's closed-stream rule says, but under its own group id: the reopened stream's snapshot redraws main's copy. The three switch-on census files for tab moves now pass without a switch mock: the context menu's move routes to the tab bar's move, the drag hook's pane-column split goes to main in a layout-owner build, and the tab bar's own move is pinned on a paired server's workspace. A new frame test pins each move through main and its frame. * A tab's rename, color, pin, view and a pane's title show from main's frame alone; the window no longer applies them again (dark, layout-owner build only) * A divider drag sends main the ratios it moved, not the window's tree, and an in-tab pane move shows from main's frame; the ratio hold is gone (dark, layout-owner build only) * Divider ratio params and their command tests get their own modules (file size) * Layout-writer baseline pruned: the tab move no longer writes the layout from the window * The window never draws a tab prop, pane title or in-tab move itself; while main's stream is down the reconnect's snapshot shows them (dark, layout-owner build only) * View-mode test seeds its paired-server workspace without a type assertion * A moved tab is selected in the frame flush that shows the move, unless the user selected since; main's answer no longer selects it (L120, dark, layout-owner build only) * Frame sync stays within its line budget * Frame sync writes its patches through the moved-tab selection, within its line budget * The moved tab's selection reuses activateTab's own store update, so no new layout writer is added (L120) * Format the tab-move frame test * A tab moved while main's stream is down is selected by the reconnect's snapshot that shows the move (L120) * A generated tab title main gave is drawn by its frame alone; the window writes only titles left to it as today, and the rename test checks the phone graph republishes (dark, layout-owner build only) * A divider ratio names the split it means, and main refuses it where that path holds another, so the window snaps back to main's ratios; a drag back to main's ratio before it answers is sent too (dark, layout-owner build only) * test(layout-owner): tab moves drawn from frames make no window tab or group writes * test(layout-owner): a reopened tab shows its title and color from main's frame * test(layout-owner): the tab bar's preview pin through main ends the preview and counts a use * test(layout-owner): read main's generated-title renames without a type assertion * test(layout-owner): type the sent renames so the title helper reads them without an assertion * The frame-apply check selects the moved tabs the flush selected, so the tab-move tests exit clean and a wrong selection still fails (dark, layout-owner build only) * test(layout-owner): the title batch's live labels are checked in both builds; only the generated title differs * Main keeps a tab's first agent-generated title unless the window's title replaces it, so a second prompt while main's stream is down no longer renames the tab (dark, layout-owner build only) * test(layout-owner): a revealed terminal's tab name is drawn from main's frame, not its reply (dark, layout-owner build only) --------- Co-authored-by: E2E Test <e2e@test.local>
…e place per buffer record (phase 1b-2, no behaviour change) (stablyai#31313) * feat(editor): unused editor-buffer identity key (phase 1b-1) Adds `editorBufferIdentityOf`, not consumed yet: [environment, host, pairing root, path key] with exact case, normalized Windows separators and drive letter, WSL alias folding only for a local Windows client, SSH keyed with its runtime environment, and a detached record id for retired hosts. Splits `resolveEditorModelOwner` out of the owner-key function; the owner key is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> * fix(editor): editor-buffer identity keys can't falsely merge files (phase 1b-1 review) - A folded WSL alias is a tagged tuple, so a real `\\wsl\<distro>` share can't collide with it. - `//` is ambiguous between POSIX and UNC, so it folds only for a known-Windows (local) host; drive and `\\` paths still fold separators and the drive letter on any host. - A proven UNC path folds its server name, matching the Monaco URI authority today. - SSH identity includes the target's endpoint incarnation: a target id edited to point at another machine gets a new key, a reconnect keeps it; an unknown endpoint never shares. - Documents the live pairing resolution and per-event retirement ids. Co-Authored-By: Claude <noreply@anthropic.com> * fix(editor): identity takes the tab's captured SSH endpoint and a verified host path flavor - The SSH endpoint is the one the tab captured at open, passed in by the caller; a live lookup would match an old tab to whatever machine the target id names now. Missing means never share. - Remote hosts accept a verified path flavor (`windows` | `posix` | `unknown`), so a Windows host's `//SERVER/share` folds like `\\SERVER\share`, as the Monaco URI authority does today. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(editor): route close, discard, pending flush, restart save and save keys through the buffer view index (phase 1b-2) No behaviour change. Each tab still has its own record; this adds the indirection the sharing switch will change instead of call sites: - editorBufferIdOf / editorBufferViews, maintained by the record middleware with openFiles - the invariant now covers the index, records without views, and drafts across a record's views - writeEditorBuffer, setEditorDraft and clearEditorDraft(s) write every view of the record - needsSavePromptOnClose at every close site, discardEditorBuffer, record-wide pending flush, restartSaveTargets, and injected save-queue keys (queue = model URI, timer/generation = record) Parity tests check each entry point against the old per-tab expression across a fixture matrix. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(editor): require the buffer view index, close in processing order, cheaper draft checks (phase 1b-2 review) - Lookups take a required EditorBufferViewIndex; tests pass an explicit empty index. - needsSavePromptOnClose drops the batch set: every site asks against current state as it processes each view, so a record's last open view prompts once in any order. - Drafts-only writes check draft agreement only; the index rebuild is skipped when no tab joined, left or moved record. - Artifact upload flushes through the record; the per-tab flush/has functions are gone. - Membership and adoption read record ids from the index; drop the save-key injection and the test-only useTerminalSaveDialog migration. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…anged (stablyai#31319) * fix(github): an issue-source change on a twin saves to the row the user changed setIssueSourcePreference resolved its target by repo id alone, and since stablyai#31119 that refuses a repo id with rows on two hosts. Callers now pass the row's host, the write goes through updateRepo with that host, and the optimistic patch touches only that row. Co-Authored-By: Claude <noreply@anthropic.com> * fix(github): find a twin's Tasks row by host, not path Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…ab's hover card, and add Regenerate name (stablyai#30646) * fix(native-chat): name a chat from its first message at once; the generated name replaces it A native chat's one stored name was written only by a best-effort, one-shot generation, so any failure left every surface on the 'Codex Chat' placeholder. The first prompt now names the chat when its first turn starts, and the generated name replaces it only while the field still holds that seed. * fix(native-chat): the chat names setting says a chat shows its first message until it is named * fix(native-chat): retry a failed first chat name twice before keeping the first message A failed name generation was final for the chat's lifetime. The first name now gets two retries, after 2 and 4 seconds, and stops early once the first-message name was replaced. * fix(native-chat): show a failed chat name on the tab hover card, with Regenerate name A chat whose name generation failed after every retry gave no sign of it, and nothing could run it again. The host now keeps the last failure in memory and publishes it with the chat's status while the chat still has the name it failed to replace; the tab hover card shows it with the error dialog, and a new agentSession.regenerateName (behind a capability) makes one more try. * fix: derive chat naming progress from host jobs * fix(native-chat): merge duplicate import CI caught * fix(native-chat): declare agentSession.regenerateName in the cross-version surface manifest * fix(native-chat): stub naming-failure hook in SortableTab rename shortcut test
…tablyai#31303) * perf(serve): copy the supervisor's code aside with one cp clone on macOS orca serve --supervise copies the app's unpacked code and node_modules out of the app before re-running itself from the copy. Node's fs.cp clones those ~6,000 files one at a time, which took 8-15 s in the foreground and around 35 s under the service manager's throttled I/O. One /bin/cp -cR per tree does the same copy in about 1.5 s. The clone-then-plain-copy fallback the terminal host copy already used now lives in src/shared/macos-tree-clone.ts, and both copies call it. Linux keeps fs.cp with reflink cloning, and Windows does not support --supervise. Claude-Session: serve-snapshot-clone * fix(serve): let the macOS tree copy run without a time limit and throw on failure The shared cp helper ran under runProcess's 30 s default time limit, which the old fs.cp copy never had. At background I/O priority a large tree can outlast it, so the supervisor would quietly give up and run from the app itself. The helper now runs cp with no limit; the terminal helper copy stays bounded by its deadline signal. The helper moves to src/main beside the asar-safe rm and uses it directly, so it no longer takes an rm option. It throws instead of returning a boolean, so copyBundle is gone. The relocation reads process.platform instead of a platform dependency, and its copy test now runs on every platform but Windows. Claude-Session: serve-snapshot-clone * fix(build): keep the macOS tree copy module where the packaged CLI loads it The serve supervisor's relocation code in the CLI now imports the macOS tree copy module from the main-process sources. The CLI is compiled file by file, but the later main-process build replaces out/main and keeps only the files it is told to build as entries, so the packaged CLI failed at load with "Cannot find module '../../main/macos-tree-clone'" on every platform, and the CLI runtime verifier rejected the build. Register the module as a CLI main entry, like the other main modules the CLI imports, and unpack it from the app archive. Its asar-safe removal dependency is bundled into a shared main chunk, which is already unpacked. The build output contract test now covers both. Claude-Session: serve-snapshot-clone * fix(build): unpack the asar-safe rm the CLI's tree clone imports The compiled CLI layout keeps out/main/macos-tree-clone.js requiring ./asar-transparent-fs directly, and that file was still packed, so the packaged CLI failed to load (caught by packaged-cli-assets in CI). The bundled build only passed because the main build inlines it into an unpacked chunk. Claude-Session: serve-snapshot-clone
…blyai#31300) * perf(ci): accelerate adhoc release builds and runner scheduling * Overlap adhoc release preparation and reuse qualified build inputs * Bind qualified template reuse to its embedded CLI source commit * Use the available CPUs for native compilation in fast dev builds * Verify qualified template cache provenance and daily invalidation * Avoid incremental MSBuild tracking for fresh fast-release builds * Right-size Intel macOS and Windows ARM release qualification * Route Intel qualification through the standard larger macOS label * Overlap Windows preparation and initialize the server native compiler once * Test server compiler invocation through its process-runner interface * Align adhoc signing contract with its larger packaging runner * Recognize the native cache fingerprint as batch-file metadata
…st re-pairs (stablyai#29610) Client-hosted browser pages for a server Orca deployed (WSL hosts today; managed SSH desktop tabs use the SSH route) now derive their partition from the environment and the server's public key, not the pairing revision and device id that rotate on every re-pair. Existing jars are adopted, and each binding keeps the shipping build's fingerprint (with the stable one as an alias) so a downgraded build still opens the same jar.
…tablyai#31200) * refactor(serve): move the update handoff file reads and writes into their own module Move-only: the supervisor module is at its line budget, and the handoff file IO is a separate concern from the child supervision loop. Claude-Session: serve-supervisor-reexec * fix(serve): restart the macOS serve supervisor from the updated app After a remote update on macOS, the node-mode supervisor kept running the old Orca executable, which the installer moved aside and deleted. Under a LaunchAgent the system charges every descendant's privacy permissions (Accessibility, Screen Recording, files) to that supervisor, so once its path no longer resolved, screen capture and computer use failed for the whole served tree while the permission check still reported them granted. Once the new bundle is verified on disk and no stop is pending, the supervisor now re-runs itself in place (same pid) from the installed executable instead of spawning the replacement itself. The handoff stays install-requested, so startup's existing resume path spawns, verifies and completes it. A --supervise supervisor that runs from its copy outside the app restarts through the app's own CLI entry, so it copies the new release aside again rather than mixing old scripts with the new executable. If the re-run fails it logs and starts the replacement as before. Only applies when serving our own executable on macOS; Linux, Windows, orcad and overridden executables are unchanged. Claude-Session: serve-supervisor-reexec * fix(serve): restart a supervisor whose executable the install replaced Three review fixes on top of the update restart: - Decide the restart by file identity instead of which path observed the install. The supervisor records the device and inode of its executable at start; after any install wait (including resuming an interrupted update at startup) it restarts only if the installed executable is a different file. A supervisor that started on the old app before the swap now restarts too, and the process after a restart never restarts again. - A relocated --supervise supervisor releases its marker on the copy it is leaving, so the next relocation can delete that copy. Before, every update kept one more full copy of the app's scripts for the life of the service. - A failed process.execve aborts the process natively rather than throwing, so the fallback never ran. The restart now checks first that the executable can run and the script entry exists, and otherwise logs and starts the replacement directly as before. Claude-Session: serve-supervisor-reexec * fix(serve): restart a parked serve service whose app a desktop install replaced When the desktop app's Update & Restart parks an `orca serve --supervise` service for another profile, it writes no update handoff for that profile. After the install the service's outer loop just starts its next server, so neither update restart point ran and the supervisor kept the deleted executable. Every server start now runs the same identity check right after the park/stop gate and before spawning, so a supervisor whose executable was replaced restarts in place first. The check is a no-op when the executable is unchanged, so first starts and the process after a restart do not loop. The test drives the real park and commit functions, so the CLI typecheck project now includes that updater module (it imports only shared code). Claude-Session: serve-supervisor-reexec * refactor(serve): check for a replaced app in the one place every server start passes The restart check ran at three call sites and was threaded through the update loop as a hook, and each review round found another start path it missed. Every path (first start, resumed update, after an install, after a park or crash) spawns its server through the function spawnElectronServe builds, after its stop and park gates, so the check now lives in that spawn and nothing else carries it. The restart moves to its own module, the hook and the handoff-file move it forced are gone, and the tests drive serveOrcaApp instead of the hook. Claude-Session: serve-supervisor-reexec
…es to the winner instead of showing terminal_pane_owner_changed (stablyai#30448) * fix(terminal): a pane that loses its attach to another connection attaches to the winner When a terminal pane reconnected to a saved process that had exited, and another writer re-bound the pane meanwhile (most often the window's own session save), main threw terminal_pane_owner_changed. The window showed it as a raw red banner over a pane that was otherwise working, and nothing cleared it. Main now re-resolves the pane's owner once and attaches to it, with fresh absence evidence even when only the saved binding changed. Retiring a dead id's binding is fenced on its incarnation so it can never clear a newer process that reused the id. A second loss is still reported; the window asks once more for a local pane (never a fresh launch) and otherwise shows the existing reconnect banner with Retry instead of the raw code. Tracked in STA-4139. * refactor(terminal): move the stable-pane attach step into its own file (max-lines) * fix(terminal): fence an absent owner's retirement on an explicit incarnation, not the binding's own field * Complete bounded pane ownership recovery through host publication * Apply repository style rules to pane recovery changes * fix: preserve terminal adoption type contracts * fix: complete terminal recovery ownership and acceptance contracts * fix: keep terminal registration evidence with its candidate * fix: satisfy terminal recovery lint gates * fix: consolidate terminal recovery imports * test: explain bounded terminal fixture assertions * test: handle optional pane binding maps in assertions * fix: scope remote pane ownership refresh to recovery epoch * fix: reject remote pane replies after recovery epoch ends * Fix static checks for pane recovery * Reuse session partition assignment in binding retirement * fix: acquire returned terminal receipt before rejecting early exit * Release superseded terminal spawn preparation before retry * test: type preparation cleanup callbacks as functions * test: initialize telemetry classifier in preparation collision cases * test: identify preparation resources in reentry assertions * fix(terminal): let a recovered pane replace the binding its host proved dead Reopening an agent terminal whose process had exited showed "Orca couldn't verify this terminal's owner", and the next relaunch lost the tab. The pane operation proved the old PTY absent, but a window session save wrote that dead binding back before the fresh commit, and the commit treated it as a rival owner. The refused candidate was then shut down without an intentional-stop mark, so its exit retired the whole tab. The operation now carries its absence proof into the fresh launch, and both the pre-launch owner check and the commit accept a replay of that dead binding. A different or unproven owner is still contention. Discarded candidates are marked as replaced, so their exit keeps the tab. * test(terminal): type the discard test's runtime mocks * fix: expose reconnect for retained pane recovery * test: expect replaced stop for discarded fresh terminals * fix(terminal): type the absent owner's retirement fence after the main merge * test(terminal): follow main's Reconnect handler in the pane binding ownership test
…ark) (stablyai#31276) * wip(layout): applyDefaultTabs command in the layout model (dark) * wip(layout): applyDefaultTabs takes the window's proposed ids; main refuses a taken one * wip(layout): main closes or keeps an exited split pane by the window's rule (dark) * wip(layout): a create or split sent again after its close is refused (dark) * wip: typed test doubles * wip(layout): main writes a worktree's default tabs in one command and starts them, window or none (dark; twin e) * wip(layout): main writes a worktree's default tabs in one command and starts them, window or none (dark; twin e) * wip(layout): main provisions setup and default tabs even when the agent fails to start; no window takes over (dark) * wip(layout): pane start status in frames; a failed start keeps its pane; a terminal close stands after a failed write (dark) * wip: lint fixes (max-lines, test throws) * test(layout): a terminal close stands after a real failed profile write; switch off still refuses * fix(layout): checked types in the close-failure rule and its tests * fix(layout): a failed start keeps its pane whatever failed; default tabs start once; a tab's last pane keeps its card (dark) * test(layout): the agent's kept pane counts as the workspace's terminal; main starts it whatever failed * fix(layout): the switch-off create path gains no await * fix(layout): a failed start never writes back a pane the user closed (dark) * fix(layout): a failed start's pane goes through main's create rules, so a closed tab stays closed (dark) * test(layout): D1a's switch-on close tests assert the intent main receives (both builds) * test(layout): the close fixture answers main's command without naming the method * fix(layout): a re-created pane binds as designed; the id-only ratchet reads a shared import (dark) * fix(layout): a pane's start status may sit on a pane still bound to its old terminal (dark) * fix(layout): a pane's start status ends when its pane binds again by any path (dark) * test(layout): same-id restore ends a status; a replayed split brings back a closed pane by design * test(layout): terminal reply fixtures captured from the window-owned build, and main's switch-on proofs * wip(layout): main's terminal surface index feeds the runtime graph (dark) * wip(layout): main's panes stand in for the window's terminal list (dark) * test(layout): the window sends main-listed panes as mount facts (dark) * test(layout): surface proofs use public runtime calls * chore: lint * feat(layout): close by handle closes in main's layout; the close-time relist goes (dark) * feat(layout): main's binding outranks a caller's pane claim on the terminal record (dark) * test(layout): window-list tests stay on the rollback build; switch-on mount-fact cases (dark) * wip(layout): main's terminal index reads E1's shared projection and follows its operation ends (dark) * refactor(layout): the main-surfaces host lives beside the session-tabs readers, keeping the graph file under its line cap (dark) * fix(layout): main's panes skip the window-era membership fence, follow workspace routing, and list a terminal when it registers; window close only hands the graph back (dark) * refactor(runtime): explicit-close PTY stops move unchanged to their own module, for the file's line cap * test(layout): the cold-restore table runs in a layout-owner build too: one push per registration * test(layout): the parked and automation pane tests assert the same window report in both builds (mount facts with the switch on) * chore: one layout-owner switch import after the restack on D1a * feat(layout): main's terminal.list rows carry the host's verdict; a pane with a status is never live (dark) * chore(layout): fit main's surfaces under the lint gates after the D1a restack * feat(layout): every pane status D1a publishes keeps a row from reading live; remote host answers pass through (dark) * feat(layout): a drag-out moves main's own binding and refuses the home copy's last pane; the window's terminal claim is not consulted (L127, dark) * fix(layout): a tab close by a starting pane's handle stops the tab's terminals; a starting pane is never exited on the inventory alone; terminal.list lists every saved pane main owns; the pane claim lookup leaves the output path (dark) * fix(terminal): keep shipped exit and default-tab behaviour with the layout switch on An exited pane closes through the legacy retirement unless the keep rule holds; the default tab's title and color still reach the window and the phone's live tabs. * fix(terminal): one id guard for every pane write; keep panes on an unvouched -1 Main's taken and closed-id guard now runs where every pane write goes, so a runtime create replayed after its tab closed is refused before it spawns. An exit code nothing vouches for keeps the pane, as the window does; a kept pane sends the phone no retirement. Converts the switch-on tests that asserted the deleted window hand-off and title bridge. * fix(terminal): keep the closed-tab guard on every pane write, held-id checks on client commands A paired host starts each pane of a client's tab as a create under the tab's id, so a held tab id is refused only for a client's own layout command. * fix(terminal): a provider-reported -1 keeps the pane, as the window does Only a requested stop or a host-confirmed death closes a pane on a negative exit code. Pins that the closed-tab guard never fires in the shipped build. * chore(terminal): satisfy the exhaustive-switch and test typing checks * test(terminal): give the replay test's live process a cwd * fix(layout): main's index reads routing only for a workspace two partitions hold, the fence reads ownership once, a legacy pane keeps its claim; switch-on tests assert main's answers after a window closes (dark) * refactor(runtime): a worktree stop's terminal collection moves unchanged to its own module, for the file's line cap * fix(layout): main's index routes every workspace as the layout stream does, re-reading routing only on a write or a terminal registration; a worktree stop names every terminal main binds; a starting pane is unverifiable (dark) * feat(layout): a workspace main owns publishes its terminal and editor session.tabs rows from main's layout; the window still supplies browser, chat and view-only rows until D3b (A2, dark) * chore: lint * test: type the row fixtures * chore(layout): main's session.tabs projection is an old-client entry, imported only by main's terminal surface index * fix(layout): main's session.tabs rows carry the tab's name, the window's agent status and markdown file id, list rows in tab order, and keep groups and selection consistent (A2, dark) * refactor(layout): the projection's group order moves unchanged to its own old-client module, for the file's line cap; lint * feat(layout): every pane status main publishes keeps a row from reading live; a resuming pane is unverifiable like a starting one (dark) * test(layout): a windowless server's layout change reaches a subscribed phone's session.tabs rows (dark) * fix(layout): a window-only row keeps its place in the rows, the window's selected row lands in a group that selects it, and a workspace with no main group keeps the window's groups (A2, dark) * fix(layout): a pane main has no binding for yet keeps the window's terminal in its phone row, so an SSH restore lists it pending (A2, dark; ledger L243) * fix(session-tabs): phones show the window's live selection, focused pane, dirty state and titles over main's saved view; exited terminals stay gone; stray rows join the active group (switch-on builds only) * fix(session-tabs): main's groups stand with main's selection; certified exits only; a selected stray row joins main's active group --------- Co-authored-by: E2E Test <e2e@test.local>
…ask filters pass theirs (stablyai#31263) * fix(github): GitHub routing takes the caller's host for a twin; the task filters pass theirs * fix(github): task filters take the host from their source row, not an id+path lookup
stablyai#31260) * fix(deps): root and mobile resolve one zod, and CI fails if they drift Mobile type-checks src/shared zod schemas. With mobile on zod 4.6.5 and the root on 4.6.7, TypeScript treated them as unrelated packages, so wrapped schemas inferred {} (tests/e2e/helpers/phone-feed-list.ts:154) whenever a job installed mobile deps. * fix(deps): guard every package mobile's tsconfig remaps, not only zod * test(ci): pin the shared-dependency version check to the static phase
…th and its canonical host (stablyai#31290) The fetch picked a twin's row by id first, so the SSH copy's results were cached under the local copy's key; the task-page reader keyed by the raw executionHostId, so an SSH row carrying only connectionId read the local bucket; and an unstamped row was keyed by the focused server while its fetch ran locally. The repo lookup now prefers the row matching both id and path, and writer and reader both key by getRepoExecutionHostId.
…er's held terminals (switch-on builds only) (stablyai#31275) * A paired desktop shows a layout-owner server's held panes on the existing banner; Reopen asks the server (switch-on builds only) The server's frames carry status 'held' on a pane a newer build's terminal service may still run. The desktop now lists those panes on its existing newer-build banner, with the same wording and states. Reopen calls the server's new `layout.reopenHeldPanes` under the server's ids: the server stops its newer service, as the desktop's own Reopen does, and starts each pane still held. The desktop starts nothing; the server's next frames clear the status and bind the pane. A refusal or failure shows the banner's failed state. Served and advertised (`layout.reopenHeldPanes.v1`) only by a layout-owner build, to paired desktops (not phones). A server without the capability, or that publishes no status, shows no banner, as today. * test(layout): held server pane stays Reopening until the reattach frame, stays failed after a refusal, and shows nothing without the reopen capability * fix(layout): a held pane's Reopen acts only on its own host and on main's published status (switch-on builds only) - Reopen needs the clicked pane's key; a pane that already left the banner's store does nothing, so a server banner clicked as its frame drops the pane never stops this machine's newer service. - Local versus server is decided by the pane's host, not by whether it has a release. - The banner portal for a pane with no terminal handle exists only in a layout-owner build. - The server reopens only panes its published layout shows held, the status the desktop saw. * test(layout): Reopen ignores a pane rebound to a new terminal whose held entry is still stored * test(layout): the dropped-pane Reopen test checks for the reopen method, since the view report shares the RPC client
…yai#31673) * refactor(ipc): group Linear handlers with direct owner imports * chore: keep module organization progress local
…lures (stablyai#31480) Recover workspace capabilities after transient status failures without treating an unknown answer as unsupported. Share probes, invalidate across reconnects, and bound active-sheet retries with backoff and cleanup fencing. Fixes stablyai#16347. Replaces the earlier recovery work in stablyai#16850 (already closed). Credit Brennan Benson (@brennanb2025) for the original report and recovery implementation. Verification includes 105 focused tests, deterministic retry/leak counts, native iOS evidence with disclosed fixture limits, and green CI at a1cf7b5. Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
…for a same-id workspace (dark) (stablyai#31517) * fix(sidebar): land flip-a's switch-on fixes on main, still dark (no flip) The fixes found while the sidebar ran from main's feed, without turning that on: ORCA_SIDEBAR_INPUTS_IS_MAIN stays false, and none of flip-a's stop-gaps come along. - Main stops counting a closed tab's terminal once it was asked to stop and no pane shows it; a failed or refused stop takes the request back, and a host out of contact stays unverifiable. - Main fetches a paired server's rows with the transport's normal bound, so a server slower than 5 s still lists. - A deferred unread raise flips the folder row it was decided for, and a read while the owner check is out cancels it. - Phone: no worktree.ps flash before the feed; a host's last installed feed stands in while a fresh read is out; it highlights its own last-opened workspace and a fold never scrolls; projects file under their project group with the desktop's indent; the collapse spinner shows only after 500 ms; "Reopen to update" keeps the rows on screen. - e2e helpers and journeys for the switch-on suite. * fix(sidebar): keep an owed relay stop marked, clear a thrown stop, and prune pending unread raises - A stop-and-wait that throws takes its stop request back, like one that fails. - A final stop the legacy relay still owes stays marked, since the relay retries it. - Pending unread raises are tracked only while one waits, so the map no longer grows. - The paired-server fallback poll test gives its third fetch a reply and asserts live. * fix(sidebar): key activity, review, retention and repo lookups by host, not bare id The feed published activity and PR facts by bare workspace id, so a same-id workspace on two hosts (a paired server and this desktop, or two servers) shared one host's facts. Activity and review are now keyed by row identity, retention sweeps per listed row, and the builder resolves each row's repo by its own host's registration. Bumps the feed semantics version to 3. Claude-Session: e1fdb3d9-6985-4693-84aa-9a9fad732103 * test(e2e): read a review fact by its row key Claude-Session: e1fdb3d9-6985-4693-84aa-9a9fad732103 * test(mobile): key fixture rows by row identity Claude-Session: e1fdb3d9-6985-4693-84aa-9a9fad732103 * fix(sidebar): count and label rows by their own repo registration; keep activity keyed through a failed listing Host header counts and mixed-host labels read the row's own registration, as the host filter and row now do, so one render cannot file a row under one host and count it under another. A failed listing keys activity by the last rows. Claude-Session: e1fdb3d9-6985-4693-84aa-9a9fad732103 * fix(sidebar): a slower older listing never overwrites a newer one's rows Claude-Session: e1fdb3d9-6985-4693-84aa-9a9fad732103 * test(sidebar): read the quiet-desktop PR fact by its row key Claude-Session: e1fdb3d9-6985-4693-84aa-9a9fad732103 * refactor(repos): the renderer re-exports the shared getRepoHostIdentity Claude-Session: e1fdb3d9-6985-4693-84aa-9a9fad732103
…tablyai#30848) The phone and the desktop each parsed the status row's { approval: { tool, summary } } envelope with identical validation. Move that validation and the answer keys into src/shared/native-chat-approval-envelope.ts; each caller still builds its own card (phone English labels, desktop translated labels) and keeps its own precedence and heuristics. No behaviour change. Co-authored-by: E2E Test <e2e@test.local> Co-authored-by: Claude <noreply@anthropic.com>
…e whole profile (stablyai#30931) * fix(profile-state): repair only the damaged sections instead of rolling back the whole profile One profile-state row whose checksum no longer matches made Orca refuse to open the whole profile, and the only recovery swapped in a whole backup, losing every newer change in every section. Startup now checks each section on its own. When only some sections are damaged, the dialog offers "Repair and Restart": Orca copies the damaged database aside, then replaces just those sections with the newest backup copy that passes its checks, keeping every intact section's current value, in one checked write. Headless starts name the damaged sections and `orca profile state rollback --repair`. Whole-file damage keeps the existing backup rollback. Also turns on checkpoint_fullfsync for the profile database: on macOS plain fsync leaves the drive cache unflushed, which can leave a long row's pages from two versions after a power loss or forced shutdown. Tracked in STA-10893. * test(profile-state): pin the repair dialog's text and buttons * fix(profile-state): list the repair module for the CLI typecheck; keep section labels out of the layout-writer count * fix: validate section repair admission before decoding payloads * fix(profile-state): share backup handles during section repair
… and Force intents (stablyai#31682) * fix(wsl-host): apply the SSH update rules to a WSL connect A WSL connect deployed this app's orcad whenever the active build differed, so a distro another, newer Orca had activated ended in an unconnectable deferral, and a build the host had rolled back from was re-activated on every connect. It now runs the same planManagedOrcadAutoUpdate decision SSH uses and serves the active build when that plan skips. * fix(wsl-host): serve the skipped-update incumbent only if it is still active * fix(orcad): one update decision for SSH and WSL, applied again under the fence The connect's update plan moves to its own module, and the fenced admission every first deploy and WSL connect uses now applies it, so a transient readiness failure or a race cannot reactivate a rolled-back build. * refactor(wsl-host): adopt the serving build through the SSH first-deploy adoption * fix(wsl-host): do not retry a candidate rejected earlier this launch A rejected candidate is restored without recording rolledBackFrom, so WSL retried it on every connect. SSH's launch-scoped failure memory moves to a shared helper keyed per host, and WSL feeds it to the shared update decision. A null adoption now re-reads the record before the deploy, as SSH does. * fix(wsl-host): keep Force offered when a connect skips this app's build The WSL connect's update admission moves to its own module, which returns a forceable hint when it serves the incumbent over a rolled-back or failed build. * fix(orcad): connect, Update and Force are the three update intents on SSH and WSL Only a connect applies the skips; an explicit Update retries a skipped or failed build (clearing the failure memory) behind the terminal census; only Force passes the census. WSL's Update button now sends the update intent instead of a plain connect. A connect that records a rejection serves the restored incumbent at once. * fix(orcad): an explicit Update keeps the newer-host skip on SSH and WSL Update lifts only a host's own skips (rolled back, failed before); Force lifts all. SSH's explicit update now applies the same fenced admission, so stable cannot replace a host a newer Orca activated without Force. A rejection retries adoption only once the incumbent is back and no journal awaits recovery. * fix(wsl-host): keep Update and Force offered on a deferred cold connect A connect the host deferred with nothing serving now publishes its deferral, so a host stopped after a rollback can still be retried from the UI. SSH's explicit Update forgets a remembered failure only once it lands, and a restore that could not be read keeps the rejection as reported. * fix(wsl-host): offer only Force over a host a newer Orca runs, and keep failure memory until an Update lands A host-newer refusal gets its own deferral code so the WSL row hides Update, which can never pass it. An explicit Update no longer clears the launch failure memory up front; only a landed deploy does, as on SSH. * refactor(wsl-host): merge the intent type into the existing shared imports
…lyai#31681) * fix: remediate dependency security advisories across workspaces * test: verify security patches without ad hoc dependency installs
Use port 443 when an explicit WSS host edit omits a port; preserve saved host endpoints, bare-host inheritance and explicit ports. Adapted from stablyai#31499. Co-authored-by: Chun Chet Ng <ngchunchet95@gmail.com>
…the phone (stablyai#31524) * fix(sidebar): land flip-a's switch-on fixes on main, still dark (no flip) The fixes found while the sidebar ran from main's feed, without turning that on: ORCA_SIDEBAR_INPUTS_IS_MAIN stays false, and none of flip-a's stop-gaps come along. - Main stops counting a closed tab's terminal once it was asked to stop and no pane shows it; a failed or refused stop takes the request back, and a host out of contact stays unverifiable. - Main fetches a paired server's rows with the transport's normal bound, so a server slower than 5 s still lists. - A deferred unread raise flips the folder row it was decided for, and a read while the owner check is out cancels it. - Phone: no worktree.ps flash before the feed; a host's last installed feed stands in while a fresh read is out; it highlights its own last-opened workspace and a fold never scrolls; projects file under their project group with the desktop's indent; the collapse spinner shows only after 500 ms; "Reopen to update" keeps the rows on screen. - e2e helpers and journeys for the switch-on suite. * fix(sidebar): keep an owed relay stop marked, clear a thrown stop, and prune pending unread raises - A stop-and-wait that throws takes its stop request back, like one that fails. - A final stop the legacy relay still owes stays marked, since the relay retries it. - Pending unread raises are tracked only while one waits, so the map no longer grows. - The paired-server fallback poll test gives its third fetch a reply and asserts live. * refactor(sidebar): read the sidebar feed through one shared client library on desktop and phone The window and the phone each had their own feed reader. Both now use one reader under src/shared/sidebar-feed-client: paging, notices, retries and the snapshot it installs. A snapshot reuses each unchanged entity's parsed object, so one row's change re-parses one row instead of its whole domain. Adds the gesture and view-report pieces later cuts wire up. Lists are unchanged on both sides. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(sidebar): drop the feed client's unused pieces until their callers land Co-Authored-By: Claude <noreply@anthropic.com> * test(sidebar): key the snapshot test's rows by host identity, as main's feed types now do Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…ress hints (switch-gated) (stablyai#31553) * fix(sidebar): land flip-a's switch-on fixes on main, still dark (no flip) The fixes found while the sidebar ran from main's feed, without turning that on: ORCA_SIDEBAR_INPUTS_IS_MAIN stays false, and none of flip-a's stop-gaps come along. - Main stops counting a closed tab's terminal once it was asked to stop and no pane shows it; a failed or refused stop takes the request back, and a host out of contact stays unverifiable. - Main fetches a paired server's rows with the transport's normal bound, so a server slower than 5 s still lists. - A deferred unread raise flips the folder row it was decided for, and a read while the owner check is out cancels it. - Phone: no worktree.ps flash before the feed; a host's last installed feed stands in while a fresh read is out; it highlights its own last-opened workspace and a fold never scrolls; projects file under their project group with the desktop's indent; the collapse spinner shows only after 500 ms; "Reopen to update" keeps the rows on screen. - e2e helpers and journeys for the switch-on suite. * fix(sidebar): keep an owed relay stop marked, clear a thrown stop, and prune pending unread raises - A stop-and-wait that throws takes its stop request back, like one that fails. - A final stop the legacy relay still owes stays marked, since the relay retries it. - Pending unread raises are tracked only while one waits, so the map no longer grows. - The paired-server fallback poll test gives its third fetch a reply and asserts live. * feat(sidebar): main publishes cached snapshots and announces its own stores; detected, view and live-content domains (inert) * fix(sidebar): keep links confirmed only by a new git run, no rescan on metadata writes, no phone wait on detected checkouts * fix(sidebar): rescan the workspace rows when the Windows runtime default changes * feat(sidebar): one lifecycle queue per workspace in main, with in-progress hints (switch-gated) Create, delete, terminal.closeAll, terminal.sleep and worktree.sleep now run through one first-in, first-out queue per host-qualified workspace: strict order, no merging, delete cancels what has not started and waits for what has, an unconfirmed outcome pauses until the host's own answer. Hints publish in a new inProgress feed domain (no reader yet); the creations domain is its projection. A finished create or delete keeps its hint until a scan started after it applies, in the same revision. Adds the paired-server metadata serializer (saving domain, no caller yet). Replaces the creation registry. * fix(sidebar): a create never queues behind the previous occupant's delete * fix(sidebar): resume a paused workspace by re-observing, never replaying - A command issued while paused waits behind the pause; when the owner answers again main re-reads terminal liveness and the listing, then carries on. A forced delete runs past it. - Commands keep their arrival order across a workspace lookup; deletes by name key by the resolved id, so they wait for that workspace's running sleep. - A second delete with different options is refused as already in progress. - Close and delete read lost contact as sleep does. - A create always frees its own queue entry, even after a retry took over its id. - Drops the unused opId, and the paired-server write serializer and its feed section, which land with their first caller. Co-Authored-By: Claude <noreply@anthropic.com> * fix: restore main's final P2 test fixes a merge reverted --------- Co-authored-by: Claude <noreply@anthropic.com>
…31465) Normalize bounded Base64 input consistently across desktop, browser, and mobile pairing paths while preserving lossless legacy padding and trailing-bit spellings. Retain offer validation and authentication behavior. Adapted from stablyai#12027. Compatibility verified across 378 spellings with no previously accepted inputs lost; full PR workflow and aggregate gate passed for a84dfae. Co-authored-by: Henry Su <henrysu4707@gmail.com> Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
A reused file reader carries a note composer into another document, and a late submission closes the new composer. Give document state and callbacks a keyed lifetime.
…stablyai#31690) A focus read started before Enable can finish afterward and switch notifications back off. Fence reads against permission and preference mutations.
…blyai#31692) Saving an endpoint after the user leaves can pop a second screen. Retire navigation ownership while allowing the endpoint save and client refresh to finish.
* Use one model catalog per execution scope * Accept a missing session record in host scope * Merge duplicate launch-resolution import * Fix catalog typecheck errors * Align catalog tests with the one-list design * Type the integration fixture's send without a cast * Give the discovery test complete runtime dependencies * Fix model catalog completion and scoped configured display * Preserve model catalog authority across released clients * Fix mobile first-frame catalog fixture narrowing * Keep startup catalog persistence with one completion owner * fix: preserve chat selections across CLI model updates A newer CLI can omit a model that an existing chat still runs. Keep that chat's selected model and effort instead of replacing them from the available list. Discover one model list per host, account, launch command and folder, refreshing after two minutes. Keep current values and the running model's exact effort choices with its chat. Show only the known effort when Claude supplies no matching menu. Preserve older paired-client delivery and configured launch arguments. Make Codex use independent discovery and retain the established ACP and Pi paths. Tracked in STA-10947. * fix: keep the Pi options read within its file limit after main's growth Tracked in STA-10947. * fix: import the option snapshot's shared module once Tracked in STA-10947. * fix: keep Grok and Pi model menus and fast controls across restarts Restore the established ACP and Pi catalog route: newer listed models and qualified startup defaults stay visible, and genuine retained effort menus survive later reads. Keep exact-child Fast and Speed controls. Update the phone option contracts and the remaining stale expectations to the selection rules. Tracked in STA-10947. * fix: share each workspace's model list and keep live Grok and Pi menus New chats read their workspace's own model list through the one shared request for that workspace, so a second chat in the same folder starts on the model and effort the agent actually runs there, and a slow first answer can no longer overwrite a newer list. A running Grok, OpenCode or Pi chat keeps its live model menu when an older shared list arrives later. Update the tests that still expected the replaced behavior. Tracked in STA-10947. * fix: keep separate agent and chat model lists for Grok, OpenCode and Pi A brand-new Claude chat now offers the selected model's effort levels before the first message, as it did before. Grok, OpenCode, omp and Pi again keep the list read from the installed agent apart from the list a running chat reports, and each newer list replaces only its own source. So a chat shows the model the agent is actually running with that model's own effort menu, and an effort change can no longer act on an older model's menu. Saved model lists on WSL are readable again without starting the agent. Tracked in STA-10947. * fix: keep desktop catalog storage out of the phone's type graph and show Grok's late model answer The phone app's type check no longer pulls in desktop-only catalog storage. The model-list code no longer reaches the saved-catalog file through a type import. When a Grok model change times out and Grok later confirms the model, the chat shows the model Grok actually runs. Tracked in STA-10947. * Remove unrelated merge formatter and package manager churn * Clarify independent catalog ownership and refresh contracts * Declare picker demand callback as an effect dependency * Complete mobile catalog surface and catalog merge types * Complete catalog contract fixtures for current host types * fix: preserve catalog source contracts and account fallback * test: inject independent Codex discovery transport * test: remove accidental client fixture assertion * test: qualify the saved account fallback read * fix(chat): preserve catalog evidence and current child controls * Fix catalog CI types and mobile option readiness * Reconcile session controls after inventory changes * Fix catalog import and deferred test resolver types * Keep model capability observations separate from picker views * Use installed model choices for chat options * Fix model catalog test fixture contracts * Restore model option compatibility and stable settlement * Return a catalog reply in provisional launch fixture * Compose unreported live model defaults in the host * Preserve explicit replacement model on recovery send * fix(chat): use stable scoped catalogs on local desktop * test(chat): preserve generic catalog runner type * fix: scope catalogs to configured environment and keep saved reads passive * fix: satisfy catalog regression and controller CI checks
…along the address bar on desktop and mobile (stablyai#31597)
…ablyai#31686) * refactor(linear): group issue-context owners and use direct imports * test(linear): mock concrete current-context owner in WSL move suite
# Conflicts: # src/shared/protocol-version.ts # src/shared/runtime-capability-constants.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merges 206 upstream commits: stablyai/orca@8cc3aa4...4ed095d
Conflicts
src/shared/protocol-version.tssrc/shared/runtime-capability-constants.tsResolution (Claude)
upstream 동기화 충돌 해결 (sync/upstream-20261022-090002)
src/shared/runtime-capability-constants.ts— upstream은 capability 상수를 영역별 모듈(*-runtime-capabilities.ts)로 나누면서 이 파일을 삭제했고(refactor(protocol): each area owns its published runtime capability list stablyai/orca#30641), fork는 여기에TRIVIUM_WORKSPACE_RUNTIME_CAPABILITY를 추가했었음. upstream대로 파일을 삭제하고, Trivium 상수를 새 파일src/shared/trivium-runtime-capabilities.ts로 옮김(TRIVIUM_RUNTIME_CAPABILITIES목록도 함께 정의, upstream의 영역별 패턴과 동일).src/shared/protocol-version.ts— upstream은 import/RUNTIME_CAPABILITIES를 영역별 목록 spread 방식으로 바꿨고, fork는 기존 import 목록과 배열에 Trivium capability를 끼워 넣었었음. upstream 버전을 그대로 가져온 뒤 Trivium 줄 3개만 추가:TRIVIUM_RUNTIME_CAPABILITIESimport,export * from './trivium-runtime-capabilities'(Trivium 코드가 계속protocol-version에서 상수를 import하므로), 배열 끝의...TRIVIUM_RUNTIME_CAPABILITIES. 광고되는 capability 문자열(trivium.workspace.v1)은 이전과 같음.src/renderer/src/components/trivium/use-trivium-workspace-status.ts— 충돌은 아니었지만 typecheck 오류. upstream이runtimeEnvironmentIdWithLegacyFallbackUnlessTwin을runtimeEnvironmentIdOrLocalUnlessTwin으로 이름만 바꿈(refactor(renderer): name the owner adapters for what they do (resolveOwnerOrLocal) stablyai/orca#31626, 동작 동일). Trivium 훅의 호출을 새 이름으로 변경.검증:
pnpm install --frozen-lockfile,pnpm tc,pnpm run verify:rpc-params-catalog, fork 테스트 파일 85개(pnpm test, 763개 테스트) 모두 통과. 변경 파일에 대해 oxlint, oxfmt, max-lines ratchet도 통과.사람이 확인할 것: 없음. 머지는 진행 중 상태로
git add -A까지만 했고 커밋은 하지 않음(동기화 스크립트가 커밋).Checks run locally: pnpm tc, verify:rpc-params-catalog, fork test files.
🤖 Generated by ~/.orca-upstream-sync/sync.sh with Claude Code