mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 09:07:40 +00:00
efcf8f0888d7db170126b428facacb3cb4e0f682
12240
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7f21cc91b8 |
fix(codex): Codex copy from a remote pane reaches the viewer clipboard (#31453)
* fix(codex): copy from a paired client's or WSL Codex reaches the viewer's clipboard * test: type the Claude launch fixture |
||
|
|
920a033928 |
fix(native-chat): typing fast after focus moves keeps keys in order (#31203)
* fix(native-chat): typing fast after focus moves keeps keys in order Typing a burst right after focus left the composer (Move Tab to Split, a paired chat, a click on the transcript) could scramble the first letters: "Reply exactly" came out as "Rply exactly...e". The first key was routed into the composer programmatically: focus moved at once, but the caret was set a frame later from a position computed before the key. The next keys landed natively, then that late caret write pulled the caret back. Quote "Add to chat" and picker completion had the same deferred caret write. Each programmatic write now puts the text and caret into the editor in one synchronous step, read from the editor's live value, then focuses it. No write happens while an IME composition owns the field. * fix(native-chat): retain saved chips after synchronous composer edits |
||
|
|
9577c531c7 |
fix(ssh): a managed host's 'no desktop is sharing its browser' error names the browser routing setting too (#31444)
* fix(ssh): the host's 'no desktop is sharing its browser' error names the browser routing setting too The desktop withholds the browser bridge from a managed SSH host whose pages browse from the desktop's own network, but the host's CLI was told only to turn on the per-host CLI control switch, even when it already was on (#31421). * fix(ssh): name the per-host routing opt-out too, and guard the labels against UI copy drift |
||
|
|
c7f722bbdf |
fix(pairing): offer only addresses the runtime listener answers (#31447)
The runtime WebSocket listener widens to 0.0.0.0, which never accepts IPv6, yet the Connection address picker and the default pairing address offered global IPv6 interfaces, minting links nothing answers. Filter the interface list by the family of the host the listener binds when serving the network. Reported-by: @fongyuan-2011 |
||
|
|
5fc6cb46b0 |
fix(plugins): a development plugin reloads an edit saved while its watcher was starting (#31394)
* fix(plugins): a development plugin reloads an edit saved while its watcher was starting Orca scans a development plugin folder, then starts watching it. The watcher goes live about a second later and reports only changes made after that, so a save in between was never picked up and the plugin kept running the old files. Once a development root's subscription goes live, schedule one catch-up refresh through the existing debounce. * test(plugins): count a replaced dev root's catch-up refresh regardless of timing |
||
|
|
92732bb0f9 |
fix(native-chat): bring back Working-only-while-held (#30134) without shutting down Claude chat workers (#30577)
* fix(native-chat): a Claude chat worker keeps running after Claude acknowledges its task
Claude's adapter dropped an echoed send from the sends it holds before
reporting the send's answer to the host, and emitting the echo's turn
can publish at once. That publish read the still-pending send as held
by nobody, so it read as "couldn't confirm": orchestration worker-start
then closed the new Claude worker, and every other answer wait and
client saw a one-frame doubt.
A frame's settled sends now stay held until their answers are reported,
when the host's handover window takes them over; a Stop's rejected
sends are released and reported one at a time, as Pi and ACP already do.
Tracked in STA-11160.
* test(native-chat): assert the settled send's dispatch state through the send result
* Reapply "fix(native-chat): Claude, Pi and ACP chats are Working only while the agent holds the send (#30134)" (#30572)
This reverts commit
|
||
|
|
cfdc347bd8 |
Layout-owner build: main shows live terminals that lost their tab, at the same moments the window did (dark) (#31430)
* feat(layout): main places a swept workspace's live terminals; the window sweep only reads (switch-on builds only) The window's activation sweep (first visit, an empty or sleeping activation, a phone wake) now asks main, through its existing terminal.list call, to place that workspace's unshown live terminals before listing them. With the switch on the sweep writes nothing to the window store and counts a pane main names as shown, so it no longer seeds a fresh shell beside a terminal main just placed. Main's "still binding" check also covers a registration in flight. * fix(layout): a sweep's placement pass holds back only its own workspace's first surface (switch-on builds only) * test(layout): switch-on activation-gate and census-call tests read main's listing, not window binds |
||
|
|
2bf77e605b |
fix(ssh): draw every host list through one bounded, searchable host picker, so a 13k-host ssh config can't crash any of them (#31426)
* fix(ssh): draw every host list through one bounded, searchable host picker A generated ~/.ssh/config can import 13k+ SSH hosts, and several surfaces drew a row per host: Settings > General workspace directory 'Apply to', Notifications > Machines, the sidebar and Agents Hosts submenu, the composer run-target picker, the Add Project host selector and remote step, the skill-install machine pickers, Settings > Servers SSH host picker, repository 'Add to another host' and the automation host field and filter. They now share components/host-picker: boundHostList (pinned hosts plus a ranked, capped match list), HostCombobox for single choice (a Command in a Popover that mounts nothing until opened, replacing Selects that mount every item), and useBoundedHostRows with HostListSearchField and HostListOverflowNote for checklists. A host-list-render ratchet stops new JSX from mapping straight over a host collection. * fix(ssh): host lists keep room for search results, menu search is keyboard-reachable - boundHostList gives pinned hosts and other matches separate limits, and narrows pinned hosts by the query, so a large selection never hides what the user types. - In a dropdown, the host search is a menu item: arrow keys reach it, focus moves into the input, and arrows leave it again. - The Add Project host picker hides its setup actions while searching, so Enter picks the matching host. * fix(ssh): re-picking the current host in a host picker is not a change The Selects these pickers replaced suppressed same-value changes; the skill install form clears its workspace and target on a machine change. * chore(ci): let the shared ratchet runner pick up the host-list render ratchet main now discovers every check-*-ratchet.mjs through run-ratchets.mjs, so the separate workflow step would run it twice. * fix(ssh): host lists rank pinned matches and keep input order; label and scroll fixes - boundHostList ranks pinned and other hosts by the same match order before cutting each to its limit, then keeps input order among equal ranks, so a muted or checked exact match is always reachable and clicking a row never moves it. - The Servers SSH host picker is named by its form label again. - The sidebar Hosts submenu scrolls its host rows instead of running off screen. - The composer's overflow note sits outside the listbox. |
||
|
|
505d6719c0 |
Refuse a workspace parent that would close a loop, even when the worktree cache is cold (#29340)
* fix(worktree): refuse a parent loop even when the worktree cache is cold The parent cycle walk read each workspace's current instance from the resolved-worktree cache, which the reparent itself invalidates, so it only knew the two endpoints and accepted A->B->C->A. Read saved worktree metadata instead. * fix(worktree): fall back to locator metadata and tolerate loops saved earlier * fix(worktree): skip another host's locator metadata in the parent loop walk * fix(worktree): read loop-check identities from a read-only, ownership-scoped view The per-host metadata accessor migrates a hostless legacy row into the queried host, so the loop check could adopt another host's worktree and refuse a valid parent. Read one read-only host snapshot instead, counting a row only when its project is registered on that host (the resolved list's ownership rule). * fix: import host-qualified worktree meta helpers once * test: match main's host-targeted lineage write contract |
||
|
|
7dfcfbf735 | fix(codex): resume native chats by conversation ID (#31041) | ||
|
|
786a40ef1f |
Custom tab shortcuts work right after a keyboard worktree switch (#30941)
* fix(shortcuts): app shortcuts run before the terminal, whatever order they mounted in App shortcut owners and terminal panes both listened on window capture, so React's effect order decided who saw a chord first. After a worktree switch the new pane registered before the tab shortcut listener and turned a user-bound Cmd+Right into Ctrl+E; clicking a tab flipped the order back. A keyboard router installed before React renders now runs every app owner, in registration order, ahead of any pane listener; a key no owner claims still reaches the terminal's own handling. Held tab chords are claimed on every repeat but switch once. Tracked in STA-11205 (same bug as STA-4602). * test(shortcuts): type the router mock with key event names * fix(shortcuts): install the keyboard router in the web client before it renders * test(shortcuts): floating panel specs read the routed key listeners from the window stub |
||
|
|
fc8439e05f |
fix(notifications): a pane with hooks gets its finished banner from its hook turn only (#30620)
* fix(notifications): tie a completion banner to the hook turn it announces, not a 10 s window A title-only working->idle edge (Claude's /btw side question fires no hook) raised a generic "Task complete" banner because the pane's hook row was older than 10 s. On a hook-reporting pane the hook row now decides: a row not yet announced supplies the banner content however old it is; an already-announced row means no turn ended, so no banner. Hookless agents keep the generic title fallback. * fix(notifications): a pane with hooks gets its completion banner from its hook turn only The title lanes (mounted pane and parked watcher) announced a finished task on every working->idle title edge, even on panes whose agent reports turns through hooks. A title spin that ends no hook turn (Claude's /btw side question, Codex's /compact) raised an extra banner that could only be filled with the workspace name and tab title. On a pane the hook lane is tracking, with a settled Done row from the same agent, the title lane now defers before it claims the turn, so the hook lane's banner (with the last message) is the only one. A BEL in the same burst waits on the hook lane's pending Done. Hookless agents, restored rows, hooks switched off and the post-restart re-arm keep the title banner. This replaces the previous commit's announcement memory in the dispatcher. * fix(notifications): hook-owned turn ends settle pane bookkeeping and announce re-entered Stops - A title idle edge that defers to the hook lane now settles the pane's turn, so quitting the agent after a /btw no longer raises a generic "Task complete" banner. - A BEL held while the hook lane's Done is pending is superseded by that Done at fire time instead of being held until the next turn start. - A Stop re-entered after a blocking Stop hook carries its new final message in its identity, so the hook lane announces it instead of deduping it against the first Stop. - Title lanes ask the hook lane whether it can announce the turn end (live, agent evidence, not re-arming) instead of copying its conditions, and fall back when the agent's own hooks are switched off. - The hook lane's non-quiet Done forwards its own snapshot, so its banner content no longer depends on the stored row's age. - The whole-flow test now drives the real pane wiring, the real hook lane and the real dispatcher. * fix(notifications): the host marks each real turn end, so a repeated status can't raise a second banner The renderer told a Stop re-entered after a blocking Stop hook from a restatement by its final message. The message also changes when the listener's cache resets (a session reset, then an idle reminder), so a restated finished turn raised a second, empty "finished" banner. The execution host now classifies each provider's own turn-end event by name and stamps an optional `turnEndedAt` on the status row. Idle reminders, session events, replays, child re-emits and result retries restate it; a turn-end event on an already finished row is a new end only when the agent says it re-entered (`stop_hook_active`). Local hosts and SSH hosts (orcad) mint it in `attachStatusTiming`; the WSL relay mints it at admission and sends it on its envelope. The field is persisted, forwarded over IPC and the paired-server session projection, and the renderer's done identity is `turnEndedAt ?? stateStartedAt` (after the existing `turnCompletedAt` precedence). `stateStartedAt` and every other clock are unchanged. Paired servers that predate the stamp advertise no `agent-status.turn-ended-at.v1` capability; their panes keep the terminal-title completion fallback exactly as before. * test(notifications): type the old-server pane setup in the completion flow test * fix(notifications): a blocking Stop's final banner reaches the user; /compact and quitting an idle agent stay quiet Real-agent QA on the hook-only completion design found three gaps: - A Stop re-entered after a blocking Stop hook could be dropped twice over: inside 1 s of the first banner by the replay guard (a new host turn-end stamp is now a real end), and by main's 5 s per-workspace desktop cooldown. A banner with the same notification id and changed text now replaces the one already shown instead of being dropped; every other cooldown rule is unchanged. - On a mounted pane, a hook-owned idle edge (Codex /compact) no longer claimed a completion, so the burst's BEL went out as a Bell banner. The deferred idle edge now absorbs that BEL like a claimed completion did. - Quitting an idle agent after a title spin raised a generic process-exit banner. On a pane whose hook lane owns turn ends and whose last turn is done, an exit ends no turn; a crash mid-turn still gets its banner. * test(runtime): the projection reference keys on the host turn-end stamp too * test(relay): restore the Copilot retry test while CI isolates a runner error * test: key the hot-path projection reference on the turn-end stamp; relay retry restates the end * fix(notifications): host turn identity per agent session; keep later blocking-Stop banners past main's cooldown - The execution host (local main and the WSL relay, one shared reducer) now tracks which turn an agent session is in: Codex by its own turn id, Command Code by its transcript prompt position, every other agent by its prompt-opening hook. A turn end mints a new end when its turn differs from the last ended one, so a Codex turn woken by agent mail and a text-only Command Code turn get their banner; duplicates, replays, tool reopens and result retries restate it. The record is saved with the status row (validated on restore) and never published. - Main's desktop and mobile notification cooldowns are back to main's code exactly. - A later end in the same done segment (a Stop re-entered after a blocking Stop hook) that lands inside main's cooldown is held, latest only, and sent once after that cooldown through main's unchanged gates; the renderer learns main's answer to anchor it. - One pane check gates every changed pane behavior: the pane's bound PTY runs on a host that stamps turn ends and its agent's continuation signal is verified (Claude, Codex). Every other pane, and an unbound PTY, keeps main's behavior. - A hook-owned idle edge absorbs its burst's BEL for main's full 1.5 s; exits and replacements ask the owner check with the exiting agent; a title spin on a hook-owned pane opens no turn. * test(notifications): run the whole-pane harness through main's shared desktop cooldown, not main's service module * fix(notifications): decide hook ownership from how the host identified each turn end The execution host now labels every classified turn end with one optional disposition beside its end stamp, by the same rule for every agent: identified (an unused prompt opening, a new native turn id, or a verified re-entered Stop) mints a new end; a same-turn repeat is a proven duplicate only when the agent supplies both a turn id and a verified continuation flag; anything else is ambiguous and never mints. Replays and retries restate the decided answer, and local and relay hosts carry an end only within a done segment. The renderer replaces the Claude/Codex pane gate with one evidence decision: identified ends are hook-owned, ambiguous ends keep the title lane's role, and rows without a disposition (older hosts, unclassified agents) keep the previous behavior exactly. A title working edge on a hook-owned pane keeps its turn bookkeeping and only the hook receipt; an owned idle edge settles after the existing max wait, or announces through the title path if the end turned out ambiguous. Idle and working title edges resolve the same owner (the title's agent, else the foreground one). The held later banner for identified continuations is unchanged. * test(notifications): give the whole-pane harness's host event its local connection id * fix(notifications): a settled cancellation ends the open turn, so its late Stop folds as on main A cancel Orca inferred from Esc or Ctrl+C writes a done row with no turn-end event. The host reducer now completes the current turn key on that settled cancellation, so the same turn's late Stop is a repeat (ambiguous, no new stamp) instead of a second identified end that requested a second banner. * fix(notifications): a settled cancellation ends the open turn on every lane, so a relay's late cancel end restates none The desktop restates a WSL relay's end as decided. The relay never learns a cancel the desktop inferred for a non-Claude agent, so Grok's own late stop_cancelled reached the desktop as an identified end and gave a second banner. One predicate now decides both lanes: after a settled cancellation that carried no end, a restated end keeps the shown one, and a child event carrying the cancellation also ends the open turn. The relay's own cancel row is the main agent's, never a child's. * fix(notifications): a relayed done segment shown without its own end keeps showing none The restatement fold tested the previous row's cancel verdict, so the row it left behind (a late plain stop) no longer matched and the relay's next restatement brought its end back. Fold on the row's turn-end fact instead, which the folded row satisfies again. |
||
|
|
ae9bc8d120 |
A slept native-chat workspace reads asleep and hides (#28457)
* fix(sidebar): a slept chat workspace reads asleep and hides * fix(sidebar): a wake during Sleep's teardown keeps the workspace awake * test(sidebar): give the activation-race mock store the chat tab map Sleep reads * feat(native-chat): the host marks the chats an explicit Sleep stopped The fact "the user's Sleep put this chat to rest" now lives on the host that runs the chat, on its session record, instead of in the window. - New runtime capability agent-session.workspace-sleep-mark.v1 and an optional markAsleep on agentSession.sleepWorkspace (strict params, so only hosts advertising the capability are sent it). - The Sleep close decides each chat's receipt inside the same per-session step that stopped it; with markAsleep it writes sleptAt in that step's record write, so a chat is marked exactly when it is in the receipt. - The next send the host hands over clears sleptAt, whichever client sent it; the idle sweep and plain stops never mark. - sleptAt rides the status summary (optional field), its equality, and the retained summary of a closed chat, so it survives restart through the persisted tab index. * refactor(sidebar): read a slept chat from its host's mark, not a window record The window no longer keeps its own saved record of which chats Sleep stopped (explicitlySleptChatsByWorkspace). That record had to mirror every host event that should end it (sends, replaced conversations, renames, profile and WSL moves, removal, wakes during teardown), and each missed mirror was a bug. The host now owns the fact (previous commit). - Delete the record, its schema, store slice, workspace-session field registrations, moves, renames, purges, migrations and hydration, and the renderer clearing hooks. Those files are back to main. - Opening a workspace no longer wakes its slept chats (product ruling): drop the chat Sleep-attempt tokens and their set-active-worktree hook. A slept chat wakes only when a send reaches its host, from any client. - The status bridge mirrors each chat tab's published sleptAt into a store map keyed by the chat's pane key, dropped when its projection unmounts and never saved; a chat whose host runs it again is not slept. - The asleep derivation, hide filter, palette, board and context menu read that map. Explicit Sleep sends markAsleep only to the local host and to hosts last probed with the new capability; a project move never does. * fix(native-chat): preserve host-owned Sleep across fallback and legacy tabs * fix(native-chat): clear Sleep when a new send is admitted * chore: restore incoming documentation after merge hook formatting * fix(native-chat): let admitted sends supersede pending Sleep receipts * fix(sidebar): wait for host Sleep state before showing restored chats * fix(sidebar): retain host Sleep answers when chat projections remount * fix(native-chat): clear Sleep when queued drafts become sends * test(sidebar): cover unknown and known chat Sleep state on the board * test(vault): expect both host status capability checks * test(native-chat): consolidate Sleep behavior coverage * fix(native-chat): commit Sleep wake with send admission * test: pin visibility while chat host is unidentified * test: consolidate workspace sleep regression flows * test: keep status feed regression within renderer boundary |
||
|
|
c9d34bba1a | Open new Markdown changes in the rich editor (#31399) | ||
|
|
546304fa11 |
Discard pauses autosave of the file in every workspace; read-only Markdown/CSV tabs can't overwrite unsaved edits (phase 1b-3) (#31413)
* Editor: git Discard pauses that file's autosave in every workspace; read-only markdown/CSV never pushes over edits (phase 1b-3) - A Source Control discard holds autosave (and waits for in-flight saves) for every tab of that file in every workspace, keyed by its model save key, until the discard finishes or fails; the quiesce now returns the release, called in a finally. Watcher notices don't release it. A 120 s backstop only shows the changed-on-disk banner on dirty tabs and never reopens autosave. This closes the race where the compare fallback on older hosts reads disk just before git restores it. - A read-only tab renders raw Monaco source whatever its type, so it counts as a code view: a clean read-only .md/.csv tab can no longer push disk text over a writable tab's unsaved edits. Only removes or delays writes. One autosave timer per model moves to 1b-4. Co-Authored-By: Claude <noreply@anthropic.com> * Editor discard hold review: release on a failed quiesce, cover unstaged diffs, re-show an overdue banner (phase 1b-3) - A quiesce that throws (e.g. an unresolved CSV cell) releases its hold and rejects, so the discard reports the error instead of hanging with autosave off; a bulk discard frees the holds the other paths took. - Holds key on the physical file (host + path), so an unstaged diff tab of the file in another workspace is paused and drained too; save-queue keys are unchanged. - Past the backstop, a dirty held tab whose banner was dismissed gets it back, so the pause is never silent. Taking a hold no longer re-syncs; the save-time check already refuses. Co-Authored-By: Claude <noreply@anthropic.com> * Editor discard hold: key the physical file with the 1b-1 path rules, not the client's URI rules (phase 1b-3) The hold key went through the Monaco model URI, which normalizes paths by the client platform, so on a Windows client `/repo/a\b.ts` and `/repo/a/b.ts` on a POSIX host shared a key. It now uses the owner key plus `editorBufferOwnerPathKey` (new, in editor-buffer-identity), which applies the 1b-1 rules: POSIX backslashes kept, separators folded only for proven-Windows paths, UNC server case, and WSL aliases on the local Windows client. Same on the tab and the discard-path side. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
b2370f1c6e |
fix(terminal): hidden tabs reconnect to sessions that survived a restart (#31254)
* fix(terminal): hidden tabs reconnect to sessions that survived a restart pty:hasPty answered from the provider's attached-this-run set, so right after a restart every surviving session read as exited and #29459's hidden-agent hold kept live hidden tabs disconnected until shown. On an attached-set miss, ask the host through the provider's existing liveness probe; providers without one (the in-process provider, its own sole owner) keep their answer. * test: remove liveness fixture type assertion |
||
|
|
0a9980b141 |
fix(terminal): a saved terminal folder on a dead network drive no longer freezes the Orca window (#31381)
* fix(terminal): a folder on a dead network mount fails the terminal start instead of hanging it Bound the local folder checks a terminal start runs (folder-workspace admission and the worktree-folder cwd fallback) with a 5s deadline, and make the fallback check async so it can no longer block the main process. A timeout refuses the start with the existing folder_workspace_path_unavailable code. * refactor(terminal): build the folder deadline on the existing working-directory validation helper Drop src/main/local-folder-stat-deadline.ts. statLocalFolderWithDeadline now lives in working-directory-validation.ts and reuses its abandon-on-signal waiter and UNC route lane, so a dead SMB share caps how many folder checks can hold fs threads. * fix(terminal): keep only the non-blocking saved-folder check; drop the folder-check time limit Per owner decision, a hung folder waits as before. Folder status and working-directory validation return to main; the saved-folder fallback check stays async so main never blocks. |
||
|
|
01562f8bcc |
fix(ssh): window the SSH Hosts list and search past the Remote Hosts cap, so a 13k-host ssh config no longer crashes the renderer (#31405)
* fix(ssh): the Remote Hosts menu lists a capped set of idle SSH hosts * fix(ssh): the SSH Hosts settings list renders hosts a page at a time * fix(ssh): window the SSH Hosts settings list and keep project hosts in the capped menu Settings > SSH Hosts now windows its rows against the Settings scroller with the shared VirtualizedList instead of paging 100 at a time, so every host stays reachable by scrolling or the filter while only the rows near the viewport mount. The Remote Hosts menu cap rises to 25 idle hosts, fills it with hosts that own projects first, and the overflow row says to search the rest in Settings. * fix(ssh): search the Remote Hosts menu's capped idle hosts in place Web clients reach SSH hosts through this menu but have no SSH Hosts settings pane, so a link to Settings stranded every host past the cap. The menu now shows a search box whenever idle hosts are hidden; it narrows them by name, and the overflow line says how many are left. Also gives the two existing status-bar store mocks the repos field the menu now reads. * fix(ssh): typing in the Remote Hosts menu goes to its host search The search box is not a menu item, so arrow keys can't reach it; letters typed while the menu has focus now land in the search instead of Radix typeahead. * fix(ssh): the Remote Hosts search keeps focus and drives its results by keyboard The host rows are not menu items, so the menu's arrow keys never reach them. The search box is now a combobox over the connectable results: it keeps focus, arrow keys move the active result (announced through aria-activedescendant), and Enter connects it through the same path as the row's Connect button. * fix(ssh): Remote Hosts search only reaches hosts that match, and keeps the active one in view Every not-connected row now has to match the search, so Enter can't connect a failed host the user didn't type. Arrow keys scroll the active result into view. Also drops restyles on the search Input and fixes the singular overflow default. * refactor(ssh): find hidden Remote Hosts in a searchable picker, not a search box inside the menu A search box inside the dropdown kept fighting the menu over focus, Tab, Enter repeat and IME input. The overflow row is now a menu item that opens a host picker (Command in a Popover, as the style guide prescribes for single choice with search). It ranks exact names first so any host is reachable by typing its name, mounts at most 50 results, and says how many more match. The menu keeps config order when nothing is capped. * fix(ssh): closing the host search from the keyboard returns focus to the status button The picker is anchored, not triggered, so Radix had nowhere to restore focus and it fell to the page. A keyboard close now focuses the Remote Hosts button; a click elsewhere keeps focus where the user clicked. Also drops the padding restyle on PopoverContent. * fix(ssh): the host search marks the keyboard-selected host with the palette highlight --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
e2196403c3 |
fix(terminal): realign mobile snapshot residual metadata after query stripping (#31348)
Align plain mobile/legacy residual metadata after snapshot-boundary query stripping; preserve genuinely transformed span metadata. Fixes #9682. Based on the report and suggested correction by @bbingz. Co-authored-by: bbingz <zzb@gxsmjx.com> |
||
|
|
d8508da889 | fix(ui): clarify continuing conversations between chat and terminal agents (#31402) | ||
|
|
87fe8424eb |
fix(runtime): a server with no window answers its first terminal create, not 'handle stale' (switch-on builds only) (#31391)
* fix(runtime): a server's new terminal keeps answering under its own handle after main's pane adopts it (switch-on builds only) * fix(runtime): keep handle branching on the leaf path; only callers with no leaf fallback read the adopted own handle (switch-on builds only) * fix(runtime): with no window, a terminal's own handle keeps naming it after main's pane adopts it, as before main listed panes (switch-on builds only) |
||
|
|
8bec7323ef |
fix(mobile): preserve Claude chat after renaming its pane (#31358)
fix(mobile): retain Claude chat when its pane is renamed Distinguish a manual title override from a later shell OSC title, keeping transcript identity and live hook selection while preserving exit retirement. test: cover restored mobile chat after manual shell-like rename Co-authored-by: Wooseong Kim <innocarpe@gmail.com> |
||
|
|
b0d873d86a |
test(claude): isolate real CLI fixtures and cover chat option requests (#31201)
* test(claude): real-claude suite passes on any signed-in machine and current model aliases Model ids come from the CLI's own model list instead of dated ids, tests that need a SessionStart hook or an effort bring their own project settings, and the supervised-stop test launches and reads its transcript under one Claude home, as acquisition does. * test(claude): one real-claude flow for the model, effort and permission pickers and the context meter Drives the live CLI through the production send path (model list, set model, apply effort, read settings), checks the turn's context report, and relaunches into a new permission mode the way Orca changes it. CI only meets these requests through stand-ins. * test(claude): accept the ran options a send now reports Since #30561 a Claude send resolves with the options it ran under; the stop test only needs the send admitted, and the options flow checks the ran options are exactly the per-message pick. * test(claude): an alias pick's dispatch report may omit the model the CLI resolved |
||
|
|
d1ed73ba9b |
fix(orcad): refuse new terminals while an update stops the server (R32) (#29834)
* fix(orcad): refuse new terminals between an update's last census and the stop The last census before an SSH or WSL update stops the server now closes the server's terminal admission first. Every spawn lease (acquireWorktreeTerminalSpawn, which both spawn paths hold across the provider spawn) passes the fence, the census waits for leases taken before the close, and an update that leaves the incumbent serving reopens it. A reattach to a session the daemon still runs is still admitted. * fix(orcad): fence-exempt reattaches spawn attach-only; reopen admission when no stop was sent * fix(orcad): stop nothing once the recount's admission fence may have lapsed |
||
|
|
b3e6705337 |
fix(orcad-migration): a sidebar view setting never blocks moving an SSH host to its server (#31374)
* fix(orcad-migration): a sidebar view preference never blocks moving an SSH host to its server * fix(orcad-migration): keep repo filters on the desktop, keep later acknowledgements; e2e repro |
||
|
|
ce71535bbf |
Layout groundwork (switched off): main starts every split and new pane, the window only attaches (#31258)
* feat(terminal): the window's store shows main's pane start status (layout-owner build) A status-only frame changes one store entry and no pane tree, so the pane's view stays mounted. * refactor(terminal): keep the frame sync within its line budget * feat(terminal): every pane main starts shows 'starting' from before it is written (layout-owner build) * feat(terminal): a pane main is starting waits and attaches; a failed start shows why (layout-owner build) Fixes owner-visible row 15: a failed agent or setup start no longer opens a plain shell. * test(terminal): iterate store listeners directly * fix(terminal): an exit that reports no code keeps its pane, as lost contact (layout-owner build) * test(terminal): a pane main started attaches to main's terminal at its own grid * test(terminal): detach census tests assert main's move and frame with the switch on * test(terminal): exited-pane tests assert main's close with the switch on These 8 have failed switch-on since #31130 moved the exit close to main. * feat(terminal): main writes and starts every split of a pane it holds; the window attaches (layout-owner build) The window's split, a startup split with its command, and a CLI split all go through main's terminal.split; main's frame mounts the pane, with or without a window intent. Deletes the window's split wait and withdraw, the split appear intents, and the queued-split skip and cancel. The e2e helper uses the real split. A startup split leaves focus on its source pane. * fix(terminal): the attach-only wait leaves sleeping, resuming and held panes to their restore path * fix(terminal): a split pane attaches to the terminal main bound it to; a taken pane id is refused Also restores the proof that a split closed while it starts gets one answer and leaves no pane or start status behind. * fix(terminal): a window split main cannot start still shows its pane, failed, with why * fix(terminal): check a taken pane id first, keep the start's own error, clear a gone pane's status Three test stores gain the pane-start field the window's store always has. * test(terminal): the admission stub answers whether its pane is still written; a closed pane's failed start clears * refactor(terminal): the rebound workspaces' bindings show through one call in the bindings module * test(terminal): a pane main wrote and the user closed mid-start drops its failed status * fix(terminal): a split main starts is listed to phones and session.tabs without a window's report * fix(terminal): a reconnect shows its pane starting and why it failed; a lost owner race shows no failure; a split no one asked to see keeps the phones' tab * fix(terminal): a kept create another owner won shows no failure; the window attaches to the winner |
||
|
|
046f29b9e5 |
fix(runtime): a phone's Markdown save keeps the route it started on (#31376)
forSave captures the workspace's route (and its pairing pin) once; every read, stat, status.get and write of that save uses it, so a workspace that moves between local and a paired server mid-save never sends the write to the other host unpinned. |
||
|
|
cd007d4106 |
fix(session-history): scoped history no longer fails on a very large transcript corpus (#30905)
Scoped Session History built its candidate list by spreading each discovery's files into push(), which throws RangeError once one root holds more files than V8 accepts as call arguments. Append item by item instead, and do the same for the bucket-layout candidates (unbounded on unlimited scans) and the search index's unchecked-paths hand-off. Tracked in STA-11092. |
||
|
|
a763c1d8b7 |
style: format pty-connection-split-spawn.spec.ts (main fails oxfmt --check after #30442) (#31378)
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125 |
||
|
|
f7c07409d6 |
docs(orca-cli): teach agents cross-project parents and re-parenting (#29900)
* docs(orca-cli): teach agents cross-project parents and re-parenting * chore(cli): regenerate bundled orca-cli guide * docs(orca-cli): scope the parent host rule and paired-server selectors |
||
|
|
8a2ec06a22 |
Start an agent's terminal at its first real size instead of after a 12-frame wait (#30442)
* fix(terminal): start an agent's terminal at its first real size instead of after a 12-frame wait The startup grid settle waited for the grid to change and then hold for 2 frames; a grid that was right from the start always paid the 12-frame maximum (~90 ms at 120 Hz, ~200 ms at 60 Hz, seconds when frames are throttled, since it also cleared the 250 ms fallback timer). Its only case, the setup split, was retired in #29819. Command panes now connect like plain panes: on the first frame with a real measurement, or the 250 ms fallback timer. * fix(terminal): bound startup by completed fit and deadline * test(terminal): brace startup contract control flow * fix(terminal): release pending startup when window hides |
||
|
|
b99552cabc |
fix(runtime): a phone reads and saves Markdown tabs on a headless server (#31101)
* fix(runtime): a phone reads and saves Markdown tabs on a headless server With no window to relay through, markdown.readTab and markdown.saveTab answered renderer_unavailable. Main now reads the tab's file (or its saved draft) from disk and saves with a version compare plus the disk write precondition, in the shapes the window's editor relay returns. With a window attached the relay is unchanged. * Headless Markdown save: keep the draft's disk baseline, honour read-only tabs, re-find the record to clear * Use the disk baseline only for a draft * ci: run the phone Markdown spec's orcad case in the orca serve mode-switch job * Headless Markdown: answer from disk only; a window's saved draft stays its view state Clearing a saved draft wrote workspace layout from main, which no layout command carries. Main now never shows or edits a draft; the desktop's restore check reports a disk change. * Phone Markdown: one path through main and the disk, window or not A paired server's workspace is read and saved on that server with files.read / files.writeIfUnchanged, so it answers from its own disk and keeps the precondition. The window relay (ui:mobileMarkdownRequest and the renderer bridge) is removed. * e2e: a phone saves a Markdown tab the desktop holds unsaved edits in The phone reads the disk with the tab marked dirty; its save lands on disk, and the desktop keeps its draft and shows the changed-on-disk banner. * Group the Markdown disk host's imports * Drop an unneeded store cast from the dirty-readers test * Phone Markdown: refuse a save on a server too old to check the disk; cover the untitled tab A server before #30605 has no files.writeIfUnchanged, so the save is refused with the existing update-required message rather than written unchecked. * Phone Markdown: refuse a file an SSH host holds outside the workspace; pin the untitled answer * Phone Markdown: untitled tabs stay read-only; older servers save like the desktop editor; a closed-source preview still reads - The desktop lists an untitled document's tab with isUntitled (optional, additive); main answers it read-only with unsupported_untitled and refuses its save before touching the disk. - A server without the files.write.disk-precondition capability gets the desktop editor's guarded save: compare from here, then a plain files.write, or a conflict. Every call of a save is pinned to the pairing it probed. - A preview whose source tab was closed has no saved record; it reads its own file, read-only. * e2e: read the untitled tab's answer without a cast * Phone Markdown: pin a save's pairing before its first read A save captures the server's pairing before it reads, and every call of the save, the read included, is pinned to it, so a re-pair between the read and the write is refused. * Test: with no window, a phone saves an existing untitled.md and never creates a deleted one --------- Co-authored-by: E2E Test <e2e@test.local> |
||
|
|
dda855e837 |
test(layout): pin the layout-owner save format for downgrade, and keep main's touched-records save with an exhaustive guard (dark, layout-owner build only) (#31341)
* test(layout): downgrade probe for the layout-owner session format, and the touched-records patch's guard Downgrade probe (ledger L8): a checked-in golden of the session main writes in a layout-owner build (layout commands through the Store plus a window save), matched byte for byte; a switch-off Store hands it on unchanged and a switch-off window restores its tabs, panes, groups, editor file and browser tab. Regenerate with ORCA_UPDATE_LAYOUT_OWNER_GOLDEN=1. L104: for each writer of the touched-records patch (layout.apply, the cross-partition move, the window save), the patch equals a full save on every partition the load leaves alone, and differs only on records the load normalizes otherwise. Two concrete legacy cases are pinned, and a switch-off window restores each differently after a full save, so the patch stays. * test(layout): type the guard's partition ids and locate the golden without import.meta * test(layout): pin every legacy fixture's patch difference, assert restored panes and group selection, keep the golden deterministic |
||
|
|
fece03b5ac |
fix(orcad): voice dictation on headless orca serve hosts (#31217)
* fix(orcad): voice dictation on headless orca serve hosts
Mobile dictation against an orca serve host answered speech_unavailable
since serve moved to orcad: orcad never installed the speech service
factories, and the model manager and download imported Electron.
The model download now takes a host request opener (Chromium net on the
desktop, Node https on orcad, with redirects mapped to the same event),
the STT worker ships in the orcad slot, and orca serve hands orcad the
installed app's on-device recognizer. A host without a recognizer (an
SSH or WSL managed slot) lists only cloud models and sends a phone that
picked an on-device model to the setup sheet. Mobile names the fix when
an older server still has no speech at all.
The parity test now requires both roots to install speech.
Reported-by: delorean2020 (@delorean2020)
* fix(orcad): retry Node download drops, stage the recognizer on Windows, review fixes
- Node https errors (ECONNRESET, ETIMEDOUT, ...) are retryable, so an
interrupted orcad model download resumes (Codex P2).
- On Windows orca serve hands orcad a per-version copy of the recognizer
in the profile, so a loaded addon never locks the app's files against
an update (Opus).
- Drop a recognizer candidate path that could never match; one SAFETY
helper per test file instead of repeated casts; comments.
* fix(orcad): cloud speech is ready only with a key this host can decrypt; prune superseded recognizer copies
Codex round 2: a desktop-sealed OpenAI key exists on disk but orcad's
secret store cannot open it, so the catalog reported cloud models ready
and dictation failed at the end. orcad's model manager now proves the
key decrypts. The desktop keeps its existence check (no keychain prompt).
* fix(orcad): never prune staged recognizers before the profile lock; copy linked packages as real files
Codex round 3. ref-oss check: versioned copies are cleaned only by the
process that owns them, so the pre-lock prune is dropped rather than
patched.
* fix(orcad): remove a partial recognizer copy when staging fails (Codex round 4)
* fix(speech): never hand out another store's ciphertext as the OpenAI key (Codex round 5)
* refactor(orcad): no recognizer copy on Windows; Windows orcad keeps cloud dictation only
Design check after review round 5: every remaining finding came from the
Windows copy that dodges addon file locks. Dropping it shrinks the
surface to a path lookup; Linux and macOS serve keep on-device models.
* fix(speech): name the fix when an old server refuses a mic tap; always set the recognizer env; retry ENOTFOUND (final Opus review)
* fix(managed-server): probe install completeness relative to the slot dir
CI: shipping stt-worker.js added one more absolute slot path to the
install-complete probe, pushing it further past the exec-size budget.
Testing each artifact relative to the slot (inside a subshell cd) cuts
the probe from ~12.2 KB to ~3.6 KB, so it drops off the over-budget
list instead of raising a ceiling.
* fix(mobile): pin host.platform's read to a platform type, independent of which zod copy resolves
With mobile's own dependencies installed (which CI does for any PR that
touches mobile/), the e2e program resolves two zod copies, and the
salvaged platform arm from src/shared inferred as {}. The schema now
narrows through an explicit platform guard.
* fix(mobile): keep the host.platform read line intact for its RPC mutant; narrow in a second step
Mobile verify: the recorded-RPC mutant anchors on the read line, which
the previous guard rewrote, so the mutant could not apply.
* refactor(cli): app-executable resolution in its own module; drop the platform guard now zod is aligned
launch.ts went one line past max-lines with main; resolveAppRoot,
resolveForegroundOrcaExecutable, getExecutableAppArgs and
stripElectronRunAsNode move to orca-app-executable.ts (re-exported, so
callers are unchanged). With mobile and root on one zod (#31260), the
host.platform schema no longer needs the extra narrowing step.
* refactor(cli): import app-executable helpers from their module; no re-exports from launch.ts
|
||
|
|
d98b8eddb4 |
fix(chat): keep resumed Claude messages deliverable after settings reports (#30982)
* fix(chat): separate applied Claude choices from provider reports * fix(chat): preserve Max effort without settings readback * test(chat): retain Max effort confirmation contract * test(chat): a literal model change no longer pre-flights the catalog * fix(chat): record observed execution independently of accepted choices * fix(chat): fence model reports at prompt input before recording * test(chat): inspect Fast evidence without unsafe outcome access * test(chat): honor input preparation in turn fixtures * test(claude): accept dispatch metadata after stopping a turn * fix: retire deferred chat options at input preparation |
||
|
|
5999642ff1 |
fix(settings): prioritize sharing in Remote Orca Servers (#31335)
* fix(settings): prioritize sharing in remote server settings * chore(i18n): remove obsolete remote server settings copy |
||
|
|
356bda10d1 |
fix(github): repo cache eviction clears every host's entries, keeping a live twin's (#31312)
* fix(github): repo cache eviction drops every host scope's entries, keeping a twin's own Eviction and issue-source changes matched only unscoped key prefixes, so SSH-, runtime- and source-scoped entries survived repo removal. Keys are now composed by one builder and read back by its inverse, so eviction classifies each key by host and keeps only a surviving same-id twin's bucket. Hosted review (GitLab MR) entries are evicted too. Co-Authored-By: Claude <noreply@anthropic.com> * fix(github): read task-source scopes with empty parts back whole Co-Authored-By: Claude <noreply@anthropic.com> * fix(github): an issue-source change evicts only the changed twin's buckets Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
379ad0c5cd |
test(terminal-surfaces): pin why main's pane republish is deferred to a microtask (#31333)
The deferral keeps a terminal's connected flip, noted inside syncWindowGraph's record update, from re-entering syncWindowGraph and recursing. Layout-owner (switch-on) builds only. |
||
|
|
c6becfaf7a |
fix(github): main finds a twin project's registered row by id and path, and a refresh by its host (#31274)
validateRegisteredRepo took the first row with the requested id, so for a repo id registered locally and over SSH it read the other twin and refused the second twin's fetch as a path mismatch. It now matches id and path together, uses the GitHub source host when one is named, and lets a PR-refresh candidate's host pick between twins at one path; a lone row's own fields still win over stale candidate fields. |
||
|
|
018c6e0c42 |
fix(mobile): create and edit notes on headless hosts (#31291)
Create, read, and save Markdown notes on headless hosts through authorized file operations and persisted editor tabs. Addresses the Markdown-note reproduction in #22186, reported by @shota46. Thanks to @Jinwoo-H (#31101), @brennanb2025 (#25436), and @Meapri (#23357) for related community implementations and regression coverage. Those PRs retain broader/distinct scope. Validation: focused tests, paired-headless E2E, iPhone simulator proof, and all CI checks passed. |
||
|
|
065e6873f6 |
A paired desktop shows the held-terminal banner and Reopen for a server's held terminals (switch-on builds only) (#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 |
||
|
|
a1f2094d8c |
fix(github): work-item cache keys a twin by the row at the request path and its canonical host (#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. |
||
|
|
0c2b01367e |
fix(github): GitHub routing takes the caller's host for a twin; the task filters pass theirs (#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 |
||
|
|
6e589bb4ba |
Layout-owner build: phones see main's terminal and editor rows (A2, dark) (#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> |
||
|
|
8e5992663b |
A terminal pane that loses its reconnect to another connection attaches to the winner instead of showing terminal_pane_owner_changed (#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 |
||
|
|
d7ef7741d1 |
fix(serve): restart the macOS serve supervisor from the updated app (#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 |
||
|
|
60b8eb4a6f |
fix(browser): keep in-app browser logins when a WSL or managed SSH host re-pairs (#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. |
||
|
|
1c76c71409 | fix(mobile): list session tabs in the desktop tab bar order (#31269) | ||
|
|
4e483ae2e6 |
perf(serve): clone the supervisor's code copy in one step on macOS (#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 |
||
|
|
5c666d2974 |
fix(native-chat): retry a failed chat name, show the failure on the tab's hover card, and add Regenerate name (#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 |