Repository navigation
sync: upstream 5d32f372e7 into swaplabs/main - #50
Conversation
…rames survives other changes to that workspace (switch-on builds only) (stablyai#31920) * fix(L273-core): tabs this desktop created in a paired server's frame-drawn workspace survive the server's frames (switch-on) A tab the server's layout never lists (a client-hosted browser tab today; any kind recorded the same way) used to vanish on the server's next frame rebuild. This desktop now records such tabs by server and workspace, in its localStorage only (never a session field nor sent), and the server's frame builds keep them as drawn, inside the server's own groups: - A held tab drawn in a group the server lacks moves to the nearest server group; it shows there only if the user was on it, and the user's group follows it. The group tree stays the server's. - The record forgets an id once the server's frame lists it, on close, and on worktree removal, and prunes ids the restored session no longer has before the first frame after a reload. - A held tab's drag moves it here only (no session.tabs.move); it offers no new split, and a server tab sharing its group counts only the server's tabs for split no-ops. - Client-hosted staging records its tab (both ids) in layout-owner builds; restage takes the worktree from its caller instead of scanning the store. * refactor(L273-core): the desktop-created record is inert with the switch off; clearer names - With the switch off the record is never read, kept or written (a single gate in the record). - The frame-drawn check is isDesktopCreatedTabInFrameDrawnServerWorkspace in frame-drawn-desktop-created-tabs.ts: "held" stays the frame hold's word. - The held-tab placement drops its unread activeGroupId; the group follow derives it. * fix(L282): a client-hosted browser tab this desktop creates in a frame-drawn server workspace completes once the server's snapshot lists it, instead of undoing itself after 8 s (switch-on) * fix(L284): a server-hosted browser tab this desktop creates in a frame-drawn server workspace completes when the server's frames take it over, instead of reading as the user's close (switch-on) * chore(L282/L284): adopt only staged client-hosted handles, and name the frame build's handover note (review nits) * fix(L284): a staged tab the server's frames took over completes the create though its pane, unmounted, has no handle yet (layout-owner build only) * fix(browser): a switch-on host answers a paired client's browser create with the new page's id, not its workspace's, before main's frame draws it * fix(browser): the reply names the page id the create asked for first, then the new record's own page (L291) * fix(L284): the frame build scans dropped pages for a handover only when a staged create's page is drawn, so a large close stays one pass * fix(L285): a staged server-hosted browser tab stays through the server's frames until the server lists its page, then hands over to the server's own tab (layout-owner build only) * fix(L285): release a held browser tab by the server listing its page, staged or adopted, in one scan per frame (review)
…e set of default tabs when the same path exists on another host (stablyai#32080) * fix(worktree-create): a workspace created on a paired server seeds against that server, so a same-path twin on another host can't duplicate its default tabs (L277) * fix(worktree-create): name the create's host once, keeping the flow under its line cap; type-only test import
…yai#29104) * fix(runtime): release remote streams when the renderer reloads * ci: re-run code scanning (no source change)
…lyai#28569) * Offer Resolve with AI for paused merge, rebase, and cherry-pick A paused operation can need recovery after every conflict is staged, but the Resolve with AI action only appeared when unmerged paths remained. The operation banner now offers it whenever AI actions are visible and an operation is in progress. Starting an AI conflict-resolution agent is also blocked while a Git operation is running. The composer, the banner and card buttons, and the agent launch itself check the running-operation state, so a launch that began during agent detection cannot start while Git is still running. * Block paused-operation recovery only on Orca's own Git work The previous gate scanned the host's process table and refused "Resolve with AI" whenever it could not prove no Git process was running. That refused recovery on older paired hosts, on Windows whenever any Git process ran anywhere on the machine, and while a paged `git reflog` or `git grep` was open in a terminal. - The host tracks Orca's own mutating Git commands per worktree (including ones still queued) and reports them through `git.mutationInFlight`. Reads and fetch/push/ls-remote are not tracked. - Recovery is refused only when the host reports that work. Older hosts without the method keep today's behavior. - The gate applies only to launches from the paused-operation banner and conflict card, not every resolveConflicts launch, so the Checks panel's PR-conflict flow is unchanged. - The agent prompt now tells the agent to stop if index.lock exists or another git command is running, which covers Git started outside Orca on every host. With no unmerged files, the prompt asks the agent to find why Git paused instead of listing zero conflicts. * Preserve Git mutation state until terminated commands exit - Cancel stale recovery checks and agent launches when their dialog or scope closes - Guide rebase recovery agents to amend fixes after failed exec steps * Cover failed rebase exec recovery with real Git * Recognize global Git options in worktree mutation tracking
…cides (stablyai#30910) * fix(acp): OMP chats send images; an ACP agent's own startup answer decides Images reached an ACP agent only when it advertised them and Orca also listed it, and OMP was never listed. Trust the advertised capability alone, offer attachments to every ACP agent until its live report narrows them, and send an image's @path reference to an agent that takes none instead of refusing the message. Tracked in STA-11152. * fix(acp): preserve image file references when native content cannot fit
…tablyai#31593) * feat(resources): end local terminals with checked identity checks - Add a PTYs view to the Resource Manager that lists local terminals and can end one. Before stopping, the host confirms the terminal's incarnation, agent ownership, and attachments. This prevents ending a same-ID replacement that started after the list was read. - Bump the daemon protocol to v50 and gate the new fenced kill on it. Older daemons ignore the extra fields, so the client refuses End instead of sending an unchecked kill. - Admit diagnostic reads (PTY listing and memory snapshot) only while the Resources view is visible, so a hidden window does no background scanning. - Report incarnation and host on spawn and exit events so the renderer can match them to the terminal it showed. - Replace the popover's terminal-session list with the new inventory and move memory polling into the Resources view. * Show unrouted daemon PTYs in the Resource Manager * Simplify PTY controls and clarify safe termination * Format local build compatibility protocol lists * Add PTY resource management localization strings * Harden PTY resource manager test fixtures * Use typed IPC event in PTY kill test fixture * Refine local PTY resource view model typing * Refine local PTY resource view model typing * Include local host identity in PTY lifecycle event tests
…s twice (stablyai#31657) * Install shared skills for 11 more agents Add skill install provider entries for OpenClaude (own .openclaude folders, OPENCLAUDE_CONFIG_DIR), and OpenCode 2, MiMo Code, OMP, Prime Agent, Qoder CN, Codebuff, Freebuff, Ante, Jcode and DeepSeek Harness, which read the shared .agents/skills folder at both scopes. A coverage test now requires every Orca agent to have an entry or a recorded reason it has none. * Use the shared skills folder for Qoder Qoder's CLI loads .agents/skills at both scopes by default, so its own .qoder/skills placement made every skill appear twice. * Keep scanning Qoder's own skill folders Qoder still reads .qoder/skills, so discovery keeps both folders as discovery-only roots now that installs go to the canonical root.
* Stabilize remote host status bar layout during retries * Add connecting status translation for remote hosts
* Set minimum width for native chat table cells * Allow markdown table cells to wrap long content * Set minimum width for native chat table cells
… server workspace from the server's layout (switch-on builds only) (stablyai#32088) * wip(h5c-1): server workspace layout intents go to the server's main; positions per stream * wip(h5c-1): e2e without a cast * fix(h5c-1): a routed move never moves here; routed callers read the server's stream; a restarted main starts its positions over * fix(h5c-1): layout-apply-send back under its line limit * test(h5c-1): a routed answer reaches the handler with the server's stream key * Editor tabs main-first: the window sends each editor action to main and draws editor tabs only from main's frames (dark, layout-owner build only) With ORCA_LAYOUT_OWNER_IS_MAIN on, editor open (preview replace, open to the side), reopen at its position, close, close-all, promote, the resolved conflict placeholder, a restored-editor move and a disk rename are layout commands the window sends before it draws anything. Window content (open file records, drafts, buffers, the autosave gate) is written at once for an open, and rekeyed in the same store update as the frame for a move or rename. Main publishes each non-edit tab's view, keeps views in memory across applies and partition changes, stamps the tab's time, folds same-file duplicates, and republishes a windowless host's session.tabs after its own file open. The switch-off path is unchanged. * fix(terminal): a CLI or phone close through main keeps a pinned tab, as the window-era close did * feat(terminal): main applies phone closes, renames, pane titles, sleeps and agent-launch tabs itself, with or without a window (layout-owner build) * fix(terminal): a tab close through main also ends the tab's dormant SSH relay session; agent-launch tabs main writes keep the window's focus rule, prompt and outcome (layout-owner build) * feat(terminal): main starts a new tab's first terminal under the window's ids, and the pane attaches to it (layout-owner build) * test(terminal): tests that counted the window's create calls or publications see main's start of a new tab * fix(terminal): an account-switch restart binds only into a pane main's layout holds, with no window tree (layout-owner build, L197) * test(terminal): the new-tab start tests hold no type assertions; the start's ledger row is L252 * fix(terminal): a new tab's pane attaches to any terminal main binds it to, and a failed start keeps the tab's startup * test(terminal): a Claude account-switch restart's tests draw the pane main holds * test(d3a-2): the buffer view index follows a rename main answers * fix(terminal): a launch pane main takes back still tells the window why, which drops its hold and prompt; a rename through main answers the name it stored (layout-owner build) * fix(terminal): a new tab another owner's terminal won attaches to it, and a failed start's startup never runs twice * fix(terminal): main's pane close reads its stop result as a type only, so no runtime module loads before its base class * fix(terminal): a phone's close through main drops the closed rows from what phones list at once, not at the window's next publish (layout-owner build) * chore(terminal): mark the phone-close row drop interim (ledger L260) * test(worktree-create): a new local tab's startup counts as run when main starts it (layout-owner build) * test(worktree-create): record main's starts without a type assertion * fix(terminal): a tab closed before main declines its start gets no startup back (layout-owner build) * feat(h5d): hand desktop-only editor tabs to a layout-intents server before its frames draw the workspace A paired desktop dropped an editor tab it opened in a server workspace (with its open file and unsaved draft) on the server's next records frame once that workspace became frame-drawn, and editor opens made after gating hit the same loss. - openEditorTab is now on SERVER_ROUTED_LAYOUT_COMMANDS: the proposed tab id and group are kept, the file id becomes the server's own, and the record loses the desktop's runtimeEnvironmentId. - Before a workspace gates, server-layout-frames hands its desktop-only editor tabs to the server's main (sendToServerMain), recomputes them on each answer, and gates only when all answered ok or tab_exists. Any other refusal or a failed call keeps it on the mirror for that connection; the next one retries. No timer, no hold store. * fix(h5d): only an applied open counts as handed over; tab_exists keeps the tab on the mirror * fix(terminal): the window's new tab starts in main when its own window asks over IPC, which labels itself a client (layout-owner build) * feat(h5c-2): a paired desktop's writes to a server workspace its frames draw land in that server's main (layout-owner build) - Routes setTabProps, setChatPane and non-terminal renameTab to the server's main as layout.apply; closes, starts, sleeps and terminal names keep the RPCs that main now applies (session.tabs.close, terminal.close, terminal.split, session.tabs.createTerminal, terminal.rename). A window-style closeTabs is never routed: main takes it as already stopped and stops nothing. - A close the server refuses (a pinned tab) redraws the workspace from the server's frames, so the window's optimistic drop never outlives the answer; same for a refused pane close. - A frame-drawn workspace keeps none of the mirror's ledgers (L240): no tab-id mappings, close or focus intents; they stay only for servers without layout.intents.v1 until the Phase 2 cutoff. Its pane-geometry push is skipped (it goes as intents); pane renames still push. - e2e: paired-desktop-server-layout-writes covers close (layout, session.tabs, terminal stop), rename, split and new tab on a windowless switch-on serve. * fix(h5c-2): an undelivered close of a frame-drawn tab is held for replay; a launch-agent clear stays off the server route * refactor(h5c-2): the undelivered-close test moves to its own module, keeping the lifecycle file under its line cap * fix(h5d): a tab closed while its hand-over was in flight is closed on the server before the workspace gates * fix(h5d): an editor tab closed in a workspace a server's frames draw closes on that server by session.tabs.close; a tab kept on the mirror closes here only * fix(h5d): a handed-over tab closed here before the gate is closed on the server first; a server-opened tab's close in a frame-drawn workspace is covered * chore(h5d): a closed stream also closes the hand-over its hand-back started * refactor(h5d): move the frame stream's routing inputs out of server-layout-frames to keep it under the line cap Main's stablyai#31869 grew server-layout-frames past 300 lines once merged with H5d; the two helpers move unchanged.
…tays where you opened it (switch-on builds only) (stablyai#32003) * fix(L273-core): tabs this desktop created in a paired server's frame-drawn workspace survive the server's frames (switch-on) A tab the server's layout never lists (a client-hosted browser tab today; any kind recorded the same way) used to vanish on the server's next frame rebuild. This desktop now records such tabs by server and workspace, in its localStorage only (never a session field nor sent), and the server's frame builds keep them as drawn, inside the server's own groups: - A held tab drawn in a group the server lacks moves to the nearest server group; it shows there only if the user was on it, and the user's group follows it. The group tree stays the server's. - The record forgets an id once the server's frame lists it, on close, and on worktree removal, and prunes ids the restored session no longer has before the first frame after a reload. - A held tab's drag moves it here only (no session.tabs.move); it offers no new split, and a server tab sharing its group counts only the server's tabs for split no-ops. - Client-hosted staging records its tab (both ids) in layout-owner builds; restage takes the worktree from its caller instead of scanning the store. * refactor(L273-core): the desktop-created record is inert with the switch off; clearer names - With the switch off the record is never read, kept or written (a single gate in the record). - The frame-drawn check is isDesktopCreatedTabInFrameDrawnServerWorkspace in frame-drawn-desktop-created-tabs.ts: "held" stays the frame hold's word. - The held-tab placement drops its unread activeGroupId; the group follow derives it. * wip(L270): a host advertising agentSession.create.tab-placement.v1 opens a created chat's tab where the creating client asked, in its own group and tab ids * test(L270): a switch-off windowless host saves a chat opened with no client place at the end of the remembered active group, as before (test only) * wip(L270): a desktop's chat in a frame-drawn server workspace stays put: the frames hold its provisional tab until the server lists it, and the create names its group and the server tab before it (layout-owner build only) * test(L270): a switch-on serve saves a client's chat after the tab it named; the server-layout read moves to one e2e helper (test only) * refactor(L270): the create's tab place keeps its own name, and the layout rule's comment names the client's place first * refactor(L270): the create's tab fields live in their own schema file and the windowless save passes its chat through, so no file passes its line limit; the placement test types its mocks without assertions * refactor(L270): the chat's place names server tabs through H5c's route, which also skips tabs the server's frames do not hold * fix(L270): a chat opened while a reconnected server stream awaits its snapshot still names its group; the layout-owner e2e list runs the placement spec * fix(L270): only a create in a frame-drawn server workspace awaits its tab place, so every other create keeps its timing (the eager History joiner after an early terminal close opens again) * fix(native-chat): read a frame-drawn chat tab's place without a tick, so an eager History joiner's create is still sent * test(native-chat): type the create's params so an async placement cannot compile; name the probe that fills the cache * test: type the placement mock's original through a type import
…ment tail (stablyai#31934) Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…ai#31935) Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…ker (stablyai#31936) * perf(chat): remove repeated scans and array copies from the skill picker * perf(chat): keep picker grouping within the module line limit --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…1950) * perf(ai-vault): bound scope cache write batch enumeration * test(ai-vault): type scope cache stamp fixture --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…nce on the shared agent writer (stablyai#32092) * feat(agent-write): opt-in launch cancel, approval wait and binding fence on the shared writer executeAgentLaunch takes an optional signal; sendTerminalAgentPrompt (and sendAgentTurn) take optional onPermission: 'wait' and expectedBinding. No existing caller sets them, so every current path is unchanged. * fix(agent-write): keep the default writer path main's; fence cancel and real submit boundary Only opted-in writes take the submit layer, so default timing and the leaf re-read are unchanged. Cancellation is rechecked before a structured-refusal terminal fallback, and an opted-in launch learns Enter from an onSubmitWritten callback at the write itself, OMP's combined frame included. * fix(agent-launch): a cancel after a failed startup spawn recovers the created workspace
…lyai#31893) * Revert "Preview HTML files by default" (stablyai#31575) Restore source/diff opening for general HTML file-opening routes for the release. This reverts commit 524fa00, retaining the published files.readDocPreview capability for mixed-version clients and removing an unnecessary restored test assertion to satisfy the current lint gate. Existing explicit previews and terminal HTML links retain their previous behavior; the underlying local-file preview exposure remains tracked in STA-11670. * Preserve file ownership and symlink guards during HTML preview rollback Keep the existing owner checks after file-drop uploads and tab-entry stats so an ownership change cannot reopen a file through a stale host. Preserve local preview source checks so symlinks outside the workspace keep user-file access. The race and symlink tests reproduce failures on the mechanical revert before these guards are retained. Cover HTML source opening for folder workspaces, Explorer, and Source Control without restoring preview-by-default. * fix: split tab entry browser requests to satisfy lint limit
…ds a terminal asks main's layout (stablyai#32002) * refactor(runtime): every reader of a terminal's pane goes through paneOfPty (L80) With the layout-owner switch on, main's layout answers which tab and pane hold a terminal, and nothing reads or writes the PTY record's tabId/paneKey copies or their graph stamp. Switch-off reads the copies exactly as before. * refactor(runtime): read a terminal's pane through the runtime's paneOfPty method * fix(runtime): main's pane index answers paneOfPty before any window graph A headless host never installs a window graph, so the query asked a publisher that did not exist and every terminal read as paneless. The index is now made on first query; it publishes only once a graph is installed, as before. * test(runtime): pin that a query with no window publishes nothing * test(runtime): a terminal summary reads its pane once (L80) * feat(runtime): pin bind-first at registration, and one own-handle rule with or without a window (switch-on only) * test(runtime): census and tests that every create path binds its pane before it registers (L80) * test(runtime): a bind-first fixture that stands in for the spawn commit (L80) * test(runtime): fixtures bind a terminal's pane before registering it, as the spawn commit does (L80) Also, with the switch on: skip the bind-first check for a paired server's workspace, release a closed tab's terminal ownership once main has dropped the pane, and publish a pane's agent status from the terminal main's layout binds it to. * feat(runtime): an exit after main's close still finds the pane it retires (switch-on only, L80 Q2) Main's layout write records every pane it drops whose terminal no pane holds any more, in a new optional session field closedTerminalPanesByPaneKey (same TTL and cap as closed-tab records). Exit cleanup reads main's layout first, then that record by terminal id and incarnation, and removes the entry it used. Older builds ignore the field. * refactor(layout): one dropped-leaf step every live layout edit runs (switch-on only, L80 Q2) The step receives each dropped pane (host, worktree, tab, leaf, terminal, incarnation), computed across the whole layout so a moved pane is not dropped, and runs only for a commit that declares a live edit. Today it records the closed-pane entries; a drop is recorded once per terminal. * fix(runtime): a phone or headless tab close still stops and releases its live terminals after main's layout drops the panes (switch-on only, L80) The headless close reads each row's live terminal before main's close drops the panes it is found by, and a renderer-retired tab releases the owned terminal main left in no pane, as the explicit close already did. Close tests now commit through main's layout and bind panes first. * fix(runtime): a local workspace's terminal tab left in a paired server's partition is main's pane (switch-on only, L257) Older builds could save a local worktree's tab rows into a runtime partition (stablyai#18202); its terminals are local, so main's index takes those tabs for workspaces the router homes locally. A tab or terminal the local partition also holds is indexed once, from there; SSH and server-owned workspaces are skipped as before. * test(layout): the legacy-close comparison leaves out the closed-pane record only layout-owner builds write (L80) * test(pty): a pane whose failed start withdrew its tab resolves to terminal_not_found in layout-owner builds (L80 #7) Main's layout no longer holds the pane, so there is no pane to answer for; window-owned builds keep the disconnected stub from the exited terminal's record. * test(runtime): orca-runtime suites bind panes first, as the real store and spawn commit do (L80) Fake PTY spawns commit the binding into the pane the runtime wrote, stores list their partitions, and SSH sessions sit in their ssh:<conn> partition; terminals registered directly bind first. * fix(runtime): main's layout names a pane's terminal only when the workspace and stamped incarnation match (switch-on only, L80) A record made before its adoption registers (a host restart) or a local id reused after one matched a saved binding by id alone, so a phone or paired desktop could be handed a second handle for the pane before the terminal's own handle arrived and stay on it. * test(runtime): a restored pane is only unnamed in the adoption gap, and no local record rescues a paired server's tab (L80) * test(runtime): recovery, close and routing suites set up terminals as production does (L80) A recoverable pane is one the host inventory dropped with no exit, live terminals register rather than appear only in the window graph, and SSH stores route as the real Store does. * test(runtime): suspects 3, 4 and 5 bind, register and apply as production does; PR-1's main-layout closes in the part-10 and part-11 close tests (L80) * test(runtime): a fixture store applies main's layout commands as a profile does; PR-1's own closes in the part-05, part-08 and UUID-leaf close tests (L80) * test(runtime): the profile-store stand-in moves to its own fixture, keeping bind-first under the line limit (L80) * test(runtime): orphan adoption reads the pane from main's layout with the switch on, not the record copy (L80) * test(runtime): a phone's pane close and a pinned tab close are main's layout closes with the switch on (stablyai#31571), in the no-widening and surface-close tables (L80) * test(runtime): a paired phone close commits in main's layout with no relay ack when the switch is on (stablyai#31571) (L80) * fix(terminal): a parked pane resumes its agent once main has closed the other pane that held the session (layout-owner build, L80 P1) * test(runtime): the parked-pane suite's restart holder is a saved pane with no registered terminal, under the line limit (L80 P1) * fix(terminal): a spawn commit that binds a reused local terminal id unbinds it from the saved pane of the older process (layout-owner build, L80 P2) * test(runtime): the legacy OSC row test uses the UUID leaf main's layout holds and registers its live terminal with the switch on (L80 E5) * test(runtime): the pty-exit echo close of a dead headless tab is expected to fail with the switch on until W4's close PR (L255) * fix(terminal): a terminal record that arrives, changes incarnation or moves workspace after main composed its panes re-composes them, so a restored pane lists once (layout-owner build, L80) * fix(terminal): a pane bound to a terminal a paired server runs keeps that binding and host, and is never indexed as a local spilled tab (layout-owner build, L80/L257) * fix(terminal): a pane main owns no longer proves its terminal runs; the host's inventory and the terminal's record decide, so a dropped shell reads disconnected (layout-owner build, L80) * fix(terminal): an SSH pane whose host says its shell ended keeps its leaf on a negative or failed code, as the window does, so recovery starts a shell in it; a stale handle learns the connected replacement (layout-owner build, L80) * fix(agent-launch): a terminal main's layout still binds counts as running through a host listing gap, so a launch never starts a second shell in its pane (layout-owner build, L80) * fix(terminal): a disconnected terminal main still binds stays a stop target, never reads stopped unproven, and keeps an exit wait waiting until a certified exit (layout-owner build, L80) * test(terminal): the wait unit fixture answers the exit-settled dependency * test(terminal): realistic fixtures for three phone-row tests: unique leaf ids per pane, and a window that draws main's own panes (layout-owner build, L80) * fix(terminal): a stale handle learns a pane's replacement only when it was resolved for that pane, its own terminal ended, and the replacement is connected there now (layout-owner build, L80) * test(session-tabs): switch-on variant for a window's chat placement (main's layout order rules); headless phone tab order is L283 * test(terminal-close): switch-on close reds name their ledger rows (L255, L256, L259a, L259b); a same-id respawn's commit stamps its incarnation; the crossing terminal binds first * test(terminal): the crossing registration is refused by main's workspace fence (L256 switch-on variant); the placement test's tabs are main's saved layout * refactor(terminal): the stale-handle guard reads an issue-time pane stamp on the handle record, not a second map; moved-pane, restart and single-reader tests (layout-owner build, L80) * test(session-tabs): switch-on variants: the desktop's tab name is main's rename, and a mobile-created tab's live shell is the one its commit registers (layout-owner build, L80) * fix(terminal): a re-issued handle keeps its issue-time pane while no pane holds its terminal (review) * fix(terminal): stop targets and exit waits use main's holds check (workspace and incarnation), and any exit event settles a later wait (review) * test(session-tabs): name the switch-on placement and split-group reds L288 and L289 * fix(terminal): an exit drops a closed pane's record in the same write that retires the pane, and an unbind folds into the binding write (layout-owner build, L80) The exit used to remove main's closed-pane entry in a session write of its own, before the retirement write. It now reads the entry, and the retirement write for that pane drops it, re-checked by terminal id and incarnation. An exit that keeps the surface (relay contact lost, intentional stop) keeps the entry until a confirmed exit or its TTL. A reused terminal id's unbind is now a pure computation that applyPtyBinding applies through its existing writes. Two split-pane tests still expected a -1 exit to close the pane; they now follow the ruling that any negative code keeps it. Static-gate fixes: test fixtures and tests use typed member access instead of Reflect, and no longer use type assertions. * test(runtime): the shared fixture store announces its session writes and the L283 store lists its partitions, as a profile Store does; L289's test passes on that store (layout-owner build, L80) The fixture store bindPaneFirst fills now tells write listeners of every session write, direct or by a layout command, so main's post-commit merge runs as it does on a real Store. The notice is not enumerable, so a test's own `{ ...store }` copy, which keeps its writes elsewhere, does not inherit a subscription it would never fire. With it, the targeted split-group create (L289) passes at this head, so its it.fails mark goes. L283's test runs on a store that names its partitions and stays it.fails("L283"). * fix(terminal): recovery checks the caller's handle after the cheap guards again, so the switch-off build re-issues no handle on a refused recovery (layout-owner build, L80) The stale-handle switch point had moved the handle lookup ahead of the pane, terminal and workspace checks. That lookup can re-issue a handle, so a switch-off recovery refused on those checks gained a side effect base never had. The lookup runs after them again, and the switch-on replacement check lives in one method. * test(runtime): the layout-owner start fixture's spawn commits the pane binding before main registers the terminal, as the host does (L80 switch-on variant) The window-tab start test came in with the main merge after bind-first; its fake spawn returned a terminal no pane was bound to, so registration refused it. The fixture's spawn now binds the pane first, as every other bind-first fixture does. * fix(terminal): a new process under a reused terminal id starts with no exit code, so an exit wait on it cannot settle on the old process's code (layout-owner build, L80) The record of a reused id kept the earlier process's lastExitCode, and with the switch on an exit wait settles on any recorded code once the terminal reads disconnected. A new incarnation now clears the code and cause. Also drops the L283 test's partition-list option: the fixture store already lists its partitions. * test(runtime): switch-on variants for four tests the full local run never loaded (L80) - A restored pane's host lists the terminal it started; a pane main owns vouches for none. - The other pane holding a slept session is in main's layout, so an unverifiable holder blocks the resume as it does through a window. - Main sleeps a workspace whose panes it lists itself, even with a renderer attached (stablyai#31571). - A graph sync no longer reconnects a disconnected terminal of main's pane; the inventory decides. * test(runtime): the phone Markdown route tests use stablyai#31667's realistic store, and the untitled cases wait on L268 with the switch on The window's tab uses main's id (the file path), and the store lists its partitions and saves each open editor's unified tab and group, as the window does (the fixture from stablyai#31667). With the switch on, the three untitled cases fail on L268, main's row merge dropping the untitled flag, which stablyai#31667 fixes; they are it.fails until A1b's history includes it. * refactor(runtime): two merged files back under the line limit, by moving A1b's additions to their helpers (L80) After the main merge, the holder-pane check and a closed tab's live-terminal lookup pushed two files past the line limit. The holder check is now paneHoldsSession (main-terminal-surfaces.ts) behind one method, and the closed tab's lookup is one parent-class method. Behaviour unchanged. * test(runtime): the headless chat tests open the chat on the publish path, as every product path does; L283 stays it.fails for the main-owned reorder openChat recorded the chat only in the runtime's snapshot, so main's layout never held it and the phone's reorder was refused as an unknown tab. On the publish path main's layout holds it; the reorder is accepted but does not survive the row rebuild after a close (L283, reopened). * test(runtime): bind-first census follows the one spawn-commit step (stablyai#31596); the unbound-registration test keeps its spawn from the suites' stand-in commit After the main merge both start lanes register in spawn-commit-assembly.ts, after savePtySpawnPaneBinding (or, adopted, on the live owner's pane); the census lists that file. A phone create now writes its pane before the spawn, so the suites' stand-in spawn commit bound it and the test of a path that registers unbound passed for the wrong reason; that test's controller now opts out of the stand-in commit. * test(runtime): convert four switch-on tests the disguised full runs hid Real switch-on runs showed four files whose fakes registered terminals unbound or confirmed a stop without its exit. Bind first, stamp the source as main does, and deliver the exit a confirmed stop delivers. Switch-off unchanged. * test: type the two converted fakes instead of casting them The PTY controller fake now takes the controller's optional pane fields, and the switch-on store is a typed RuntimeStore, so neither needs an assertion.
…model picker on every open (stablyai#32014)
…try can never re-run its command (stablyai#32278) * fix(terminal): a timed-out CLI split names its pending pane and can be retried without splitting again Fixes stablyai#32169 * fix(terminal): a blank retry id or a closed pane never re-runs a split's command * refactor(terminal): a split retry only waits for its pane and never asks the window again * fix(terminal): a split missing from the graph is reported pending, never closed
…32274) Every place Orca reads an SSH key or identity file now goes through one reader that refuses directories, FIFOs and devices (including through symlinks) with a nonblocking open, and caps the read at 1 MB. An auth failure now names any identity file that was skipped and why. Fixes stablyai#31965. Reported by @Huge, who also proposed the first fix in stablyai#31966. Co-authored-by: Martin Vlach <mr.huge@seznam.cz>
…unchanged (stablyai#32251) * fix(mobile): keep a terminal tab's chat identity while its handle is unchanged A status frame that omits agentType or providerSession flipped chat back to the terminal for tabs with no launchAgent (stablyai#31977), and left a launched tab with no session to read (stablyai#32134). Mobile now remembers the last resolved agent and session per tab and handle, so a missing field is treated as missing evidence. * fix(mobile): keep remembered chat read-only and drop it on relaunch A chat resolved only from this phone's memory may sit on a bare shell, so its writes stay locked until live status confirms the agent. A live agent frame without a session no longer inherits the previous conversation, and the latest remembered agent outranks an older launch hint. * fix(mobile): gate native-chat image sends on a confirmed live agent An image send from a chat only this phone remembers could paste into a bare shell; it now shares the confirmed-agent gate with text sends. * fix(mobile): type remembered chat identity structurally Importing MobileSessionTab into the eligibility module pulled React Native globals into the e2e program through nested-agent-idle-session-owner.spec.ts, retyping setTimeout as number there.
…n reveal or recovery (stablyai#32281) Settled reveal and every other atlas recovery forced the current xterm buffer onto the screen even while a TUI held DEC 2026 synchronized output. Codex erases rows inside each held redraw, so a reveal mid-redraw showed a blank pane for a frame (stablyai#32198). Viewport presents now have one mode: they keep the last coherent canvas and let xterm present when the TUI closes the frame, bounded by xterm's 1s synchronized-output safety timeout. Structural replay keeps its retained frame up until an open synchronized frame closes, bounded by its existing hold cap. Co-authored-by: YEONGUK LEE <contact@abruption.dev>
* Use incremental tsrs for local typechecks * Update pinned Bun to 1.4.4 * fix(deps): bump dompurify to 3.4.17 and docs next to 16.4.1 for security advisories dompurify 3.4.17 patches GHSA-cg2g-xh64-v4q7, GHSA-mrv2-3pqr-j5jr and GHSA-c6gr-qvv4-5v3f in the root and mobile lockfiles. next 16.4.1 patches GHSA-2464-fhr4-w9xw, GHSA-673q-mjm6-r5xr and GHSA-p3m5-59mp-jv5r in the docs site. * Prepare mobile bundle for smoke benchmarks * Give native compiler benchmarks a larger ARM runner --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
) * test: remove redundant cases and repair behavioral assertions * docs: reconcile audit records with current main * fix(deps): apply security updates from stablyai#32101 Apply the six-file DOMPurify 3.4.17 and Next.js 16.4.1 patch from PR stablyai#32101 to resolve the dependency security gate for this test audit. Original patch by Brennan Benson. * test: satisfy changed-line quality gates Add required braces and replace filter indexing with find. Rerun both CI quality gates and the 27 affected relay tests; refresh audit hashes and record the corrections. --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* test: replace redundant checks with observed behavior * docs: reconcile completed audit batches and upstream review --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…s itself back on after a cooldown (stablyai#32397) * fix(watcher): back off on system event stalls and let the crash fuse recover The watcher child's canary treated any missed delivery as a native deadlock and exited. When the OS event source itself is stalled (an overloaded macOS file-event daemon), each restart recreated every stream, adding load, and after three restarts in two minutes the crash fuse disabled native watching until the app restarted. - Before exiting on a starved canary, probe the same dir with an independent fs.watch. If fs.watch delivers, it is a child deadlock: restart as before. If both stay silent (and fs.watch was proven to work on this host), it is a system stall: keep the child and its streams and back off the canary (30 s doubling to 5 min) until delivery resumes. - The crash fuse now half-opens after an exponential cooldown (5 min doubling to 60 min) so native watching is retried; callers keep their polling fallback while it is open. A trial crash reopens it immediately; a trial that stays healthy for 10 min resets the escalation. - Arm the canary while a native subscribe is pending: always on macOS (its subscribe does not crawl), and after a 10 min grace on crawling backends, so a subscribe stuck behind a deadlock no longer disables the check. Co-Authored-By: Claude <noreply@anthropic.com> * fix(watcher): bound stalls, keep callers fresh, and let the fuse reopen reach callers Review follow-ups for the watcher stall/backoff change: - fs.watch proof is no longer a cached startup probe. Any delivery in the process (including the canary's own fs.watch) proves it; a failed shallow probe is re-measured after 30 s instead of failing shallow subscribes forever. - The canary keeps one long-lived fs.watch on its dir and checks whether it delivered since the probe started, instead of opening one per probe (which makes macOS rebuild the shared event stream). - Any native callback in the child during the probe window counts as proof the child is not deadlocked. A stall that lasts 15 min forces a restart, still rate-limited by the crash fuse. - The child reports delivery health (healthy/stalled) to the host. A stall that outlasts one backoff period makes the host send an overflow (rescan) to every subscription on that cadence, and once more on recovery. - Roots that fail while the crash fuse is open are no longer cached as unwatchable until shutdown, and the git-common narrow watch's polling fallback retries native events every 5 min. Log wording no longer claims every caller polls. - Linux/Windows keep the canary disarmed during a pending crawl (huge or network trees); macOS stays armed. - Fuse escalation resets only after the trial child's canary has confirmed delivery for 10 min, not merely after it survived 10 min. Co-Authored-By: Claude <noreply@anthropic.com> * fix(watcher): back off stall rescans and end stalls when the canary disarms - Host rescans during a stall now back off with the canary (30 s doubling to 5 min) instead of every 30 s for the whole stall, and stop once no subscription is live. - A canary that disarms after a stall (no live roots, or a crawl on Linux/Windows) reports 'unknown', which ends the host's stall mode with one rescan; the stall is re-detected once the canary re-arms. The crash fuse ignores 'unknown'. - Health reports are ignored after supervisor disposal (they were already limited to the current child). Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…non-UTF-8 files ask before converting (stablyai#32413) * fix(editor): keep every view of a shared record on its text; ask before a lossy UTF-8 save - A clean record's draft may reach a view's live model. Views of a record share one draft, so a commit that reads clean (a rich-mode undo that differs from disk only in trailing whitespace) is the record's text; refusing it left the source view showing undone text, and typing there resurrected it. - A rich view applies another view's text (or a reload) without an undo step, so its Cmd+Z no longer reverts the source view's typing or re-applies text undone there. - An outside change to a path (Source Control discard, watcher) now reaches views of the same record in other workspaces' panels, not only the named workspace's, so their cached copy of the discarded text is dropped and reloaded. - Explicit saves send the lossless-UTF-8 precondition. A non-UTF-8 file asks for confirmation (Save as UTF-8, destructive) before replacing characters that aren't valid UTF-8; declining leaves the bytes alone. The autosave-paused notice no longer promises a lossless conversion. - Dev builds resolve worker-thread entries from the main output folder when the caller was bundled into a shared chunk (packaged builds resolve from resources and are unchanged). Co-Authored-By: Claude <noreply@anthropic.com> * fix(editor): say why save-and-close refused a non-UTF-8 file --------- Co-authored-by: Claude <noreply@anthropic.com>
…ead of the whole folders (stablyai#32410) * perf(watch): stop watching all of userData and .git for a handful of files On macOS a directory fs.watch is a recursive FSEvents stream. Two watches used that to notice a few files: - The Active Server preference watched the whole userData directory for orca-environments.json, so Orca's own writes there (agent-hooks status, terminal history, browser caches; ~10 events/s measured) were routed back to it. - The shallow git metadata watch ran fs.watch on each repo's whole common .git dir (and .git/logs), receiving objects/, refs/, lfs/ and every linked worktree's churn to see six files. Both now watch the individual files (kqueue on macOS, no fseventsd) through a new rebinding file watch: events and a 30s sweep re-stat the path, rebind when a rename-replace or deletion moved it, and pick up files created later. Events are confirmed by a stat (size/mtime/ctime/inode) because macOS raises an attribute event for every mmap read of the index and packed-refs; the watch is one-shot with a backing-off window so a file every git process maps costs about one rebind a second instead of a wakeup per read. The registry path is unchanged, so CLI/serve readers and older peers are unaffected. Linux and Windows keep the non-recursive directory watch for shallow subscriptions. Co-Authored-By: Claude <noreply@anthropic.com> * fix(watch): don't hang unsubscribing an errored watcher; find late metadata files sooner - Node closes an errored fs.watch handle without emitting 'close', and a later close() is a no-op, so awaiting 'close' could hang a watcher-child unsubscribe and every subscribe queued behind it. Errored watchers are now dropped without a close, and closeFileSystemWatcher also settles on 'error' or after 1s. Applies to both the macOS file watch and the Linux/Windows directory watch. - Missing metadata files (packed-refs from pack-refs/gc) are re-statted every 2s instead of waiting for the 30s rebind sweep. - On macOS the delivery probe watches a file, matching the kqueue path the shallow subscriptions now use. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
* test: audit delayed replies and database boundaries * docs: reconcile the fourth test audit batch --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…i#32442) * ci: run relay integration on the Blacksmith x86 pool * ci: use the validated Blacksmith pool for Linux packaging * test: allow shared Linux download caches across validated runner pools
* test: audit CLI output and deferred delivery boundaries * docs: reconcile fifth test audit with main * test: build process-host for Cloud-only relay validation --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Upstream range a45bd06..5d32f37, 138 commits. 3 conflicted files, each one hunk; the fork's lines are kept unchanged on upstream's new code. - Update sources (fork #10 #14), src/preload/api/updater-api.ts: upstream's shortened listBuilds comment (upstream stablyai#32257) plus the fork's `source` line. - Host resume (fork #30), src/shared/agent-hook-listener.ts: the fork's statusRowToComplete on the previous row, beside upstream's new agentProcess read (upstream stablyai#31780). - Host resume (fork #30, past sync resolution 1a247d1), restored-pane-materialize.ts: upstream's resumeClaim (upstream stablyai#31718) beside the fork's HOST_PANE_AGENT_REPORTED guard on the fresh-launch branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
When session hydration has completed with an explicit empty tabsByWorktree row and the group has no client-hosted rows, showEmptyState is true at the same time as the newly merged WorkspaceEmptyState condition above. This mounts the absolute TabGroupEmptyState over the richer workspace chooser, producing duplicate terminal actions and potentially intercepting its central controls. Reuse one empty-state implementation or make the predicates mutually exclusive rather than rendering both.
AGENTS.md reference: AGENTS.md:L24-L26
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Follow-ups from this sync: #51 (the |
ELI5
Upstream Orca moved forward 138 commits since yesterday's sync (#47). This PR merges them into
swaplabs/main. Three files conflicted with fork changes, one small hunk each; each keeps the fork's lines unchanged on top of upstream's new code. It supersedes #49 (headmain), which could not merge.What Changed
Upstream range
a45bd06eac..5d32f372e7, 138 commits (compare). One signed merge commit,5aafdd009f(Merge upstream main (5d32f372e7) into swaplabs/main), onsync/upstream-20261023, cut at the integration head412ee3374a.Conflicts, by fork PR (
orca-fork-ops conflicts):src/preload/api/updater-api.tslistBuildsdoc commentsource; the signature andlistSourcesare unchangedsrc/shared/agent-hook-listener.tsagentProcessright after the previous row, so the identity guard can tell a nestedclaude -pfrom its parentstatusRowToComplete(...)on the previous row, then upstream'sreadHookAgentProcessline1a247d1c5csrc/main/runtime/session-lifecycle/projection/restored-pane-materialize.tsresumeClaimshared by pane resume and the new targeted resumeresumeClaimline, then the fork'shostResume !== HOST_PANE_AGENT_REPORTEDguard on the fresh-launch branchNo fork lines were moved or dropped. For every one of the 28 files both sides changed, the fork's added and removed lines against upstream are the same before and after the merge. The only exception is the comment in
updater-api.tsabove.How the fork's hook-store resume (#30) sits next to upstream stablyai#31718: it runs only when
resolvePaneOwnedResumeStartupreturnsundefined, which means the pane has no record. Upstream returns{}, notundefined, when another resumer holds the claim, so the fork's resume never runs while upstream holds a claim. The fork's resume does not take upstream's new in-memory claim itself. The only caller of upstream's targeted resume is the external-conversation reopen, which is off by default behindexperimentalConnectors. This is noted on #41.Some notable upstream changes in the range:
Why
Daily upstream sync.
Linked Issue
N/A: daily upstream sync. #41 notes the targeted-resume claim above.
Visual Proof
N/A: no UI change of the fork's. The conflicts are in a comment and in the main-process and shared code.
Testing
All checks ran on the merge result in the sync worktree, before the commit:
pnpm tc(all four projects, incremental tsrs) passes. A deliberate type error in a scratch file made it fail, which confirms the gate is live.orca-runtime.test.ts, which runs the fork'sworktree-ps-agent-row-dismissal.spec.ts. 117 of 118 files pass. The one failure is upstream'srepository-project-operations-part-02.spec.ts: this host's signing wrapper refuses its fixture commitinit. With fixture signing off (commit.gpgsign=false),orca-runtime.test.tspasses: 1,497 passed, 3 skipped.src/main/runtime/session-lifecycle,src/shared/agent-hook*,src/shared/agent-status*,src/preload,src/main/agent-hooks,src/main/updaterandsrc/renderer/src/components/settings: 559 files, 5,042 passed, 0 failed.check-changed-code-quality.mjswithORCA_SYNC_PR_DIFF_BASE=5d32f372e7: 0 findings across 331 fork files.cloud/):oxlintpasses with both configs, andoxfmt --checkreports every file formatted. The pre-commit hook then ran normally, and the tree before the commit (59707d1a11) equalsHEAD^{tree}after it.verify:localization-runtime-catalog,-catalog,-catalogs,-extraction,-coverage,verify:rpc-params-catalogandcheck:max-lines-ratchetall pass.5aafdd009fisGfrom the sync keyBF1DD75F888E96080C2453E2D9E158C5D6874235.AI Disclosure
Prepared by the "Orca upstream sync" automation (Claude Code, Claude Opus 5.5).
Agent skill upstream boundary
Notes
orca-fork-ops statusforswaplabs/main(412ee3374a) against upstream5d32f372e7, before this merge:LOCAL: post a Slack note when a fork build publishes or fails);LOCAL:prefix and no upstream PR.No upstream release tags were missing from the fork.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.