* refactor(shared): move the pane title sanitizer to shared, unchanged
So main's layout Loader can apply the same rule the window applies on load.
* fix(layout): pane titles stay layout in a layout-owner build (dark)
The window stops writing a revealed terminal's tab name as its pane title,
and main's Loader drops a lone pane title that only repeats the tab's name,
as the window's load does. Both behind ORCA_LAYOUT_OWNER_IS_MAIN.
* fix(layout): the Loader compares a lone pane title with the tab bar's quick command label too (dark)
As the window's row hydration does when the row has no label.
* fix(layout): the Loader compares a lone pane title with the tab bar label, the live title (dark)
* fix(ssh): a tab closed while a managed host is disconnected stays closed after Connect
#30130 made a user's Disconnect mark the host's managed server manually
disconnected, so the desktop now answers its calls itself with
runtime_manually_disconnected. The tab close path read that as the host's
answer and dropped the close instead of holding it for replay, so the tab
came back on reconnect. Treat it as an undelivered close, and route the
Disconnect sources to the managed-orcad Docker lane.
* test(e2e): close the disconnected host's tab the way a user does
The journey called the store's bare closeTab, which sends only terminal.close
and holds nothing; it passed only because the disconnected host used to be
redialed in the background, which #30130 stopped. Click the tab's X instead,
the path that holds and replays an undelivered close.
* test(e2e): the shared-home spec tells a host's own server from a PID that only collides
Each Docker host has its own PID namespace, so the PID host A records in the
login-home slot can belong to an unrelated live process on host B; kill -0
alone then read it as B running from A's slot. Count a recorded PID only when
its process runs from that slot.
* fix(serve): orca serve on orcad serves the browser client and answers plain HTTP
orcad, the default local orca serve host since the compatibility launcher,
was never handed the app's web client directory, so its HTTP server had no
request listener: every GET hung and no Web client URL was printed (#30100).
The CLI now passes the app's unpacked out/web to orcad via --web-client-root,
a server without a web client answers plain HTTP with 404 instead of hanging,
and packaging fails when the unpacked web client or a file it loads is missing.
* test(serve): pin orca serve handing orcad the app's web client root
* feat(mobile): name the desktop under the Tasks title
Tasks can open on any connected desktop, so the header now shows which
one, in the same subtitle line the host screen uses under its title.
* fix(mobile): reserve the Tasks desktop line so the title does not jump
The subtitle line now holds its height before the host profile is read,
a failed read leaves it blank, and the tests cover a desktop switch
while a read is in flight.
* test(mobile): re-measure the page script sweep after the Tasks subtitle
The Tasks route now shares the host screen's styles, so it emits one
script fewer (77 to 76 at its prefix, 80 to 79 for the tree). Re-swept
every route prefix; the worst route step is now 11.
* fix(native-chat): a Codex chat is Working only while Codex holds the send
Working read 'a send still pending delivery', so any echo or idle signal
that never arrived (a hook-blocked prompt, an answer naming no turn, idle
before a timed-out send was written) kept the chat Working, Stop up, the
queue frozen and /compact refused until the agent exited.
Codex now reports which sends it still holds: unanswered, answered into a
turn that has not ended, or answered into none while a status read after
that answer says the thread runs. Replies and statuses are ordered as they
are read, so a stale status cannot release a newer send. The host reads
that set (plus the short window while it hands a send over) for Working,
publishes a send the child stopped holding as in doubt on every page and
frame so older clients agree, and republishes when the set changes with no
row. The Codex idle latch and its release are removed.
* fix(native-chat): name the handover journal type; reverse without mutating
* test(native-chat): a lost Codex answer while idle is no longer the chat's work; run the held-sends test with the database runtime
* fix(native-chat): bound work updates and own command handovers
* refactor(native-chat): reuse session delivery plumbing
* test(native-chat): allow separate ownership restatements
* test(wire): resolve the release tab predicate after relocation
* test(native-chat): reuse existing promise and empty-batch primitives
* fix(mobile): wall hosts that cannot serve the page instead of opening native screens
* refactor(mobile): drop the native-route state and the shell fallback prop; unknown routes say unavailable
* test(mobile): read the nullable page route refusal in its policy test
* refactor(mobile): every build is the OTA shell; drop the native/OTA build switch
The owner retired the native-screen fallback with no emergency native build, so
EXPO_PUBLIC_MOBILE_SHELL, mobileShellBuildKind, shellSwitchDecision, the Metro
cache-key suffix and both release workflows' shell input go. Every host route
mounts the shell; a route naming no screen the shell could open gets the
existing refusal screen instead of a native screen.
Claude-Session: https://claude.ai/code/session_6cea92f6
* refactor(mobile): delete native screens the OTA shell no longer reaches
With every build the OTA shell, these native siblings run nowhere: the
native-executed closure (index.js plus every non-web route) does not reach
them, and the page resolves their .web siblings. Each .web sibling takes the
plain name, its web-overrides entry goes, and the native-only rich Markdown
editor and Mermaid WebView generators and their outputs go with them.
Kept: native siblings the node test suite still renders shared screens
through (terminal WebView, browser frame path, clipboard, dictation, media
picker, live input, snapshot budget, input styles).
Claude-Session: https://claude.ai/code/session_6cea92f6
* test(mobile): drop the phone-preview comparison the deleted native sibling held
Claude-Session: https://claude.ai/code/session_6cea92f6
* refactor(mobile): drop the dead web route, flag-era comments and the one-value shell kind
The host route files no longer switch, so their comments say what they do now:
a route naming no screen shows the unavailable screen. app/h/[hostId]/web.tsx
duplicated index.tsx and nothing linked to it, so it and its stack entry go
(the bundle sweep is re-measured: 77 scripts at 15 routes). Diagnostics print
the bridge protocol on its own instead of a shell kind that has one value.
Claude-Session: https://claude.ai/code/session_6cea92f6
* refactor(mobile): one host for the rich editor, plain Text, and drop two unused native deps
MobileSelectableText was react-native's Text, so callers use Text. The rich
Markdown editor's host-facing seams (messages, URL prompt, keyboard inset) are
required now that the page is its only host; the WebView defaults go. Comments
that described deleted native components are rewritten, and
expo-image-manipulator and react-native-uitextview leave package.json and the
lockfile.
Claude-Session: https://claude.ai/code/session_6cea92f6
* feat(mobile): draw the host Accounts screen from the desktop's page
Accounts was the last host screen the shell still drew natively, so its
changes needed a phone build. It is now a page route like Tasks: the
screen moves to src/accounts/MobileAccountsScreen.tsx, accounts.web.tsx
renders it on the page, and accounts.tsx mounts the shell for it.
What React Native Web needed: Alert.alert is a no-op there, so the
reset confirmation, its outcome and a failed account switch are
ConfirmModal drawers (ConfirmModal can now drop Cancel for a notice);
Back goes through the route handoff, which pops the shell's stack; and
the reset idempotency key is built from getRandomBytes, since
randomUUID needs a secure context the iOS custom scheme is not.
edit.tsx edits the phone's own pairing and never renders on the page,
so the route manifest leaves it out and the page's host stack does not
declare it. The script sweep is re-measured for the 14-route tree.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(accounts): answer a same-offer Codex reset retry with its recorded outcome; mobile mirrors the desktop
The desktop keeps no client journal for a reset credit: the host's ledger
owns the attempt. A remote client that lost a reply and retried the same
offer under a new key was refused with "already attempted" (or "unknown
outcome" while the provider call was unresolved), which is why the phone
carried its own durable journal to replay the old key.
The coordinator now answers a request for an offer an earlier key already
claimed as that attempt: the settled outcome, the running attempt, or a
resume of the pending one under its original key. A different offer for
the account is still refused while an outcome is unknown, so nothing can
spend twice. The reply shape is the one a same-key replay always had, so
installed clients with their journal read it unchanged.
With that, the Accounts page sends a fresh key per attempt like the
desktop and keeps nothing on the phone: the AsyncStorage journal, its
client singleflight and the retry-record warnings are deleted, and the
journal-resume recording goes with them.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(accounts): resume an account's unresolved Codex reset when the retry names a newer offer
Review follow-ups:
- Host: after an unknown provider outcome the usage refresh moves the
offer, so a retry names a newer one. The coordinator now answers it as
the account's unresolved attempt (resumed under its own key and scope),
as the desktop's consumeCurrent does, instead of refusing forever. One
ledger lookup finds own key, offer claimant, then unresolved attempt.
- Mobile: a reply is matched on target, account and account revision,
not the offer, so the resumed attempt's scope is accepted.
- The key is minted inside requestCodexResetCredit from randomV4Uuid, the
session helper moved to src/platform/random-v4-uuid.ts.
- RefreshControl goes; the header refresh stays. ConfirmModal's
onConfirm is optional for an OK-only notice.
- Stale journal comments and the mobile docs line are corrected.
- The edit.tsx page exclusion is dropped from this PR; the script sweep
is re-measured for 15 routes.
- Root oxlint: escape the apostrophe; zod v4 strictObject.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): secure UUID fallback from expo-crypto; pin reset retry routing
- randomV4Uuid never falls back to Math.random: without crypto.randomUUID
it builds the v4 UUID from ExpoCrypto.getRandomBytes, as the old reset
key did. Tests get Node's secure bytes for expo-crypto from the setup
file, since the native module has no Node build.
- Host tests: a new offer after a settled attempt spends again rather
than replaying, and a retry naming a newer offer joins a resumed
attempt in flight (one provider call, original key).
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* chore: restore main's bytes for files the merge's pre-commit format touched
The merge commit's lint-staged run reformatted 27 files this PR does not
change. Committed without the hook so they match origin/main exactly.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): stand in for expo-crypto with a Node alias, not a setup-file mock
Anti-slop forbids module mocking outside test files. The vitest config
now aliases expo-crypto to a faithful Node implementation of the names
shared code reaches; a test file's own mock still wins.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* docs(mobile): match the bridged-parity comments to the measured 795-golden tally
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): wall hosts that cannot serve the page instead of opening native screens
* refactor(mobile): drop the native-route state and the shell fallback prop; unknown routes say unavailable
* test(mobile): read the nullable page route refusal in its policy test
* refactor(mobile): every build is the OTA shell; drop the native/OTA build switch
The owner retired the native-screen fallback with no emergency native build, so
EXPO_PUBLIC_MOBILE_SHELL, mobileShellBuildKind, shellSwitchDecision, the Metro
cache-key suffix and both release workflows' shell input go. Every host route
mounts the shell; a route naming no screen the shell could open gets the
existing refusal screen instead of a native screen.
Claude-Session: https://claude.ai/code/session_6cea92f6
* refactor(mobile): delete native screens the OTA shell no longer reaches
With every build the OTA shell, these native siblings run nowhere: the
native-executed closure (index.js plus every non-web route) does not reach
them, and the page resolves their .web siblings. Each .web sibling takes the
plain name, its web-overrides entry goes, and the native-only rich Markdown
editor and Mermaid WebView generators and their outputs go with them.
Kept: native siblings the node test suite still renders shared screens
through (terminal WebView, browser frame path, clipboard, dictation, media
picker, live input, snapshot budget, input styles).
Claude-Session: https://claude.ai/code/session_6cea92f6
* test(mobile): drop the phone-preview comparison the deleted native sibling held
Claude-Session: https://claude.ai/code/session_6cea92f6
* refactor(mobile): drop the dead web route, flag-era comments and the one-value shell kind
The host route files no longer switch, so their comments say what they do now:
a route naming no screen shows the unavailable screen. app/h/[hostId]/web.tsx
duplicated index.tsx and nothing linked to it, so it and its stack entry go
(the bundle sweep is re-measured: 77 scripts at 15 routes). Diagnostics print
the bridge protocol on its own instead of a shell kind that has one value.
Claude-Session: https://claude.ai/code/session_6cea92f6
* refactor(mobile): one host for the rich editor, plain Text, and drop two unused native deps
MobileSelectableText was react-native's Text, so callers use Text. The rich
Markdown editor's host-facing seams (messages, URL prompt, keyboard inset) are
required now that the page is its only host; the WebView defaults go. Comments
that described deleted native components are rewritten, and
expo-image-manipulator and react-native-uitextview leave package.json and the
lockfile.
Claude-Session: https://claude.ai/code/session_6cea92f6
* fix(mobile): wall hosts that cannot serve the page instead of opening native screens
* refactor(mobile): drop the native-route state and the shell fallback prop; unknown routes say unavailable
* test(mobile): read the nullable page route refusal in its policy test
* refactor(mobile): every build is the OTA shell; drop the native/OTA build switch
The owner retired the native-screen fallback with no emergency native build, so
EXPO_PUBLIC_MOBILE_SHELL, mobileShellBuildKind, shellSwitchDecision, the Metro
cache-key suffix and both release workflows' shell input go. Every host route
mounts the shell; a route naming no screen the shell could open gets the
existing refusal screen instead of a native screen.
Claude-Session: https://claude.ai/code/session_6cea92f6
* refactor(mobile): drop the dead web route, flag-era comments and the one-value shell kind
The host route files no longer switch, so their comments say what they do now:
a route naming no screen shows the unavailable screen. app/h/[hostId]/web.tsx
duplicated index.tsx and nothing linked to it, so it and its stack entry go
(the bundle sweep is re-measured: 77 scripts at 15 routes). Diagnostics print
the bridge protocol on its own instead of a shell kind that has one value.
Claude-Session: https://claude.ai/code/session_6cea92f6
* fix(mobile): wall hosts that cannot serve the page instead of opening native screens
* refactor(mobile): drop the native-route state and the shell fallback prop; unknown routes say unavailable
* test(mobile): read the nullable page route refusal in its policy test
* chore(ci): ratchet renderer importers of the deprecated runtime-owner resolvers
getRuntimeEnvironmentIdForWorktree and resolveRuntimeOwnerForWorktree read a
worktree two hosts publish as not-a-runtime, which callers treat as local. The
62 renderer files importing them are grandfathered; the list may only shrink.
* ci: register the deprecated runtime-owner ratchet step in the preflight phase map
* fix(ssh): keep existing hosts' orca CLI control switch off after the on-by-default flip
Builds before the flip saved an explicit off as an absent value, so absent=on
silently turned on hosts the user had turned off. On the first load after this
upgrade, pin every saved host's absent switch to an explicit false and record a
one-shot marker in settings so hosts added later keep the new default.
* test(ssh): check the saved profile without a type assertion
* fix(ssh): say the CLI control switch is on by default for new hosts at the call site too
* feat(orcad): ship the mobile web page so phones paired to orcad get it
orcad never carried out/mobile-web, so it did not advertise
mobileWeb.bundle.v1 and phones paired directly to an orcad server fell
back to the app's built-in native screens.
The built page now ships in every orcad package at out/mobile-web, the
path getBundledMobileWebBundleRoot already probes under the install
root. Its manifest is a fixed artifact; its content-addressed assets
join the hashed artifact list from that manifest, as one shared copy in
the template's common files. build-orcad fails when the page is missing
or stale, and CI lanes that build orcad now build the page first.
* ci(orcad): check out mobile/ for the Windows host template build
* refactor(orcad): copy the page tree whole and build it once per CI run
The guard already refuses unlisted files, so staging copies the
bundle directory as native-chat-visuals is copied. The shipped template
takes the page the qualification lanes tested, the e2e build artifact
carries it, and the PR smoke builds it through install-mobile-dependencies
behind its own scope step. Drops tests that duplicated the guard's own.
* fix(orcad): refuse a template page asset name that escapes the slot
* feat(mobile): Resume in New CLI from a phone chat tab
A chat tab on the phone had only Close in its long-press menu, so a chat
that would not load left no way to reach its conversation. The phone now
offers Resume in New CLI, like the desktop tab menu: the chat's host
prepares the conversation and the phone opens a copy in a new terminal tab.
The resume and fork startup builders move to src/shared so the desktop and
the phone build the same command.
* test(mobile): type the fake terminal create instead of casting its calls
* fix(mobile): pass the host's fork source as is; match the quoted args in the test
* test(mobile): give the fake host client the untyped mock the other client fakes use
* style: format the shared resume builder and phone fork files
* Fix mobile chat CLI fork creation on owning host
* Fix mobile chat fork lint and split create owner helpers
* Keep chat fork beside phone anchor across tab groups
* Combine mobile terminal runtime type imports
* Repair mobile create CI contracts after chat fork extraction
* Classify chat fork preflight refusals and preserve mobile create recordings
* Keep route-owned create cleanup quiet after unmount
* Preserve typed runtime repair refusal in chat fork preflight
* Preserve checked store through mobile chat fork preflight
* Register mobile chat fork runtime fixture before test setup
* Remove unused mobile create recording value
* style(mobile): format the chat fork create files; move fork reply messages into the fork feedback module
Formatting pushed the create hook past its line limit, so the fork-only reply message, partial-success warning and host capability check now live beside the other fork messages. Same order and copy as before.
* Keep quick command mutation aligned with formatted request
* Keep mobile chat CLI forks in terminal view
* Pass mobile chat resolution its existing controller arguments
* Match the mobile create fixture mock layout
* Keep mobile chat CLI forks open when placement is unavailable
* Brace placement contract fixture checks
* test(mobile): keep the prompt controller mock under its line limit after the main merge
* Expose pane admission in the runtime test fixture type
* Complete the chat fork contract controller fixture
* Persist session parse results beyond the memory cache limit
* Expire Session History parse rows by idle time instead of a count cap
The parse cache dropped its oldest row once it held 4,096, so a scan over
more transcripts evicted its own upcoming hits and re-parsed every file on
every scan. Rows now stay until no reader has used them for 24 hours, swept
after each completed History scan and each search-index pass. Only the live
parser state keeps a 4,096 count bound; dropping it costs one whole re-read
of a file that changed, never a re-parse of one that did not.
Removes the overflow directory and the orcad persistence wiring from the
earlier revision of this change.
* Expire only rows History lists, and keep the newest transcripts under bounds
The search pass ran the idle sweep every 20 s but never re-stores History's
unchanged rows, so with search on a day away from History dropped them all
and reopening it re-parsed everything. Only a completed History scan sweeps
now, and it expires only rows History listed (on a monotonic clock). Rows
only the search index or a title lookup created keep main's 4,096 count
bound, so an index build can't grow the scanner process without limit.
Scans store newest transcripts first, so every store-order bound dropped the
newest, most active files: the saved snapshot kept the oldest ~11k rows and
the resume bound stripped the newest files' parser state. The snapshot is now
ordered by transcript mtime, and both count bounds drop the tenth of their
rows with the oldest transcripts.
* Drop the 24 h idle sweep; History rows live for the process
The sweep let whichever listing ran last decide when a row died, and
listings differ in scope: a smaller one (the phone's Agent History, a lower
desktop limit) expired rows only a larger one used, so the next Unlimited
scan re-parsed them. Rows History lists now live as long as the scanner
process; the snapshot's newest-transcripts cap bounds what survives a
reload, and rows other readers made keep the 4,096 bound.
Also clears the resume bookkeeping for a row the unlisted bound drops in the
same store.
* Skip resume bookkeeping for a row the unlisted bound just dropped
Running the resume trim first let a row the unlisted trim then deleted push
the resume set over its bound and cost 409 live resume points.
* perf(ai-vault): store the Session History parse cache one row per transcript
The parse cache was persisted as one JSON file, rewritten whole after any
change and capped at 64 MB / 1M tokens (about 11k rows). Past that cap, a
restart re-read the oldest unchanged transcripts, and an agent writing while
History was open rewrote 10-27 MB every few seconds.
The scanner child now keeps the rows in their own SQLite file. A miss in the
in-memory map reads the row by path; changes are written per row, debounced
and chunked; deletes run immediately. A used_at column, refreshed at most
daily, expires rows nothing has used for 30 days, so rows for transcripts
deleted outside Orca have a way to die. The old JSON file is imported once
and removed. Electron main only records the path for the child.
The rebuild-on-corruption open moves out of the search index into a shared
versioned cache database helper used by both.
* fix(ai-vault): import the old parse cache at open and expire rows only for missing transcripts
Review round 1 on the per-row parse cache store:
- The old JSON was imported only by a History scan or search pass, and only
into an empty database, but deleted either way. Tab-title sync writes a row
first, so the import was skipped and the first History open after updating
re-read everything. The import now runs when the database opens, before any
lookup, write or delete can reach it, keeping rows already present.
- A 30-day last-use expiry also dropped rows for transcripts that still exist,
so opening History less than monthly re-read them. Rows unparsed for 30 days
are now checked on disk and dropped only when the transcript is gone, then
the freed pages are reclaimed. The daily last-use rewrite goes away.
- 8 KB pages (a ~2.3 KB row filled a 4 KB page), busy_timeout before the WAL
switch, and lookups served from rows still waiting to be written.
- Revert accidental pnpm-lock.yaml churn.
* chore: revert accidental pnpm-lock.yaml churn
* fix(ai-vault): never stat WSL transcripts from the parse cache recheck
A stat on a \\wsl.localhost path outside the scanner's running-distro
filter and gated WSL lane could boot a stopped distro or hang on a stuck
one. WSL rows now skip the existence check and age out after 180 days
unparsed. SQLite-backed session keys use the shared OpenCode path splitter.
* fix(ai-vault): register the parse cache database with the WSL mover census
Each schema keeps its own meta table again, so the shared open helper
creates none and the search schema's SQL is unchanged. The parse cache's
tables are recorded as not moved: a cache rebuilt from transcripts.
* fix(ai-vault): a forced whole read never reuses a row a failed database delete left behind
* fix(ssh): allow a host's orca CLI to control Orca by default, and stop resetting saved choices
An absent allowRemoteCliControl now means on; only an explicit false turns the
desktop browser bridge and relay-era orchestration commands off. Removes the
one-time reset of saved true values, the consent stamp writes and the reset
notice, which would have silently turned off hosts upgraded from stable.
* fix(ssh): keep the CLI control choice when adding a host from the sidebar
* fix(ssh): read an older client's omitted CLI control switch as off over RPC
* fix(ssh): check the managed fence before routing settings in browser bridge candidates
* Persist session parse results beyond the memory cache limit
* Expire Session History parse rows by idle time instead of a count cap
The parse cache dropped its oldest row once it held 4,096, so a scan over
more transcripts evicted its own upcoming hits and re-parsed every file on
every scan. Rows now stay until no reader has used them for 24 hours, swept
after each completed History scan and each search-index pass. Only the live
parser state keeps a 4,096 count bound; dropping it costs one whole re-read
of a file that changed, never a re-parse of one that did not.
Removes the overflow directory and the orcad persistence wiring from the
earlier revision of this change.
* Expire only rows History lists, and keep the newest transcripts under bounds
The search pass ran the idle sweep every 20 s but never re-stores History's
unchanged rows, so with search on a day away from History dropped them all
and reopening it re-parsed everything. Only a completed History scan sweeps
now, and it expires only rows History listed (on a monotonic clock). Rows
only the search index or a title lookup created keep main's 4,096 count
bound, so an index build can't grow the scanner process without limit.
Scans store newest transcripts first, so every store-order bound dropped the
newest, most active files: the saved snapshot kept the oldest ~11k rows and
the resume bound stripped the newest files' parser state. The snapshot is now
ordered by transcript mtime, and both count bounds drop the tenth of their
rows with the oldest transcripts.
* Drop the 24 h idle sweep; History rows live for the process
The sweep let whichever listing ran last decide when a row died, and
listings differ in scope: a smaller one (the phone's Agent History, a lower
desktop limit) expired rows only a larger one used, so the next Unlimited
scan re-parsed them. Rows History lists now live as long as the scanner
process; the snapshot's newest-transcripts cap bounds what survives a
reload, and rows other readers made keep the 4,096 bound.
Also clears the resume bookkeeping for a row the unlisted bound drops in the
same store.
* Skip resume bookkeeping for a row the unlisted bound just dropped
Running the resume trim first let a row the unlisted trim then deleted push
the resume set over its bound and cost 409 live resume points.
* perf(dev): keep pairing web bundle after test file changes
* fix(dev): detect web input replacement within one timestamp tick
* test(dev): use real files for web replacement coverage
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
- Specs with their own CI jobs were also being assigned to general E2E shards, so they could run twice or in the wrong job.
- planE2e now drops dedicated specs before balancing and fails if no general specs remain, instead of producing an empty plan.
- The mobile subscribe spec builds a paired runtime connection from the pairing offer before sending requests, matching the updated remote runtime helper signatures.
* Finish structured agent readiness before delivering held work
* Adapt permission fixtures to startup lifecycle
* Keep model discovery for chats before their first start
* Use one host readiness promise for provider operations
* Await provider opening in the execution host
* Await Claude initialization in host-owned open
* Observe Claude readiness through the host status
* Keep optional readback behind host readiness
* Recheck launch permission after host readiness
* Preserve the awaited child during permission revalidation
* Record failed owned opening before the first message
* Abort a matching provider open when its child exits
* Cover provider exit during an unanswered host open
* Cover explicit failure facts on a host-stopped child
* Keep startup waits cancellable and preserve operation order
* Keep live choices and delivery order while agents start
* Apply startup picks and let parked messages release operations [skip ci]
* Save a waiting startup pick after Stop ends its opening [skip ci]
* Save choices when startup ends or a replacement is still opening
* Check saved choices separately from Claude startup reports
* Align startup fixtures and preserve observed Claude exits
* Order startup picks before later messages and retain flow regressions
* Keep reviewed lifecycle contracts while pruning duplicate tests
* Save picks after a proven exit without requiring a child
* Type the startup reply fixture against the Claude control contract
* Honor the before-input callback in the startup reply fixture
* Keep startup option menus independent and show the saved choice
* Exercise startup picks across real menus and the runtime
* Keep saved effort visible when picking a model during startup
* Include the running-turn scope in repeated Stop fixture
* Use the current attach contract in import fixtures
* Align carried-message fixture with startup event contract
* Keep launch-only permission picks usable while prior writes wait
* ci: rerun on main with the locale fix
* Preserve goal retries and chat launch selections
* Give launch regression its request identity
* test(native-chat): type the provisional-launch hook's session prop
* ci: build the WSL server template once
* ci: reuse validated Windows native addons in E2E preparation
* ci: avoid the unused standalone server build in SSH E2E shards
* Refuse a chat create whose close came first, behind the layout-owner switch
With ORCA_LAYOUT_OWNER=main, main remembers a close that reaches it before
the chat's create and refuses that create, or closes the chat as it lands.
The window then tells this machine's host about a cancel instead of
keeping a cancel tombstone; a host it cannot tell still gets the
tombstone. Paired hosts keep the tombstones, whose fence module moves to
lib/launch-cancel-tombstones-compat/.
* Close a chat whose create a cancel beat, on any create outcome
The local cancel now retires the chat on main (close and host-tab close),
so a create that landed first leaves no tab behind; a tab_not_found answer
means the create had not landed. Main closes the chat for any create
answer once a close was noted, including a refusal or a throw past commit,
and publishes no tab for it. The tombstone fallback notifies listeners.
* Keep the switched-off create path byte-identical
* Log, not throw, a failed close of a chat closed before create
* Run the close-before-create test in the Node SQLite project
* ci: remove Windows build and persistence test overhead
* ci: collect unit phase diagnostics during PR checks
* test(ci): seed hook reuse fixture from the canonical prebuild
The hook reuse regression first built into a missing private --output, so it
always reached Cargo (74s warm on Windows). Build through the default output
instead, which reuses a warm canonical prebuild, then copy its executable and
fingerprint into the private fixture root.
* fix(lint): clear inherited max-lines and broad object tab parameter after main merge
Combine the duplicate shared/execution-host imports in runtime-host-services
(301 -> 300 lines) and type the overlap test's tab mapper with the
RuntimeMobileSessionClientTab owner type instead of object.
* fix(ci): align inherited runtime and workflow contracts
* test: establish changed runtime probe signatures deterministically
* test: await asynchronous daemon profile cleanup
* test: shut down owned daemons before deleting profiles
* test: clean up oracle daemons during final app close
* test: recognize verified Linux zombie exit during daemon teardown
* fix: preserve folder SSH ownership when project groups conflict
* perf(build): remove unnecessary development and config work
* feat(dev): reclaim inactive worktree Vite caches
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* perf: allow opting out of main source maps in development
* Use the session tab type in the inherited terminal regression test
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(ssh): an opted-in desktop serves its browser to a managed SSH host's orca CLI again
* fix(ssh): scope bridged pages through the renderer, render them as desktop pages, and route the e2e
* fix(ssh): bridge refuses file URLs and clipboard reads, revokes on disconnect, closes desktop tabs
* fix(ssh): judge bridged paste chords as the page sees them; accept long workspace ids
* fix(ssh): bridged clipboard denial follows popups; revoked commands never leave the browser queue
* fix(ssh): refuse option-like host values bound for agent-browser argv; report undeliverable results
* fix(ssh): bound bridged error results and fall back when the host refuses one
* fix(ssh): bridge only routed host tabs, and bound bridged page scripts by a deadline and revocation
* fix(ssh): cap waiting bridged commands below the desktop socket's result capacity
* fix(ssh): bridge revokes on routing changes, checks each page's route at run time, and cancels host-driven downloads
* fix(ssh): name the bridge registry's owner type
* fix(ssh): bridge refuses pages showing a local document, and never starts a command past orcad's deadline
* fix(ssh): bridged commands may leave Chromium error pages, and stop starting before orcad's deadline
* fix(ssh): ask for a fresh opt-in to host CLI control saved before it drove the desktop browser
* test(ssh): type the bridge registry deadline test
* [CI] Keep transport-only tests outside the app store graph
* [CI] Advance the updater install clock in failure tests
* [CI] Avoid descriptor formatting in replay clip counts
* [CI] Advance native-chat test observation clocks
* [CI] Restore observation timers after failed test setup
* [CI] Clean up final headless daemons during public close
* [CI] Keep serializer test oracle out of production smoke triggers
* [CI] Keep three unit fixtures out of production smoke triggers
* [CI] Bound frozen-clock close callbacks in tests
* [CI] Avoid the app-store graph in feedback dialog tests
* [CI] Keep hidden popup fixture compositor frames active
* test(ci): reuse mobile runtime fixture for pairing listeners
* test: restore native chat clocks before rig cleanup
The execution host upgrades a remote worker's fleet/show verdict to exited
only on its own proof: the hook store retains a proven agent exit (fenced to
the attachment, cleared by a replacement agent), and after a reboot the
process table proves the PTY gone. Lost contact stays unverifiable.
Fixes#24353
Co-authored-by: Claude <noreply@anthropic.com>
linear-sdk.ts loads the SDK through createRequire, which the orcad bundle
cannot follow, and nothing staged the package beside it. Bundle the SDK
into node_modules/@linear/sdk/index.js at build time and list it as an
orcad artifact so templates and installs carry it.
Co-authored-by: thukabjj <31043997+thukabjj@users.noreply.github.com>