Repository navigation
Conversation
… Server focus, and a no-row listing is this app's (stablyai#31119) * fix(renderer): a twin repo id refuses instead of following the Active Server focus, and a listing for a repo with no row is this app's findRepoOwner no longer lets focus pick between same-id repo rows: runtimeTargetForRepoOwner refuses a twin, as resolveOwner does, and getRuntimeEnvironmentIdForRepo names no server for one. repoHostId stamps and routes a repo with no row as this app (or a web client's only server), never the focused server. * fix(renderer): a fork names its source's host, and GitHub routing keeps its twin rule until it can refuse * fix(renderer): a fork captures its source's server transport, and a GitHub edit on a twin refuses instead of using this computer * fix(automations): a new-per-run workspace is created on the run's captured host * fix(automations): pass only a captured run host, so a twin without one refuses * test: stamp the server repo row through a store helper, prune the focus-read baseline
…dow's live group (dark) (stablyai#31123) Behind ORCA_LAYOUT_OWNER_IS_MAIN, a CLI or phone file open is applied by main as an openEditorTab layout command, so a host with no window can open files. The window reports each workspace's active group as an optional view fact on syncWindowGraph, and shows main's tab once the frame that draws it arrives. Editor language detection moves to shared so main can name a file's language. Co-authored-by: E2E Test <e2e@test.local>
…yai#30415) * perf(editor): watch only active, recent and at-risk workspaces Background workspaces no longer hold a whole-tree native file watch. The active workspace, the 3 most recently visited ones, and any showing a deleted/renamed tab keep theirs; every other workspace's subscription is released (local, WSL, SSH via the relay, and runtime hosts alike). When a dropped workspace is watched again, its tabs are verified against disk through the existing paths: one overflow resync once the watch is in place reloads clean tabs and refreshes git status, Files and markdown lists; dirty tabs go through the restored-tab disk check, which shows the changed-on-disk banner. Saves stay guarded by the host-side disk check, so nothing is held while unwatched. Also: watch requests are shared per directory and host; root events reach every workspace on that root and SSH payloads carry their connection id; late SSH and local/WSL capacity-retry installs send an overflow resync. * fix(editor): catch up combined reviews, deletions, late installs and Files trees after a rewatch - Overflow catch-up drops cached combined-diff bodies for the workspace and re-reads every loaded section of a mounted review (snapshot-backed too). - Clean edit tabs are probed on the owning host first; a file proven gone is marked deleted and keeps its last content instead of reloading into an error. - Dirty tabs are checked from the overflow itself, so the check runs after the watch is installed, including late capacity/SSH installs. - Local/WSL Files trees also take the renderer-synthesized catch-up. - Deleted/renamed-tab pins are capped at 3 beyond the active and recent set. * fix(editor): catch-up keeps unsaved combined-diff edits and drops stale existence answers - Cached Changes views reset only clean loaded sections; dirty sections keep their drafts and baselines (also for per-path invalidation). - A mounted review re-reads loaded and in-flight sections on catch-up. - Existence answers carry a per-tab generation; a newer probe or watch event for the tab supersedes an older answer. - A tab is marked deleted only on a per-path ENOENT, never on a whole-request not-found or an old host's scalar fallback. * fix(editor): re-check dirty tabs requested mid-read, prove runtime deletions, chunk existence probes - A disk check requested while one is in flight is followed by one fresh read (coalesced) instead of being answered by the older reply. - The restored-tab check probes runtime-owned files on their host; a per-path ENOENT marks the tab deleted. After a retry ceiling it stops, clears the suspension and shows the changed-on-disk banner. - Overflow existence probes are split into host-sized chunks, in order. * fix(editor): a reload that finds the file gone keeps its content; stale absence answers re-read - A tab that already read its file and now gets a path-specific ENOENT on reload is marked deleted and keeps its last content, with or without an existence probe, so older paired servers are covered. - The restored-tab check applies its request generation after the absence probe too, so an older answer cannot resolve a newer request.
… the next startup reconnects (dark, layout-owner build only) (stablyai#31118) Main runs one pass at startup and each window load, with or without a window (desktop, orca serve, orcad): every live local terminal no pane names is bound to the pane its record names while main still holds it free, else shown in a new tab bound to it. The window no longer asks main to adopt terminals; it only reads that main holds them. The window's layout.apply loses its adopt field. Main also derives activeWorktreeIdsOnShutdown, remoteSessionIdsByTabId and activeConnectionIdsAtShutdown from its own layout, terminal records and SSH connections, replacing the window's copies in every save and the quit checkpoint. Co-authored-by: E2E Test <e2e@test.local>
…l rows (stablyai#31127) stablyai#30037 made the headless row builder emit customTitle on every terminal row; the Linux baseline recorded before it (stablyai#27688) never listed the field, so the spec fails on main with either layout switch. Co-authored-by: E2E Test <e2e@test.local>
…#31108) * ci: the manual switch-on leg runs the headless serve, paired-host and serve-restart specs With H1's switch-on server template, these exercise main as the layout owner with no window. Spec paths are exact: the changed-spec job greps each file, so prefixes only work for ssh-orcad-. * ci: keep the Windows serve job out of the switch-on leg; it builds switch-off * ci: the switch-on leg also runs the server-workspace tab composition and serve-restart binding specs --------- Co-authored-by: E2E Test <e2e@test.local>
…seded or closed (stablyai#31128) Follow-up to stablyai#30415. A watch event that superseded a pending overflow existence probe left the tab's entry behind forever, and the dirty-tab disk-check request map never removed anything. Superseding now deletes the entry, and closed tabs' disk-check requests are dropped by the scan's existing open-tabs subscription. The disk-check map uses a global sequence so a reopened tab can't reuse an old read's number.
… sidebar feed, installed with the rows (stablyai#31102) * fix(sidebar): the phone reads projects and project groups from main's sidebar feed, installed with the rows The phone built its project sections from a catalog the desktop window published through a side channel (mobileRelay.projectSections). That copy changed on a timer after the event that told the phone to re-read it, and a re-read asked for during a read in flight was dropped, so the phone could keep an old grouping (a project under the wrong group, a group missing) indefinitely. Main now publishes the catalog (repos with their group and order, project groups, projects and host setups, every host's including paired servers') as a projectCatalog domain of the sidebar feed, from its own store and its own reads of each paired server. The phone reads it from the same install as the rows. mobileRelay.projectSections stays, answered from the same builder, for pages that predate the domain; a page on a desktop without the domain still reads it, now re-reading when an invalidation lands mid-read. The phone e2e list helper runs the page's real refresh lifecycle instead of re-reading on every assertion, and covers a group move while a read is held. * test(mobile): stand AppState in for the screen hooks' react-native mocks, and tolerate a world with no project rows in the parity catalog * fix(sidebar): main files a server's copy of a grouped project under its group, and reads a server's catalog only on catalog events - The window gives an ungrouped paired-server repo the group of an existing copy of the same project (applyInheritedProjectGroups). That function now lives in src/shared and main's catalog builder folds each server's repos through it, so the phone files the copy where the desktop does. - A paired server's change watch now says whether the event can carry a catalog change. Only ready, a replay and reposChanged mark the catalog stale; worktreesChanged (pins, unread, sort stamps) reads rows only. - The parity fixture now builds the server side from raw server answers, folded through the window's mergeFetchedReposForHost and through main's own server read and builder, with a server copy of a grouped project and a partial saved manual order. It fails without the inheritance. * fix(sidebar): parse a paired server's catalog rows with a schema instead of reading keys reflectively * fix(mobile): sync the feed ref after render and re-read queued project sections without an await loop * fix(mobile): spell the AppState shape the refresh reads, so the e2e helper pulls in no React Native types * test(sidebar): give the merged builder test's feed domains a project catalog --------- Co-authored-by: E2E Test <e2e@test.local>
…(dark, layout-owner build only) (stablyai#31131) * feat(layout-owner): a layout-owner server advertises that it takes a paired desktop's layout intents (dark) status.get adds layout.intents.v1 only in an ORCA_LAYOUT_OWNER=main build. It versions the layout.subscribe frame shape and the layout.apply contract a paired desktop will rely on. Tests pin the layout methods open to a paired desktop and closed to phones, and that an older desktop still reaches every session.tabs mutation on such a server. Co-Authored-By: Claude <noreply@anthropic.com> * test(layout-owner): read the paired-desktop frames and seed the server without casts Co-Authored-By: Claude <noreply@anthropic.com> * review: the capability comment says only what the code does, and tests pin each next check Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: E2E Test <e2e@test.local> Co-authored-by: Claude <noreply@anthropic.com>
…rminal ready at once in the layout-owner build (stablyai#31132) In the layout-owner build, main's load binds the pane again from its tab row while the terminal is alive, so the replacement host publishes the pane ready and never pending. The serve-restart spec waited for a pending frame that this build correctly never sends. It now expects pending only in the current build, and asserts no pending frame in the layout-owner build. A unit test pins the precondition on a windowless host: main's own writes leave no shutdown list, and a real store load restores the binding. Co-authored-by: E2E Test <e2e@test.local>
…'s file open to succeed (stablyai#31138) Co-authored-by: E2E Test <e2e@test.local>
…a reader built without the switch applies a layout-owner host's order (dark, layout-owner build only) (stablyai#31140) Co-authored-by: E2E Test <e2e@test.local>
…ote itself (stablyai#31144) Guards the finding behind stablyai#31132: on a host with no window, a profile written only through main's own commands (create, split, close, sleep, and a pane never started) reloads with the layout-owner switch on and keeps every tab row, pane, binding, tab-bar entry and sleeping agent. Main lists every tab it creates in the tab bar, so the load sweep never needs a shutdown list to keep them. Co-authored-by: E2E Test <e2e@test.local>
…uilding from its whole session (dark, layout-owner build only) (stablyai#31152) * The window shows each layout records frame record by record, without reading its session back (dark, layout-owner build only) A records frame no longer re-serializes the window's whole session and runs the Loader. Panes' terminal bindings and live titles apply alone, to the tabs the frame names; any other record builds only its own workspace's slices, with the view and facts read from the store. Only a workspace the store does not show from frames yet (startup, a reopened stream, a first visit, one sent whole) takes the full build. Tests install an apply check that, after every flush, compares the store with the full build for the same cache and pre-flush store; the shared layout-apply transport now sends main's records frames and fails a flush that differs. * A frame of only tab rows shows without reading the window's session back or running the Loader (test, layout-owner build only) * The account-restart test's fake binding runtime carries per-pane binding generations, so it passes with the switch on (test only) * The account-restart test types its store writes, so its five old e2e type errors are gone (test only) * A workspace build reads the editor selection and groups through the save's own writers, as the full build does (dark, layout-owner build only) * A tab released from the window's hold shows the binding main wrote meanwhile (test, layout-owner build only) * The frame-apply check still checks a flush's other workspaces when it builds one in full, and the shared transport keeps a test's own check (test only) * The frame-apply check is proven to catch a mismatch beside a full build, and the shared transport restores only the check it installed (test only) * Name the pane tree the window compares by what it holds: the tree without its panes' bindings or live titles (dark, layout-owner build only) --------- Co-authored-by: E2E Test <e2e@test.local>
… paste, drop or attach (stablyai#30815) * feat(native-chat): a queued message's Save can replace its images (host, wire and capability) * feat(native-chat): edit a queued message's images on desktop: remove, attach, paste or drop * refactor(native-chat): share the queued edit's image rules and drop the rarely-shown Save reasons * fix(native-chat): a queued edit's refusal says the edit wasn't saved, and its wire takes a full image set An expired upload refused on Save read "The command didn't run": the queued message update fell through to the command write kind. It now has its own kind whose sentence is "Your edit wasn't saved." The images param caps at MAX_BLOCKS, matching the shared edit rule that counts them with the text. * fix(native-chat): a queued edit keeps image markers right and owns drops and pastes on its card - Removing a kept image drops its [Image #k] marker from the edit's text and renumbers the later ones (shared rule, reused by the phone), so the agent never reads a marker that names the wrong image. - A file dragged from Orca's own tree onto the editing card goes to the edit, like a Finder/Explorer drop; the chat box overlay no longer covers the card. - Cmd/Ctrl+V with focus on the card's buttons goes to the edit, not the chat box. - A "changed" answer says "Your edit is in the chat box" only when something went there; otherwise it says the edit wasn't saved. - The empty hint counts an uploading image as content and is not shown for agents without image input; the list grows only while thumbnails show. - Every hand-off releases the edit's preview URLs. * fix(native-chat): an in-app drop on the editing card goes through the chat box's owner check The edit no longer runs its own copy of the in-app drop check, which resolved the target host through a path that can read the Active Server setting. The chat box's claim checks the drop and routes it by the chat's owner as before, then hands the files to the editing card's sink when the drop landed on it. Also: the kept-image id list is readonly where it is read, and the marker rule looks up the remaining images in a Set. * fix(native-chat): only an edit that takes images keeps its card's pastes, and no drop joins a Save in flight The pane paste bridge no longer skips every paste inside a queued editor: the card's own paste handler already claims image and file pastes, which the bridge respects. A menu paste is left to the card only when the edit takes images (the card is a drop owner), so an older host or an agent without image input behaves as before, and text pasted with focus on the card's buttons goes to the chat box. An in-app drop handed to the card while its Save is in flight adds nothing. * fix(native-chat): a failed capability re-check keeps the host's last answer, and an edit takes no files before its lease A re-check after regained contact that failed wrote "unknown" over a known answer for the same host, so features the host had reported (such as editing a queued message's images) vanished on a connection blip, mid-edit. A failed check now keeps the last answer; a different host still starts at unknown. Paste, Attach and OS or in-app drops into a queued edit are now blocked while it acquires its lease, as typing already is. * fix(native-chat): an image change is never saved as text alone when the host stops reading as taking images Reverts the shared capability hook to keep main's behavior: a failed re-check reads as unknown. Instead the queued edit derives from live state that an edit whose image set changed can't be saved while the host doesn't read as taking images, so a connection blip never saves renumbered markers over the old images or drops added ones. Save comes back once the host answers again. * fix(native-chat): keep queued image markers and drop feedback in sync * fix(native-chat): derive queued image markers without changing edit text * test(native-chat): the pane drop test's intake append returns the paths it didn't take * fix(native-chat): route menu paste to the focused queued edit * fix(native-chat): honor queued edit paste ownership before focus redirection * fix(native-chat): preserve native paste without clipboard access * fix(native-chat): keep paste in the owning queued edit
…ead of a split service cap (stablyai#31178) The deploy passed `--max`, which is gcloud's service-wide cap. Cloud Run divides that cap among the revisions taking traffic, so during the 20/80 stage a candidate under a cap of 5 got one instance while the serving floor kept four, and the spread check could never see the full floor (deploy M, 1 of 5 for six minutes). Candidates now pin `--max-instances` per revision, and the deploy resets a service cap below two full revisions to Cloud Run's default before it creates any candidate. The rollout overlap of two full directors is already in the checked Cloud SQL connection budget.
…yout stream (switch-on builds only) (stablyai#31181) * review: shorten a test comment; the methods file header names layout.apply Co-Authored-By: Claude <noreply@anthropic.com> * wip(h5b): a lost remote layout stream reopens with the local backoff * wip(h5b): server frame id translation (pending the binding handle) * wip(h5b): frame syncs take a source; the mirror gates frame-drawn server workspaces and indexes handles; ids translate with session.tabs handles * wip(h5b): one layout stream per capable paired server, handing workspaces between frames and the mirror * wip(h5b): tests for server frames; layout.getRemoteSnapshots returns only paired servers (L18) * wip(h5b): re-gate a server workspace when its routing arrives after the snapshot; e2e hook and spec * wip(h5b): late-owner test; e2e spec on the switch-on leg * wip(h5b): server streams follow every paired capable server, not the focused one; a known-kind guard without a cast * wip(h5b): server-frames tests read real paired-server state; no casts * fix(h5b): a handed-back workspace is the mirror's alone; frozen frames draw only gated workspaces * fix(h5b): a workspace rejoining the gate takes the full build --------- Co-authored-by: E2E Test <e2e@test.local> Co-authored-by: Claude <noreply@anthropic.com>
…only the copy sent to clients (stablyai#29982) * fix(native-chat): save every chat reply and tool output whole; bound only the copy sent to clients Orca cut Codex replies, every agent's reasoning, plans, prompts and all tool output to a 16 KB head when it saved them, and threw the rest away. The limit existed so a reconnecting client is not replayed a huge row, but it was applied at save time, so every transport limit became permanent data loss. The journal now keeps what the agent said. One outbound form, applied to every history page, live batch and catch-up, sends a client the same 16 KB head of each tool output and diff it always got, and marks the item `shortened`. A new capability-gated `agentSession.item` RPC returns the whole item, and the desktop and phone show "Show full output" / "Show full message" when the copy was cut. Items past half a page are shortened and marked instead of replaced with "too large to show". Two bounds stay, both named: a 4 MiB ceiling per text (head and tail), applied where every provider's rows enter the write queue so one write can never stall a chat, and a preview on Grok/OpenCode/OMP/Pi calls while they run, whose closing row is built from the whole value the translator held. * test(native-chat): an over-budget history item is shortened and marked, not replaced * fix(native-chat): format, keep the sink and projection under the line limit, update method count and tests * fix(chat): preserve complete writes and keep detail current * test(chat): align final tail reservation with captured content * Prototype source-batch journal ownership for Claude * Pin the resolved iterator promise in ownership experiment * Revert "Pin the resolved iterator promise in ownership experiment" This reverts commit bdb49ab. * Revert "Prototype source-batch journal ownership for Claude" This reverts commit e5ed387. * Preserve large received chat content while pacing input * Verify received chat writes wait for journal persistence * Close test journal databases before removing temporary roots * Exercise item detail in the cross-version chat wire manifest * Pin complete saved output and fitted client publication * Exercise Pi exit retention with real journal write pressure * Settle terminal web test unmount before DOM teardown * Reuse existing readers and contract support in session tests * Reuse measured chat item sizes in history pages * fix(chat): derive page form type from reader contract * fix(chat): open phone user messages while loading full text * test(native-chat): reuse imperative context provider construction * test(native-chat): format full-content disclosure fixture * ci: preserve unit shard diagnostics after a timeout * ci: retain unit shard output when tests time out * ci: expose unit shard watchdog and output stages * Revert "ci: expose unit shard watchdog and output stages" This reverts commit 0b4991b. * Revert "ci: retain unit shard output when tests time out" This reverts commit 87eec4c. * Revert "ci: preserve unit shard diagnostics after a timeout" This reverts commit 1888c65. * test: verify complete Claude tool output without large failure diffs * Simplify reply extraction in cross-version session test * Restore full message recovery for grouped phone thoughts * Target recovery button in grouped thought fixture * test(native-chat): count agentSession.item alongside main's new structured method * fix(chat): reuse strict session subscription schema * test(chat): open Codex before checking received content
…hell named only on its tab still runs (stablyai#31093) * fix(ssh): an old SSH host converts while a relay shell named only on its tab still runs The conversion check counted legacy pane-key aliases as terminals waiting to reconnect. An alias only routes hook events to a pane whose terminal the lease check, the bridge or the terminal gate already accounts for, so it no longer blocks the move. * Tighten the alias census comment * Register the tab-only relay spec: typed deployment lookup, relay fence baseline, PR route --------- Co-authored-by: E2E Test <e2e@test.local>
…esktop again, not an SSH setting (stablyai#31120) Every orcad turned on the SSH host browser bridge, so since open-url began going through it (stablyai#30849) a user-started serve with no desktop refused with SSH-settings advice. Only a client's managed launch enables the bridge now; a started server answers browser_host_unavailable (stablyai#30782). Co-authored-by: E2E Test <e2e@test.local>
… it ends (stablyai#29007) * fix(mobile): reopen the phone chat's live feed from its last row when it ends The phone opened a chat's live transcript stream once per connection and never reopened it when the stream ended or failed while the connection stayed up, so the chat silently froze on old rows. Move desktop's reopen-from-cursor read into src/shared and run the phone on it too, with an app-foreground retry. * fix(mobile): retry the chat feed on the app-resume signal client-context already relays Module mocking react-native in the shared vitest setup is rejected; publish the existing revival nudge instead, the way desktop's host-contact signal feeds its read. * test(mobile): let the phone's coalesced live read apply each frame before comparing
…ai#28983) * fix(mobile): keep the chat composer's controls inside its row The model pill never shrank (React Native items default to flexShrink 0), so a long model name slid under Mic and Send. The permission pill now shows only its mode icon, resolved from one shared mode->icon-name table that desktop uses too. * fix(mobile): resolve the permission icon only when a pill renders * style: format the permission pill test * fix(desktop): keep the permission icons a static record
…fter 30 seconds (stablyai#30395) * fix(codex): opening or resuming a Codex chat waits for Codex instead of failing after 30 s The handshake (15 s) and thread/start / thread/resume (30 s) had their own deadlines because the Codex start ignored the host's startup signal. It now honours it, as Claude's start does, so those requests carry no deadline: the host's startup limits, a close, a Stop or quit end a start that hangs, and the abort reaps Codex. Calls after the start keep the 30 s default for now: they run in the session's queue, which a Stop or a close waits behind. * test(codex): keep the handshake wait test in its own file under the line limit * fix(codex): the one-shot app-server runner takes the same no-deadline option as the chat connection * test: accept nullable Codex request timeout in probe fake * fix(codex): bound direct opens at their caller * Remove obsolete Codex startup timeout scenario
…ablyai#30939) * fix(claude): keep Orca's background Claude runs off the user's MCP servers and hooks The model picker and / menu listings, chat naming, and commit message, PR, branch and Jira text generation each start a short Claude run. Every one but the / probe loaded the user's MCP servers and hooks, so each run started every server (one cut off a Notion OAuth refresh and lost the sign-in) and fired SessionStart hooks. Every background run now passes --strict-mcp-config and disableAllHooks. A user's own inline --settings merges into the one settings value Claude keeps, and their --mcp-config still loads. When Claude refuses strict mode because an enterprise MCP config is present (which already keeps user servers out), the run is retried once without the flag. Tracked in STA-11095 and stablyai#30411. * fix: centralize background Claude generation and retry policy * fix: rebuild Claude background retries from generated arguments * test: include branch in generation retry fixture * fix: preserve user settings arguments in background generation * test: define retry fixture streams across runtimes
…tablyai#30561) * fix(native-chat): a chat's model, effort and Fast belong to the chat, not to a model Structured chats kept their option values per model on the client, so a value the host saved without a model (a resumed chat that never picked one, Codex Fast saved on the conversation) had nowhere to live. The client either dropped it or labelled the chat with the listing's default model to hold it. - One flat record per chat holds model, effort, speed and Fast independently; a model pick changes only the model and the host reconciles the rest. - Controls come from the model they apply to, separately from the values; with no model known, a saved Codex speed still shows and Standard turns it off. - The options answer marks values a catalog default supplied (current.defaulted). A resumed chat never names or sends such a model as its own; a new chat still shows the default the host says it runs. An answer without the mark reads as before. - Every send, including a new chat's first message, carries the chat's own model and effort and held picks, never a default read off the picker. - A held launch pick supersedes only an earlier pick of the same option. Tracked in STA-10947. * fix(native-chat): a new chat's first send still pins the default model it shows A new chat that never picked a model carries the default its picker names, as before, so it keeps the model it actually ran after the CLI's default changes. Only a resumed chat's defaulted model is never named or sent. Tracked in STA-10947. * fix(native-chat): narrow the host catalog before reading whether its listing names the default The flag lives only on a listed catalog, so the check confirms the catalog is not the unknown answer before reading it; the first-frame test reads the chat's flat model value. * fix(native-chat): a reopened chat whose saved model was refused records the model it now runs After a retired saved model is reset, the options answer no longer marks the model the child runs as a default, so the next send records it instead of replaying the refused model on every reopen. * fix(native-chat): a new chat names and pins the default it shows, and a model pick keeps only what the model offers A chat this view started names the default model the host fills in, whichever of the model list and the options answer lands first, and its first send carries the shown model and effort. A resumed chat still names and sends only its own values. A model pick, held or live, keeps an effort, speed or Fast only where the new model offers it, so a value the model cannot take is never shown, sent or remembered. * test(native-chat): give the no-Fast model fixture its required default flag * fix(native-chat): an open chat's model pick leaves reconciling to the host A live pick no longer drops values on the client; the host's reply, when it carries its full value set, replaces the chat's own values as a read does. Only a pick held before launch, with no host yet, keeps just what the picked model offers. * fix(native-chat): a pick reply only adds the values it names A pick's reply carries only the values the host set or was asked for, so it can't speak for the rest: it no longer removes values it leaves out. Joined tests pin real reply shapes and a new Claude chat in a fresh Claude home naming and pinning Opus High. * fix(native-chat): a tier the drawn model doesn't offer shows as Standard A Speed value another listed model offers but the named model doesn't is drawn as Standard, which is what the host reconciles it to, while the chat's record keeps the host's value. Effort, a tier no listed model offers, an unlisted model and a stand-in model's controls still show what the host holds. After a lost pick reply the picker no longer shows the old tier as a raw extra choice. * fix(native-chat): a never-sent chat names and pins its default after a reopen The options answer no longer marks the default model and effort as stand-ins for a chat this host started that has sent nothing, so a chat reopened after a restart or on the phone names them and its first send pins them, as before. A chat taken from history, or one that has sent, keeps the mark. * fix(native-chat): narrow the missing record as null and type the new-chat fixture's handle chain * test(native-chat): the unloaded-turn context test's record store answers null for a missing record * fix(native-chat): a chat's speed shows what its next turn runs A Codex tier the drawn model lacks shows as Standard, which is how Codex runs it, except Flex, which Codex passes regardless: established Flex keeps showing, and after a model pick the host hasn't answered it shows no selection until the reply or a read. Each Codex turn sends the same effective tier without rewriting the chat's saved one, and a new chat's create carries held picks as the picker draws them, settling them so they aren't applied again after publish. * fix(native-chat): one speed rule for picker, host and turns; never-sent needs proof; create keeps held picks A Codex tier the model doesn't list shows and runs as Standard under one rule shared by the picker, the host's live reconciliation, each turn and the at-rest answer, which keeps the saved tier for a model that offers it. An older host's Flex shows as it reports it. A chat counts as never sent only when no turn, rewind or untrustworthy journal says otherwise. A new chat's create carries held picks as the picker draws them but keeps them as the user made them until create succeeds, so a refused create and retry still has the user's own pick, and a newer pick still applies after publish. * test(native-chat): type the resting journal mock and cut overlapping option tests One typed resting-journal helper replaces six inline mocks and the broad object parameters the low-evidence check rejects. Cuts tests the joined flows already pin: the resumed list-or-answer ordering test, the restart-after-first-send view test, the held effort before a model pick, and the model-pick and restart loop of the saved-Fast test. * test(native-chat): cut option assertions the joined flows already pin Drops the Codex Standard row (the joined tier test pins it), the dispatched-source and literal effort-choice checks, and the resumed view test restart and model-pick steps; folds the pick table. * fix(native-chat): an at-rest pick reply and answer name the speed the next start runs The reply to a pick made at rest now carries the tier the chat model runs, through the same rule as the at-rest answer, so an older app whose follow-up read fails shows what the next turn sends; the saved record keeps the pick. A saved tier no listed model offers (a Flex from the user Codex config) is the agent configured tier at rest too, as the live rule and the picker already read it. Restores the pin that an accepted Codex reply naming Standard shows with no later read. * fix(native-chat): a reopened chat with no saved model names its default at once A reopened chat that never saved a model showed just "Model" until a send started its agent, while main names the list default straight away. The picker now names the default the host marks for every chat, as main does. Whether a send carries that default is a separate rule: a new chat sends it, so its first send pins it; a reopened chat does not, so its next start still runs the agent's own. * fix(native-chat): a chat with no saved model keeps the model its first turn ran A chat that never saved a model followed whatever the agent's default was at each start, so a chat resumed from history or a terminal could quietly switch models after a restart. When a message is handed to the agent on such a chat, the host now saves the model that turn ran: the message's own model when it carried one, otherwise the model the agent reports running. A default the host filled in, no model at all, or a model the agent would refuse as a choice on later sends, is never saved. The write sets the model only while none is saved, inside the record store's transaction, so a pick made meanwhile always wins and effort, speed and Fast are untouched. One host step covers Claude, Codex, ACP agents and Pi. No new saved field. * fix(native-chat): a chat with no saved choices keeps the model and effort its first turn ran A Codex message that left the model or effort unset was filled from the model list's defaults, not from what the running thread uses. Once the host saved the model a chat ran, the next Codex turn carried that model alone and ran the list's default effort for it (a configured xhigh became medium), and a message carrying only an effort ran the list's default model while the chat saved the thread's. A Codex message now resolves an unset model or effort to the thread's own, and only a switch to a different model takes that model's default effort, the same rule the other agents use. When a chat with no saved model records what its first turn ran, it now records the effort, service tier and Fast that turn ran too, in the same one-time store write: each only where the chat has none saved, only from the message's own value or the agent's real report, never a filled-in default, so a later change to the agent's configured effort no longer pairs the kept model with an effort it never ran. No new saved field and no wire change. A Codex follow-up sent while a chat's first turn runs is compared with what that turn actually runs, so it steers instead of waiting for a new turn just because the host saved the model meanwhile. Also fixes the delivery loop's optional recording hook type. * fix(native-chat): key what a child ran by its provider child type * fix(native-chat): record each option a turn ran where none is saved, and drop what a model pick can't run A Codex message that only switched the tier set the thread's effort as a lasting override, so a later switch to a model with no listed efforts was refused; the effort is again written only where the model brought one or an earlier override needs replacing. Recording what a turn ran now works per option: a chat that already saved its model still records the effort it ran, and so on for each option it has none saved for. Tier and Fast come only from the agent's own report, since a message steered into a running turn runs that turn's, and Fast is kept only when the agent reports it actually routed on: an off state may only mean Fast is unavailable. A model pick drops a saved effort, or Fast, that the newly picked model's listed row does not offer, in the one host step both resting and live picks go through, so no later send carries a choice the agent would refuse. The saved tier is kept, as before: it runs as the model's effective tier. * fix(native-chat): save only options a turn really ran, and only ones the chat's model runs A chat with a saved model recorded the effort its agent's settings named, not the one the turn ran: with "effortLevel": "high" in Claude's settings and Haiku saved (no effort control), the chat saved high and every later send was refused. A Codex thread effort its saved model doesn't list did the same. What a turn ran now comes from the agent's own dispatch: Claude reports the choices it applied and the effort the CLI resolved for the model it applies, never the settings level alone; Codex reports its final request over the thread's own model and effort, and a steer reports its running turn. This evidence lives only in memory on the dispatch result; agents without it keep using their own reports. One admission rule now decides every saved option, inside the ordered record write: under a model the saved catalog lists, an effort must be one it offers and Fast must be one it replays (Claude needs positive support). A late record of a turn that ran on another model than the one the chat now has saves none of that turn's options. A model pick drops inherited options its new model doesn't run, an explicit pick or a new chat's option the model doesn't offer is refused with the existing option refusal, and unknown catalogs or unlisted models pass as before. The saved tier keeps its effective-tier rule, and nothing already saved is cleaned up later. * fix(native-chat): an attach that founds a chat admits its options against the saved catalog too * fix(native-chat): Claude and Codex always name what a turn ran, and picks are judged inside the write The handover type was misnamed, which broke the typecheck. Claude and Codex now attach their dispatch evidence even when it names nothing, so a Claude turn dispatched before its settings readback never records the settings effort it did not run; the agent's own report still names the model and stands in only for agents without dispatch evidence. Recording leaves the saved-model and set-if-unset checks to the store's one admission rule. An explicit effort or Fast pick is now checked inside the ordered write, with the same refusal, and only at rest: a running chat's own menu can offer more than its saved catalog row (Pi's probe menu stops at high). * test(native-chat): Codex admission tests accept the dispatch's run evidence * fix(native-chat): a new chat's options are admitted against the folder list its picker showed * fix(native-chat): a new chat's admission reads the list its picker read, resolved once by the create * chore: re-run CI against main's formatting fix (stablyai#30918) * chore: re-run CI against main's formatting fix (stablyai#30948) * test(native-chat): explain the seed-options stub host cast * fix(native-chat): attach keeps its two-argument call when there are no attach options * Revert "fix(native-chat): attach keeps its two-argument call when there are no attach options" This reverts commit e633d20. * test(native-chat): create-path attach assertions check the params argument, not the arity
…blyai#30935) * perf(cmd-j): preload the jump palette so its first open renders without React's reveal delay * fix: reuse lazy retry wrapper for preloaded components
* Parallelize adhoc Mac architecture packaging * Include parallel Mac jobs in release timing reports * Cover parallel Mac publication and failure gates * Explicitly scope archive targets to each Mac architecture
* 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
…ablyai#31188) * fix(mobile): remove the chat's Tools toggle and its open-everything mode * test(mobile): keep the scroll-ownership tests' runs drawn without the Tools default * test(mobile): the scroll-ownership harness opens settled turns as the old Tools default did
…ts hook turn only (stablyai#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.
…blyai#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
…tree cache is cold (stablyai#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
…icker, so a 13k-host ssh config can't crash any of them (stablyai#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.
… the same moments the window did (dark) (stablyai#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
… without shutting down Claude chat workers (stablyai#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 (stablyai#30134)" (stablyai#30572) This reverts commit 3ef5a46. * fix(native-chat): a Claude send stays held until its settlement reaches the host, on the reapplied held-sends read * fix(native-chat): a Claude send stays Working, not in doubt, while a Stop is still ending its CLI * test(pi): drop the removed idle-release dependency from the adapter fixture
…ablyai#31431) * fix(release): replace mobile bundle API polling with job dependencies * test(release): update qualification token permission expectations
…auto-sync-fork-with-upstream-stablyai-orca-ru-20261021T1400 # Conflicts: # config/scripts/check-node-runtime-pin.test.mjs # config/scripts/pr-preflight-gates.test.mjs # config/scripts/win32-test-lane-registration.test.mjs # src/renderer/src/components/native-chat/native-chat-composer-prompt-recall.test.tsx
Reset 2,040 upstream-owned paths to the tag, removed 57, restored 171 exception paths and three-way merged the 21 that upstream moved, and re-merged 82 seam files without -X ours (23 resolved by hand). Retired two seams the release made obsolete: config/tsconfig.cli.json now follows imports from src/cli, and the native-chat-session-options passthrough fed an always-undefined observedAt to a function upstream replaced. Advanced the 16 tier-2 copy headers to v1.4.237 (no source moved since v1.4.232). Re-baselined AGENTS.md to +146/-1 after moving the fork's sections onto upstream's restructured file.
…orBuffers field v1.4.237 made editorBuffers a required field of WebSessionTabsSyncState (phase 1a editor buffer records), so the two fork fixtures built from a literal stopped typechecking. An empty record is the state a session with no open editor buffers carries.
…upstream's repo-host-read ratchet v1.4.237 (stablyai#31096) added check-repo-host-read-ratchet, which counts raw repo.connectionId host checks and allows none in new files. fork-loops' SSH check and the project ledger's host resolution read the raw field. Both now use getRepoRoutingConnectionId, upstream's drop-in for the same check, which also finds a row that names its SSH host only as executionHostId 'ssh:<target>' (the loops check already special-cased it).
… upstream's host-list ratchet v1.4.237 (stablyai#31426) added check-host-list-render-ratchet, which flags JSX mapped over any collection whose name contains host/target/ssh/machine, because an SSH host list can hold 10k+ rows. The handoff dialog's `targets` is a list of worktrees, not hosts, so the match was a false positive on the name. Renamed it to `destinations` (the type was already HandoffDestinationOption); no behaviour changes.
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
…e fork's one mac channel v1.4.237's new sign-mac-app.test.mjs asserts that the hourly, daily and adhoc mac channels sign with sign-mac-app.mjs. The fork's builder config (a permanent exception) ships only ORCA_MAC_RELEASE; upstream's other channels build from workflows gated to stablyai/orca. The release channel keeps every upstream assertion and the other channels are pinned unsigned. The fork exception for this test is declared in the manifest commit.
… background Claude runs v1.4.237 (stablyai#30939) adds --strict-mcp-config and --settings {"disableAllHooks":true} to background Claude invocations, so the commit-message plan now carries them before the recipe's own flags. The fork behaviour under test, recipe arguments replacing generated model and effort flags, is unchanged.
…rminal create
v1.4.237 moved web launches to createWebRuntimeSessionTerminalResult,
which takes no { notifyFailure } option, so the fork's capability-gating
test no longer expects a second argument.
v1.4.237's agent catalog builds through createLocalizedCatalog, which
reads i18n.language, so the dock test's partial mock failed the whole
suite at import. Mirrors upstream's own mocks ({ language: 'en' }).
…b-runtime launch test
sign-mac-app.test.mjs becomes a permanent exception (fork's single mac
channel). launch-agent-in-new-tab-web-runtime.test.ts drops to +27/-0:
its removed line was the { notifyFailure } argument upstream's API no
longer takes.
Upstream deleted CLAUDE.md (stablyai#31242). Without its one line (@AGENTS.md), Claude Code sessions in fork worktrees stop loading AGENTS.md, which carries the fork's ledger, ownership-manifest and sandbox-test rules.
zpyoung
deleted the
zpyoung/auto-sync-fork-with-upstream-stablyai-orca-ru-20261021T1400
branch
October 22, 2026 11:50
1 of 2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ELI5
This brings the fork up to upstream's newest stable release, v1.4.237 (from v1.4.233). Upstream restructured a few files the fork hooks into (its agent instructions, the runtime capability list, the CLI typecheck config) and added two new code checks, so the fork's hooks were moved onto the new shapes and three fork files were adjusted to satisfy the checks. One upstream feature, Codex skill picks carrying the skill's file path, lands in a composer the fork replaces; that decision is with the owner (see the end).
What Changed
v1.4.237at071747853f.auto-ours+tree. 30 content conflicts resolved by-X ours, then 4 "deleted by us / modified by them" tree conflicts by honoring the fork's deletion (all four are declareddeleted: true), then everything by the manifest.origin/backup/main-20261021-140159Zatc9feb5cc41.Ownership classifier: 4,460 differing paths; 2,040 reset to the tag, 57 removed, 171 exceptions restored, 2,192 merge-review paths.
CLAUDE.md(chore: remove CLAUDE.md stablyai/orca#31242). The fork never declared it, so it goes; the owner has been asked whether to keep it as an exception (see the end).ours.txt): no stale exceptions, no module splits. 21 changed upstream and were three-way merged against v1.4.233; 15 merged clean. The 6 conflicts:package.json: the fork's version line. Upstream's new deps (@electron/osx-sign,tsrs, zod 4.6.7) and new lint steps merged clean.pr.yml: both hunks sit in regions the fork's trimmed workflow removed (upstream's ratchet consolidation and Windows boundary list), so the fork side wins; the file is byte-identical tomain's. The new ratchets still run throughpnpm lint.ci-dependency-download-cache.test.mjs: the fork's "no Windows lane" assertion stays; upstream's now-unusedWINDOWS_BOUNDARY_TESTSimport is dropped.NativeChatComposer.tsx: upstream disables attach while a remote chat session is pending (fix(native-chat): open a New Chat's composer at once, before its host answers stablyai/orca#29358); replayed onto the fork'sAgentComposerFieldafter the props spread.use-native-chat-paste-bridge.ts: upstream's new native-paste path for a given textarea (origin/mode) is combined with the fork's focus routing and image-only fallback.pane-pty-visibility-bind.ts: both imports kept (fork quarantine, upstream hook-lane completion).release-cut.yml/release-mac-build.ymltake upstream's newqualified-orcad-template.ymlcall andblob:nonecheckout;electron-builder.config.cjstakes the new batched mac signer (sign-mac-app.mjs, no identity in it).blacksmith-*runners, all behindfast_runners/fast_releaseinputs that default to false or in upstream-only adhoc/dev-channel workflows. The fork's release paths call them without opting in, so nothing to change.checkout.txt): none. Deleted-exception sweep: none still imported. Locales: all fork bundles have all 6 locales. Cross-version wire: no new bare upstream tag pins. Workflow-swap commit under the tag: not present (v1.4.237^is a release fix, not a workflow copy).Seams (
merge-review.txt). 82 seam files changed upstream and were re-merged without-X ours. 59 merged clean; 23 were resolved by hand:.gitignore,i18n.ts,runtime-rpc-lifecycle.ts,automation-definition-operations.ts,pty-exit-lifecycle.ts,apply-final-patch.ts,rpc/methods/index.ts,launch-agent-web-host-tab.ts,launch-agent-in-new-tab-web-runtime.test.ts,web-native-chat-api.ts,vitest-node-runtime-files.mjs(upstream's import scan replaces the deleted sqlite list), and the three store files (upstream'sStructuredChatSleptAtSlicebeside the fork's slices).main-process-observers.ts: upstream folded the stats recorder intofeedAgentSessionStats; the fork's transcript observer subscribes beside it.settings-page-renderer.tsx: upstream wrapped the sections in a scroll provider; the fork's handoff section moves inside it (seam line re-indented).native-chat-structured-composer-dispatch.ts: upstream's newskillsparameter after the fork's attachment type (seam line gains a trailing comma).native-chat-picker-items.ts: upstream's path-located skill rows (feat(native-chat): Codex chats list and run the skills Codex loaded stablyai/orca#30937) and the fork's plugin rows combined; the fork'sbyName/pickerSkillsreplace upstream'sdiscoveredmap. Residual unchanged at +76/-21.protocol-version.ts: upstream moved every capability into per-area modules (refactor(protocol): each area owns its published runtime capability list stablyai/orca#30641). Rebuilt from the tag plus the fork's registrations (ask, loops, heimdall, launch overrides,ledger.v1); +35/-0, as before.AGENTS.md: upstream rewrote it. Rebuilt from the tag plus the fork's sections (the sandbox Test bullet, Running Tests, Typechecking, the Orca ledger, Fork Feature Structure). Residual re-baselined +145/-1 to +146/-1 (one separator line).bundled-skill-guides.ts: regenerated.config/tsconfig.cli.json(upstream's CLI typecheck now follows imports fromsrc/cli(build(cli): typecheck the CLI by following its imports, not a hand list stablyai/orca#30632), which reaches the two session-info files throughclaude/hook-service.ts) andstructured-agent-session-options.ts(the fork's line fed an always-undefinedobservedAtto a function A chat's model, effort and Fast belong to the chat, not to a model stablyai/orca#30561 replaced, so it had no effect).Tier-2 copies: 16 checked. All were recorded at v1.4.232 and none of their sources changed through v1.4.237; headers advanced to
071747853f.Tier-4 pending-upstream: every item's fork delta is unchanged in size against the new tag, and every ledger target exists. Upstream absorbed none.
Feature-collision review: 1 confirmed, 4 possible, 28 none. Details are in the owner question at the end.
Fixes after the merge, one commit each:
4ffc356e9a: the terminal-dock sync-state test fixtures get upstream's new requirededitorBuffersfield.088302be74: fork-loops' SSH check and the project ledger's host resolution usegetRepoRoutingConnectionId, for upstream's new repo-host-read ratchet (feat(ci): ratchet raw repo connectionId host checks; migrate the cheap ones stablyai/orca#31096).9c07953a00: the handoff dialog's worktree list is renamedtargetstodestinations. Upstream's new host-list ratchet (fix(ssh): draw every host list through one bounded, searchable host picker, so a 13k-host ssh config can't crash any of them stablyai/orca#31426) flags any*target*collection mapped to JSX; this one holds worktrees, not hosts.Why
Merging the stable tag keeps every fork SHA permanent. Replaying fork hooks onto upstream's new file shapes keeps the fork's footprint in upstream files small, where defending the old shapes would make the next sync conflict again.
Linked Issue
N/A. This is an upstream sync.
Visual Proof
N/A. No fork UI changes; upstream's own UI changes ship as released.
Testing
Local gate on
9c07953a00: frozen install;--verify-seams(1,327 lines across 656 files) and--verify-residuals(571 files) pass; typechecknode,web,cliandprocess-hosttests run one at a time, clean;pnpm lintpasses; clean tree;check-pr-verify-selections, the root-directory guard and the fork ownership guard pass; the preflight replay passes every step it can run. Tests are left to this PR's CI.Fixes from the first CI run (head
9c07953a00)Three unit shards failed deterministically; each fix is its own commit, and all four files pass on the test sandbox.
2f836623fd: v1.4.237's newsign-mac-app.test.mjsasserts the hourly, daily and adhoc mac channels sign. The fork's builder config ships onlyORCA_MAC_RELEASE, so the test is a permanent exception that keeps every assertion for that channel and pins the rest unsigned (the same shape asrelease-windows-signing-gate.test.mjsin sync: absorb upstream v1.4.233 #129).6ce5d79ff3: the fork's commit-message plan test expects upstream's new--strict-mcp-config --settings {"disableAllHooks":true}on background Claude runs (Background Claude runs no longer start your MCP servers and hooks stablyai/orca#30939).1353299e61: the fork's capability-gating test matches upstream's one-argumentcreateWebRuntimeSessionTerminalResult. The seam budget drops to +27/-0 (703ea291a3).c30eddd764: the dock test's@/i18n/i18nmock gainsi18n: { language: 'en' }, which upstream's newcreateLocalizedCatalogreads at import.persistence (macos-15-intel)is advisory. It failed on9c07953a00with a network connect timeout and passed ona206bd4462with no change.a206bd4462:CLAUDE.mdrestored and declared a permanent fork exception (see Decisions).CI on the merged head (
a206bd4462)PR checks passedis green. Advisory checks still open at merge:WSL2 distro (debian-12)pending (WSL orcad E2E).desktop_templateskipped.Decisions
The owner's rule: keep every intentional fork feature, and check in only before dropping one. None is dropped.
$nametext only. Deferred to a follow-up PR (ledgerdeferred-326) so this sync can land; no fork behaviour is lost.a206bd4462). Upstream deleted it (chore: remove CLAUDE.md stablyai/orca#31242), and without its@AGENTS.mdline Claude Code stops loading the fork's agent rules.