mirror of
https://github.com/stablyai/orca.git
synced 2026-11-03 10:37:35 +00:00
bd54fc7fa32c196e3566fabcee8da330e40e96f3
2860
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bd54fc7fa3 |
refactor(renderer): delete the direct-SSH authority and reconnect coordinator (#27587)
No ssh: git provider is registered in production, so the reconnect coordinator's catalog and lineage reads always came back non-authoritative and its worktree refresh never merged. Its only live effect was clearing an unconverted target's stale PTY bindings on each connected push and on wake; the state bridge now does that directly. Deletes the coordinator, preparation, worktree refresh scheduler, host hydration, rollout flag and their product telemetry event, the scoped direct-SSH reconnect/hydration options, and the now caller-less repos:listForExecutionHost and worktrees:listLineageForHost IPC handlers. Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
fee083fa47 |
feat(runtime): session-only browser keys and opt-in for pairing from plain-http pages (host) (#27540)
* feat(runtime): session-only browser keys and an opt-in for pairing from plain-http pages (host) - pairing.registerDeviceKey accepts ephemeral: true (advertised as pairing.device-key-ephemeral.v1). Such keyed rows are dropped when the host restarts and after 12 hours with no live socket; a sweep removes them and cuts any socket still bound to them, so later key auth gets device_revoked. - The WebSocket upgrade Origin is recorded per socket. A first pairing (a never-used pairing code) from an http:// page on another machine is refused with a sealed insecure_origin error unless the operator sets ORCA_ALLOW_INSECURE_WEB_PAIRING=1. Already-paired browsers, https and loopback pages, and native clients are unchanged. - Registry file reading moved to device-registry-file-read.ts to keep the registry under the size cap. * fix(runtime): gate first pairings only for the marked web client, never phones React Native sends an http Origin on every LAN WebSocket, so the Origin alone refused first-time QR pairing from released phones. The web bundle now marks its upgrade with ?orca-client=web; only a marked socket from an http page on another machine is gated. Released hosts ignore the query string. --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
34e2e5f469 |
refactor(remote): clients connect through a paired-connection handle, not a pairing offer (#27533)
Desktop, CLI, web and runtime transports now take a PairedRuntimeConnection (endpoint, pinned host key, credential) instead of a PairingOffer. Offers stay on the pairing input path (codes, QR, links, environment add) and are converted with pairedRuntimeConnectionFromOffer. The credential is token-only today; request envelopes still carry the same deviceToken, so the wire is unchanged. This lets device-key and ticket credentials replace the token later without touching every connect call site again. Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
f0b310b1b1 |
refactor(store): delete direct-SSH terminal ledgers (#27569)
Panes stopped spawning on unconverted ssh: rows in #27489, so the retry, live-binding and history ledgers were written but never read. Delete them, the retry and settle actions, and the detach transfer. The disconnect and new-authority clears keep nulling stale ssh: PTY ids exactly as before. Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
9ad45591af |
refactor(layout): keep compatibility code in named folders behind one entry (#27556)
* refactor(layout): keep compatibility code in named folders behind one entry Moves the old-client translators into src/shared/workspace-layout/old-client/ and the remote layout snapshot recorder and persistence into src/main/remote-layout-snapshot/, each behind an index.ts. A new check-compat-boundary-imports script, run by pnpm lint and the PR workflow, fails when code outside a compatibility folder imports past its index or when a file other than the folder's named importer imports it. Moves only; no behaviour change. * test: drop whole-repo scan from boundary check unit tests; CI step runs it * fix(lint): list only existing compatibility folders; resolve renderer aliases in renderer files |
||
|
|
8731ca9672 |
refactor(rpc): delete host-side terminal source-range acks (#27546)
The host side of the terminal source-range ACK protocol had one producer: the relay-fed SSH output intake, deleted in #27515. Since then nothing installs the runtime's source-range hooks and nothing passes source ranges into PTY output, so every terminal.multiplex subscribe that offers ackOutputSourceRanges is already answered without it and uses the byte-window ACK. Delete the ledger, the per-connection registry, the consumer hook surface on the runtime, the stream-level reservation/commit/rollback plumbing in flow control, initial snapshot and cleanup, the source-range ACK frame schema, and the range slicing in output chunks, batching and replay. The subscribe schema still accepts the ackOutputSourceRanges capability, and the client keeps its range-ACK support for older hosts. A new golden test pins that an offering subscribe gets the same reply and frames as a byte-ACK one. Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
e22fd9c901 |
feat(macos): show terminal-host Full Disk Access, tag stable-copy daemons, and give Helpers folder strings (#27354)
* fix(macos): preserve terminal privacy identity across app updates
* test(macos): make launchd fixtures portable across host platforms
* fix(macos): probe protected contents and close handles on denial
* fix(i18n): register terminal permission status labels
* fix(macos): fork the daemon when no stable-bundle job can claim the endpoint
A copy, signature, plist or deadline failure before bootstrap, a bootstrap
launchd refused with the job absent, a job whose daemon exited before
answering, and a foreign endpoint all threw out of the launcher. The app then
lost its persistent daemon and fell back to local PTYs, where main forks one.
These now fall through to the existing fork path, which also adopts a foreign
occupant normally instead of as a degraded endpoint. A timed-out bootstrap or
a job still running/unreadable stays fatal, so no second daemon races it.
* feat(telemetry): split daemon origin events by stable-copy launch method
daemon_adopted, daemon_pty_cwd_denied and daemon_pty_cwd_readable gain a
closed-enum launch_method (stable-copy / app-fork / unknown), read from the
spawner path the daemon recorded, so the update fix is measurable per daemon
even when an old forked daemon is adopted across updates. The reliability
gate is a test manifest with no runtime switch, so there is no gate state to
report; spawner_path_class reports the private copy as 'other' alongside
other non-/Applications installs.
* refactor(telemetry): report the stable copy as a spawner path class
The separate launch_method property duplicated what the recorded spawner
path already says. spawner_path_class gains `stable-copy`, classified from
the same path under the private runtime root, and launch_method, its enum
and classifier are removed from all three .strict() daemon origin events.
A deleted copy still reports `missing`.
* refactor(settings): gate terminal-host Full Disk Access on macOS once
The permissions handler already asks for the terminal-host status only on
macOS, so the status function's own platform check and the pane's
'unsupported' branch could never fire. The off-macOS guarantee is now
tested at the handler.
* fix(macos): keep the fork fallback inside the startup PTY gate
The stable launch could run until 55 s after the launch started, so a late
"unavailable" verdict left the fork fallback's own 10 s readiness wait to
finish after the 60 s gate had already moved the app to local PTYs.
The launcher now gives the stable attempt one handoff deadline: the gate
minus the fork's readiness timeout and the 5 s lease/adapter allowance
(45 s). Copy, signature check and plist conversion share one abort signal
that ends early enough for bootstrap and the job's own readiness wait to
meet that deadline; every command otherwise keeps a fixed timeout. An
unavailable verdict past the deadline is reported instead of forking a
daemon that cannot arrive in time. The per-step budget module is gone (its
deletion landed in the previous commit by a staging slip).
The launch body is now two blocks: preparation (plist, plutil, deadline
check) fails as unavailable after removing the copy and job file, and
bootstrap runs in its own try/finally that always deletes the job file.
Retirement is simpler and claims more orphans: one in-flight collection per
process instead of a per-root map and rotating 20-record scan, and the job
record (written before copying) no longer carries a bundle name because
lsof +D scans the whole runtime directory (measured equal cost, same
result on a live copy). A never-submitted copy is retired once its producer
has exited and its job is missing, so a bootstrap timeout or a crash
mid-copy no longer leaves a copy forever; the open-file check still guards
deletion. Collection now starts from the launcher after the attempt.
* docs(macos): ship the daemon launch rules, not the investigation
The signed investigation harness left the tree in the previous telemetry
commit by a staging slip; its results and pinned link stay in the PR. This
trims the reference doc to why the copy and launchd exist, the fallback
states and the cleanup rule, trims the reliability gate to what its
validator needs, and points AGENTS.md at the reference doc next to the
Windows relocation entry.
* fix(macos): hand off to the fork as soon as the stable job exits
A copied daemon refused at exec (for example by Gatekeeper) never answers,
so every fresh start paid the full 10 s readiness wait before the fork
fallback. The readiness loop now reads the job state every 500 ms and
abandons as soon as the job is stopped or missing; a running or
unreadable job is still waited out and never forked beside.
Also drops the dead forkEntryPath the stable launch never read (its
options type now omits it) and un-exports the handoff constant.
* fix(macos): keep daemon copies out of Spotlight and unregister retired ones
Each runtime copy is a full Orca.app, so Spotlight indexed it as an app
and LaunchServices kept a record for it that outlived the copy. A
registered copy claims orca: links and Markdown/CSV files, and becomes
their default handler whenever /Applications/Orca.app is not registered.
New copies go under runtime-*/app.noindex/, which Spotlight skips and which
stops the record LaunchServices made while the bundle was being copied.
Retirement now runs lsregister -u on every bundle in the copy (both the
app.noindex layout and the earlier runtime-*/Orca.app layout) once lsof
shows it unused, then deletes it. A running copy stays registered,
because Local Network access may be resolved through that record.
Collection and stable-copy telemetry match the runtime-* directory, so
copies made before this change are still adopted, classified and retired.
* docs(macos): record the LaunchServices trade-off and the rollback plan
The reference doc now explains why copies sit in a .noindex folder, why
only retired copies are unregistered, and how to turn the copy launch off:
make launchMacDaemonFromStableBundle return null, and keep the retirement
code until no copy-launched daemon can remain, because the old code never
deletes copies.
A test pins that rollback: with the stable launcher returning null, the
launcher forks and still runs the copy collection.
* fix(macos): keep daemon copies registered so Local Network can resolve them
The `.noindex` folder kept copies out of Spotlight and LaunchServices, but
Local Network access is resolved from the signing identifier through
LaunchServices to the executable's Mach-O UUID. After an update that changes
Electron only the copy carries the running daemon's UUID, so an unregistered
copy risks the same Local Network loss main has today. Copies go back to
`runtime-*/Orca.app`; retirement still unregisters each copy before deleting
it, so deleted copies leave no LaunchServices entry. The remaining trade-off:
while `/Applications/Orca.app` is missing, a running registered copy can be
picked for `orca:` links and documents.
* fix(macos): hide daemon copies from Spotlight and tie the job to Orca
Spotlight listed each runtime copy as a second Orca app. New copies go
back under runtime-*/app.noindex/Orca.app, which Spotlight skips.
Retirement and stable-copy telemetry match the runtime-* directory, so
copies in the earlier runtime-*/Orca.app layout still retire and classify
the same way.
Local Network access identifies the daemon by its code signature and
main-executable UUID, not by a LaunchServices record, so an unregistered
copy keeps it. The launchd job now sets AssociatedBundleIdentifiers to
com.stablyai.orca, which is how a job not installed through SMAppService
names the app it belongs to. Copies are never registered explicitly;
retirement still runs lsregister -u before deleting each one.
* feat(macos): package a signed terminal host helper that runs the daemon on the pinned Node
Contents/Helpers/Orca Terminal Host.app carries the pinned Node, the daemon's
out/main JS and this slice's node-pty, signed inside-out as com.stablyai.orca.
afterPack boots it through one PTY round trip, then fails the build on layout,
Info.plist, macOS floor (NODE_RUNTIME_DARWIN_MINIMUM_OS vs Node's minos),
architecture or signature drift.
* ci(macos): cache and prefetch the pinned Node before packaging
Packaging now fetches the darwin Node from nodejs.org, whose download has no
retry. Each mac workflow caches it with the electron-builder downloads, keyed on
the pin, and warms both darwin targets inside the existing retry wrapper.
* feat(macos): launch the daemon from a copy of the terminal host helper, not the whole app
The stable launch now clones Contents/Helpers/Orca Terminal Host.app (~124 MiB
instead of ~628 MiB) and runs its Node on its daemon entry, without
ELECTRON_RUN_AS_NODE. The copy must still match the running app's designated
requirement. A missing helper falls back to the fork, and macOS below the
helper's Node floor forks without copying.
* docs(macos): describe the terminal host helper, its floor and its identity
* fix(macos): bound the terminal host boot check by its deadline and always clean up
A daemon that stayed silent hung packaging past the 20 s deadline; one that closed the
connection left the hello unsettled so Node exited 0 without cleanup; a malformed line threw
uncaught. Every connect, hello and PTY wait now races the deadline, close/error/parse failures
reject, and the daemon and scratch dir are removed on every path.
* ci(macos): cache the pinned Node on its own key with no restore-keys fallback
Sharing the electron-builder cache let old Node archives accumulate through restore-keys.
Also moves release-mac-build's Electron cache comment back onto its step.
* fix(macos): reject a universal slice for the terminal host and pin its name in one test
packageMacTerminalHost turned an unknown arch into darwin-undefined; it now throws through the
shared mac arch mapping. A test asserts the packager and the desktop agree on the helper bundle
and executable names, replacing a Linux/Windows test that could not fail.
* fix(macos): pass the launchd adoption lease its options object after the main merge
* test(macos): accept a reset as the daemon closing the boot-check connection; keep only passing gate commands
* fix(daemon): bump the terminal daemon protocol to v45 so new macOS tabs start in the terminal host helper
* fix(macos): ship the daemon's required packages in the terminal host and boot it outside the build tree
* style(macos): use a template literal in the terminal host resolution check
* fix(macos): derive the terminal host's packages from the daemon's own module graph
* fix(macos): treat any non-literal daemon load as computed, follow directory loads, and keep nested dependencies
* refactor(macos): move terminal-host telemetry, Settings status and Helper folder strings to a follow-up
Keeps this change to the update-safe terminal host itself. The follow-up restores:
- the `stable-copy` spawner path class in daemon adoption telemetry;
- the per-terminal-host Full Disk Access row in Settings and its daemon RPC;
- writing folder usage descriptions into the app's Electron Helpers for the fork fallback.
The helper still takes its Desktop, Documents and Downloads descriptions from
`macos-folder-usage-descriptions.cjs`, so that constant stays.
* feat(macos): report terminal-host Full Disk Access, classify stable-copy daemons, and give Helpers folder strings
Restores what
|
||
|
|
28d07a6b63 |
fix(mobile): open Grok, OpenCode, OMP and Pi chats on the phone (#27212)
* fix(mobile): open Grok, OpenCode, OMP and Pi chats on the phone The phone advertised only the Claude chat lane, so the host retitled every other agent's chat tab 'Update to view' and the phone's resolver refused non-Claude/Codex chat tabs anyway. Mobile now reads any agent the host lists, passes the chat's real agent to its send and slash-command paths, advertises the registered-agents and Pi dialog capabilities, hides image attach for agents the host says take none (as desktop does), and draws Pi's input dialog as one line. * test(mobile): drive the agents read through the shared fake client * fix(mobile): read options reports with no model Grok, OpenCode and OMP can report no current model, and a chat at rest may have none saved. The host refuses those reports to clients that don't advertise agent-session.options.optional-model.v1, which left the phone's model picker on its seed list and dropped /clear and /compact. * fix(mobile): reserve the chat session for every launched agent The phone matches a launched chat tab only by its session id, which it minted only for Claude and Codex. A Grok, OpenCode, OMP or Pi chat started from + landed only once the host replied, and a lost reply read as "Couldn't confirm the agent started" even with the chat listed. Hosts accept an agent-named id for any agent and ignore it for a terminal launch. * fix(mobile): read the host's agents once per connection, retry after a failure Every session-screen mount asked agentSession.agents again, so the attach button popped in late for OpenCode and Pi, and a read that failed while connected was never retried. Share one read per client, drop it on disconnect, and forget a failed read so the next chat asks again. * test(mobile): pin the chat's own agent on the send path and attach gating The send-bridge test passed with the claude/codex narrowing reverted. Drive an OpenCode chat through the controller and the structured session's send, where /review is the agent's own command but Codex's host claims it, and check the overlay offers attach only when the agent takes images. * fix(mobile): offer a card's X only while a turn is running The X on a structured prompt card stops its turn. Pi opens dialogs outside a turn too, and tapping X there could only say "Stop not sent". Claude and Codex cards arrive inside a running turn, so they keep their X. * fix(runtime): let a phone read the host's registered agents The phone now asks agentSession.agents to decide whether a chat's agent takes images, but the mobile allowlist refused the method, so OpenCode and Pi never got an attach button. It returns only each agent's id and its fixed capability record. * fix(mobile): keep a host's refusal of the agents list for the connection Released hosts advertise registered agents but refuse agentSession.agents to phones (forbidden, not available to mobile clients). Treat that answer as no list until the next connection instead of re-asking on every chat; keep retrying transient failures. * fix(mobile): shrink the model pill before it pushes Send off the composer row On a narrow phone, attach + a long model label + mic left no room for Send (seen with Pi's resting label). The pill's slot is now the row's only shrinkable item, so its label truncates and the buttons keep their size. Rows that already fit look the same. * fix(mobile): keep /clear and /compact for a chat whose agent lists no models The phone dropped a structured chat's option controller when its picker had nothing to show, and the / menu reads the chat's conversation commands from that controller. OMP lists no models, so its menu lost /clear and /compact. Keep the controller for every structured chat; the picker still renders nothing without a model. * feat(runtime): say in a chat's options report whether it takes images The host's options read now carries acceptsImages for every session: the agent definition's imagePrompts, narrowed by a running ACP child to what its initialize advertised (the same rule its sends use). Absent means an older host. * refactor(mobile): read whether a chat takes images from its options report The phone no longer reads the host's agent list for the attach button. It takes acceptsImages from the agentSession.options read it already makes, keyed to the chat, and kept through a pick made during that read. Before the read answers, and from an older host, Claude and Codex keep attach and other agents don't, as before. Drops the agent-list hook with its cache and refusal latch, the host-support flag only it used, and agentSession.agents from the host's mobile allowlist. * refactor(native-chat): gate desktop chat images on the session's options report The composer's image gate takes the session's acceptsImages answer first, falling back to the host's agent list before the options read lands (a new chat's start) and for an older host that sends no answer. With the phone no longer using it, structuredAgentAcceptsImages moves back to the renderer, where it was. * test(native-chat): prove the session's image answer reaches the composer gate |
||
|
|
7d14ac0bcb |
refactor(layout): add the editor tab view type to the layout model (unused) (#27541)
* refactor(layout): add the editor tab view type to the layout model (unused) Move the open-file sub-types a diff, markdown preview, conflict, check-details or chat-visual tab carries into src/shared, and give the layout model's editor record an optional view keyed by mode. The Serializer leaves it off disk, so the saved session is byte-identical. Nothing sets it yet. * refactor(layout): hold non-file editor views by tab id, not on the file record Diff, preview, conflict-review, check-details and chat-visual tabs use a worktree root or a synthetic id as their path, so on the file record they would collide and save as edit tabs. The file record keeps only the edit-tab conflict view; the others live in an unsaved map keyed by tab id, set by openEditorTab's new optional view and dropped when the tab closes. * refactor(layout): keep every editor tab view in the one unsaved map Edit-tab conflict details join the other views in the per-workspace map keyed by tab id, so the file record and the Serializer are unchanged and there is one place to read a view. |
||
|
|
b15c2c6dd6 |
refactor(persistence): retire the dead SSH PTY lease writers (#27555)
* test(persistence): seed relay-era SSH lease rows from disk * refactor(persistence): retire the dead SSH PTY lease writers * test(persistence): read relay-era recovery rows without a cast * chore(ratchet): prune layout-writer baseline after lease writer removal --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
738e0e034b |
refactor(layout): add the per-view store, pane status and runtime leaf-record projection (unused) (#27535)
* refactor(layout): add the per-view store, pane status and runtime leaf-record projection (unused) Pure pieces the layout holder will use, with no caller yet: - per-view records keyed by view id (desktop, device:<id>), read-time fallback for removed tabs and panes, save-time prune and workspace removal; - pane status derived from the start token, last outcome, sleeping record, PTY registry and host reachability; - the runtime's tab and leaf records projected from the layout model and the PTY registry in the graph mirror's shape, checked against records captured from today's mirror. * fix(layout): follow today's close selection, title resets and lost-host status in P6 - A close moves each view's shown tab when applied, with the pre-close order: most recent remaining, else the closed tab's neighbour, through the headless close's pickNextActiveTabAfterClose (now exported). - The projection's pane title is the newer of the PTY's OSC title and the neutral reset the window applies after an agent exits or is interrupted; those reset rules move to shared/terminal-pane-title-reset.ts and the renderer uses them unchanged. - A leaf is connected, as in the mirror, unless the host proved an exit, but only when the PTY registry holds the terminal. - An unbound pane on a lost host is waiting_for_host. - The mirror capture covers a title reset, a lost host and an unregistered PTY, and names each difference from the mirror. * fix(layout): an emptied group hands a view's active group to its split sibling, as collapseGroupLayout does |
||
|
|
5e5865a226 |
refactor(layout): drop unused snapshot receipt fields and add the snapshot read method (#27536)
Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
f9794d8efc |
refactor(remote): one paired E2EE handshake core for desktop, CLI, web and mobile clients (#27523)
* refactor(remote): one paired E2EE handshake core for desktop, CLI, web and mobile clients Every paired client now builds its hello, checks the host's ready, seals its auth frame and opens host frames through src/shared/paired-e2ee-client-handshake. Platforms inject their own NaCl/base64 primitives. The v2 client session and its pure-JS key schedule (@noble/hashes 1.8.0, the version mobile pins) move to src/shared, and the per-host v2 latch policy is shared with an injected store. No wire change: frames match the captured legacy and v2 fixtures byte for byte, and a cross-version test drives the core against the released host and the released client against this host. The auth step is now async so a device-key signer can slot in later. * fix(remote): keep @noble/hashes out of the CLI's unbundled runtime The packaged CLI loads src/shared output from disk, so a bare @noble/hashes import failed the packaged-runtime check. The v2 key schedule is now a primitive: Node clients use the node:crypto schedule (moved to src/shared so the CLI can reach it), while the browser and phone keep the pure-JS one. * fix(mobile): re-check the v2 latch before sending a legacy auth frame The auth step is async, so another socket can latch the host to v2 while a legacy auth frame is being sealed. Refuse it at send time, as before. --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
1674269686 |
feat(workspace-layout): pure old-client translators for layout methods (inert) (#27534)
* feat(workspace-layout): add pure old-client translators for layout methods
Unused by main. Translates old session.tabs.* and terminal.* params into
layout commands (updatePaneLayout per rule 11, terminal.create {tabId,
leafId} as start/split/create) and projects the model into today's
session.tabs snapshot rows, with customTitle read first (#19582).
* fix(workspace-layout): match today's old-client behaviour in the translators
CLI terminal.close/closeTab keep the pinned refusal; terminal titles use
resolveTerminalTabTitle with the generated-titles setting and the sole-pane
rule; session.tabs.move keeps today's exact-order validation and error
codes; setTabProps on an unknown tab is a no-op; drop the close force
input the wire does not carry.
* fix(workspace-layout): phone tab close keeps today's pinned refusal
A terminal tab row or a tab's last pane refuses a pinned tab
(terminal_tab_pinned) with or without a window, as today; a pane of a
split tab and non-terminal tabs close as asked.
|
||
|
|
3f8d296b8d |
fix(macos): preserve terminal permissions across app updates with a small signed helper (#27021)
* fix(macos): preserve terminal privacy identity across app updates * test(macos): make launchd fixtures portable across host platforms * fix(macos): probe protected contents and close handles on denial * fix(i18n): register terminal permission status labels * fix(macos): fork the daemon when no stable-bundle job can claim the endpoint A copy, signature, plist or deadline failure before bootstrap, a bootstrap launchd refused with the job absent, a job whose daemon exited before answering, and a foreign endpoint all threw out of the launcher. The app then lost its persistent daemon and fell back to local PTYs, where main forks one. These now fall through to the existing fork path, which also adopts a foreign occupant normally instead of as a degraded endpoint. A timed-out bootstrap or a job still running/unreadable stays fatal, so no second daemon races it. * feat(telemetry): split daemon origin events by stable-copy launch method daemon_adopted, daemon_pty_cwd_denied and daemon_pty_cwd_readable gain a closed-enum launch_method (stable-copy / app-fork / unknown), read from the spawner path the daemon recorded, so the update fix is measurable per daemon even when an old forked daemon is adopted across updates. The reliability gate is a test manifest with no runtime switch, so there is no gate state to report; spawner_path_class reports the private copy as 'other' alongside other non-/Applications installs. * refactor(telemetry): report the stable copy as a spawner path class The separate launch_method property duplicated what the recorded spawner path already says. spawner_path_class gains `stable-copy`, classified from the same path under the private runtime root, and launch_method, its enum and classifier are removed from all three .strict() daemon origin events. A deleted copy still reports `missing`. * refactor(settings): gate terminal-host Full Disk Access on macOS once The permissions handler already asks for the terminal-host status only on macOS, so the status function's own platform check and the pane's 'unsupported' branch could never fire. The off-macOS guarantee is now tested at the handler. * fix(macos): keep the fork fallback inside the startup PTY gate The stable launch could run until 55 s after the launch started, so a late "unavailable" verdict left the fork fallback's own 10 s readiness wait to finish after the 60 s gate had already moved the app to local PTYs. The launcher now gives the stable attempt one handoff deadline: the gate minus the fork's readiness timeout and the 5 s lease/adapter allowance (45 s). Copy, signature check and plist conversion share one abort signal that ends early enough for bootstrap and the job's own readiness wait to meet that deadline; every command otherwise keeps a fixed timeout. An unavailable verdict past the deadline is reported instead of forking a daemon that cannot arrive in time. The per-step budget module is gone (its deletion landed in the previous commit by a staging slip). The launch body is now two blocks: preparation (plist, plutil, deadline check) fails as unavailable after removing the copy and job file, and bootstrap runs in its own try/finally that always deletes the job file. Retirement is simpler and claims more orphans: one in-flight collection per process instead of a per-root map and rotating 20-record scan, and the job record (written before copying) no longer carries a bundle name because lsof +D scans the whole runtime directory (measured equal cost, same result on a live copy). A never-submitted copy is retired once its producer has exited and its job is missing, so a bootstrap timeout or a crash mid-copy no longer leaves a copy forever; the open-file check still guards deletion. Collection now starts from the launcher after the attempt. * docs(macos): ship the daemon launch rules, not the investigation The signed investigation harness left the tree in the previous telemetry commit by a staging slip; its results and pinned link stay in the PR. This trims the reference doc to why the copy and launchd exist, the fallback states and the cleanup rule, trims the reliability gate to what its validator needs, and points AGENTS.md at the reference doc next to the Windows relocation entry. * fix(macos): hand off to the fork as soon as the stable job exits A copied daemon refused at exec (for example by Gatekeeper) never answers, so every fresh start paid the full 10 s readiness wait before the fork fallback. The readiness loop now reads the job state every 500 ms and abandons as soon as the job is stopped or missing; a running or unreadable job is still waited out and never forked beside. Also drops the dead forkEntryPath the stable launch never read (its options type now omits it) and un-exports the handoff constant. * fix(macos): keep daemon copies out of Spotlight and unregister retired ones Each runtime copy is a full Orca.app, so Spotlight indexed it as an app and LaunchServices kept a record for it that outlived the copy. A registered copy claims orca: links and Markdown/CSV files, and becomes their default handler whenever /Applications/Orca.app is not registered. New copies go under runtime-*/app.noindex/, which Spotlight skips and which stops the record LaunchServices made while the bundle was being copied. Retirement now runs lsregister -u on every bundle in the copy (both the app.noindex layout and the earlier runtime-*/Orca.app layout) once lsof shows it unused, then deletes it. A running copy stays registered, because Local Network access may be resolved through that record. Collection and stable-copy telemetry match the runtime-* directory, so copies made before this change are still adopted, classified and retired. * docs(macos): record the LaunchServices trade-off and the rollback plan The reference doc now explains why copies sit in a .noindex folder, why only retired copies are unregistered, and how to turn the copy launch off: make launchMacDaemonFromStableBundle return null, and keep the retirement code until no copy-launched daemon can remain, because the old code never deletes copies. A test pins that rollback: with the stable launcher returning null, the launcher forks and still runs the copy collection. * fix(macos): keep daemon copies registered so Local Network can resolve them The `.noindex` folder kept copies out of Spotlight and LaunchServices, but Local Network access is resolved from the signing identifier through LaunchServices to the executable's Mach-O UUID. After an update that changes Electron only the copy carries the running daemon's UUID, so an unregistered copy risks the same Local Network loss main has today. Copies go back to `runtime-*/Orca.app`; retirement still unregisters each copy before deleting it, so deleted copies leave no LaunchServices entry. The remaining trade-off: while `/Applications/Orca.app` is missing, a running registered copy can be picked for `orca:` links and documents. * fix(macos): hide daemon copies from Spotlight and tie the job to Orca Spotlight listed each runtime copy as a second Orca app. New copies go back under runtime-*/app.noindex/Orca.app, which Spotlight skips. Retirement and stable-copy telemetry match the runtime-* directory, so copies in the earlier runtime-*/Orca.app layout still retire and classify the same way. Local Network access identifies the daemon by its code signature and main-executable UUID, not by a LaunchServices record, so an unregistered copy keeps it. The launchd job now sets AssociatedBundleIdentifiers to com.stablyai.orca, which is how a job not installed through SMAppService names the app it belongs to. Copies are never registered explicitly; retirement still runs lsregister -u before deleting each one. * feat(macos): package a signed terminal host helper that runs the daemon on the pinned Node Contents/Helpers/Orca Terminal Host.app carries the pinned Node, the daemon's out/main JS and this slice's node-pty, signed inside-out as com.stablyai.orca. afterPack boots it through one PTY round trip, then fails the build on layout, Info.plist, macOS floor (NODE_RUNTIME_DARWIN_MINIMUM_OS vs Node's minos), architecture or signature drift. * ci(macos): cache and prefetch the pinned Node before packaging Packaging now fetches the darwin Node from nodejs.org, whose download has no retry. Each mac workflow caches it with the electron-builder downloads, keyed on the pin, and warms both darwin targets inside the existing retry wrapper. * feat(macos): launch the daemon from a copy of the terminal host helper, not the whole app The stable launch now clones Contents/Helpers/Orca Terminal Host.app (~124 MiB instead of ~628 MiB) and runs its Node on its daemon entry, without ELECTRON_RUN_AS_NODE. The copy must still match the running app's designated requirement. A missing helper falls back to the fork, and macOS below the helper's Node floor forks without copying. * docs(macos): describe the terminal host helper, its floor and its identity * fix(macos): bound the terminal host boot check by its deadline and always clean up A daemon that stayed silent hung packaging past the 20 s deadline; one that closed the connection left the hello unsettled so Node exited 0 without cleanup; a malformed line threw uncaught. Every connect, hello and PTY wait now races the deadline, close/error/parse failures reject, and the daemon and scratch dir are removed on every path. * ci(macos): cache the pinned Node on its own key with no restore-keys fallback Sharing the electron-builder cache let old Node archives accumulate through restore-keys. Also moves release-mac-build's Electron cache comment back onto its step. * fix(macos): reject a universal slice for the terminal host and pin its name in one test packageMacTerminalHost turned an unknown arch into darwin-undefined; it now throws through the shared mac arch mapping. A test asserts the packager and the desktop agree on the helper bundle and executable names, replacing a Linux/Windows test that could not fail. * fix(macos): pass the launchd adoption lease its options object after the main merge * test(macos): accept a reset as the daemon closing the boot-check connection; keep only passing gate commands * fix(daemon): bump the terminal daemon protocol to v45 so new macOS tabs start in the terminal host helper * fix(macos): ship the daemon's required packages in the terminal host and boot it outside the build tree * style(macos): use a template literal in the terminal host resolution check * fix(macos): derive the terminal host's packages from the daemon's own module graph * fix(macos): treat any non-literal daemon load as computed, follow directory loads, and keep nested dependencies * refactor(macos): move terminal-host telemetry, Settings status and Helper folder strings to a follow-up Keeps this change to the update-safe terminal host itself. The follow-up restores: - the `stable-copy` spawner path class in daemon adoption telemetry; - the per-terminal-host Full Disk Access row in Settings and its daemon RPC; - writing folder usage descriptions into the app's Electron Helpers for the fork fallback. The helper still takes its Desktop, Documents and Downloads descriptions from `macos-folder-usage-descriptions.cjs`, so that constant stays. * fix(i18n): restore en.json to this branch's base, not newer main The previous commit took the file from a newer main, which pulled unrelated strings into this PR. * refactor(macos): publish one terminal-host copy per helper build under a fixed launchd label Name the copy after the helper's CDHash, reuse it when present, and publish a new one by cloning into a staging dir, verifying it once against the app's designated requirement and renaming it into place. One launchd label per profile and protocol replaces per-launch labels, so job.json, producer pids and per-label collection go away. Retirement deletes any other copy lsof shows unused. The stable launcher returns null for every fork fallback and logs its reason. * fix(packaging): copy terminal-host dependencies from where Node resolves them A nested package's own dependencies were never walked, so a hoisted dependency of a nested package was left out of the helper. Resolve each dependency through the node_modules lookup Node performs from its parent and copy it to the same relative place, unless a copied package already carries it. * fix(packaging): fail release and CI mac builds that cannot boot a terminal-host slice An arm64 runner without Rosetta skipped the x64 helper's PTY boot check and shipped the slice untested. Release channels and CI now fail there; local cross-arch builds still skip with a log line. * docs(macos): describe the per-build terminal-host copy and its fallback reasons * docs(macos): keep the codesign command on one line * test(macos): type the launchd test state without an assertion * docs(macos): name what the login-session flag gates in the stable launcher * fix(macos): wait for launchd to drop a booted-out terminal job before reusing its label --------- Co-authored-by: Neil <neil@stably.ai> |
||
|
|
448b9f6fe9 |
refactor(layout): add the pane start, exit, first-surface and rename rules (unused) (#27432)
* refactor(layout): add the pane start, exit, first-surface and rename rules Pure rules in the shared layout model, with no caller yet: - startPane leaves a bound pane, or one whose start is in flight, alone; - a process exit retires its pane on code 0, keeps today's keep cases as exited, and only unbinds on an unproven exit or attach absence; - a per-pane last outcome, cleared by startPane and restartPane; - createTerminalTab/openAgentSessionTab `initial: restore | activation`; - editor tabs and their file records follow a renamed file. * fix(layout): keep a newborn pane's exit whatever its code, as today * fix(layout): keep a split by today's facts and report renamed tab ids |
||
|
|
7dfa4d6e54 |
fix(native-chat): a new chat's model and effort pickers match their first frame before the agent starts (#27031)
* fix(native-chat): a new chat's effort pill keeps its first-frame value before the agent starts A chat with no model pick runs the catalog's default model at that model's default effort. The at-rest options answer now says so for every agent, not only those whose live child reports the model's default, so the effort pill no longer blanks and then jumps when the agent starts. * fix(native-chat): read a chat's at-rest options for the folder it runs in The at-rest options read asked the model catalog with no workspace, so it always named the account's default model and effort, even in a repo whose own agent config could pick another. The new chat's first frame, read for that repo, names nothing there. The at-rest read now asks for the session's own folder, so both frames agree on the model and the effort. * fix(native-chat): scope the at-rest options read only for clients that take no model A client that needs a model in every options answer (mobile today, older desktops) refuses an answer naming none, and loses that read's commands with it. Only a client advertising the optional-model capability gets the answer for the chat's own folder; any other keeps today's account-default answer. * test: run the at-rest workspace-scope RPC test in the Node SQLite project * test: build catalog listings so the fixtures compile before and after main drops the tier map * fix(native-chat): one at-rest rule — the chat's folder decides its default for every client The folder-scoped catalog read now decides the default for every client. Where it names one, the answer names that model and its effort. Where it names none, a client that takes a model-less answer gets none, and one that needs a model gets the account default model with no effort: exactly what it got before. The reader is now required, and the first frame and the at-rest read share one rule for which locations name a local folder. * fix(native-chat): keep test callers of the host's options read untouched Only in-process callers omit the reader at the host's entry, and they read as the RPC gate treats an in-process caller. The shared read itself still requires one. This leaves main's newer test call sites unconflicted. * refactor: one native-local location check for Claude, Codex and the at-rest folder read |
||
|
|
0df25b7ac9 |
refactor(pty): delete relay-fed SSH output intake and projection threading (#27515)
Nothing in production feeds the SSH PTY output intake: its only data and exit entry points were called from tests, and the SSH PTY provider that would have fed it is gone. Delete the intake, its source-obligation ledger, model admission, projection publication and source-ack plumbing, and strip the projection ids and source-credit bookkeeping they threaded through the local renderer delivery path (accept, pending, flush, payload, accounting, exit, session). Local terminal delivery is unchanged. Because the intake also installed the runtime's remote source-range hooks, this desktop host now answers a terminal.subscribe that asks for ackOutputSourceRanges the way every orcad host already does: without the capability, so the client uses the byte-window ACK. No source ranges were ever attached to local output. Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
20f8cb02c1 |
feat(mobile): verified https pairing links; custom scheme carries no secret (#27475)
* feat(mobile): verified https pairing links; custom scheme carries no secret The desktop's copy-link now emits https://www.onorca.dev/pair#<code>, with the code only in the fragment. Orca Mobile claims that link as a verified app link and refuses any pairing code delivered through orca://, which any app can claim; orca://pair alone opens the scanner with an explanation. In-app QR scan and paste still accept the custom scheme, so older desktops and phones keep pairing. The confirm screen shows the desktop address and host key fingerprint before pairing. config/verified-links/ holds the apple-app-site-association, assetlinks.json and fallback page to deploy on www.onorca.dev, with a test that keeps them, mobile/app.config.js and the link builder in agreement. Part of #17397. Co-Authored-By: Claude <noreply@anthropic.com> * fix(mobile): refuse pairing codes nested in or normalized out of unverified links The router unwraps expo-development-client ?url= links and normalizes dot segments after +native-intent runs, so an orca:// wrapper could still deliver a code to the confirm screen. Refuse any unverified link carrying code= at any encoding depth, and match pairing routes on decoded, dot-free segments. Co-Authored-By: Claude <noreply@anthropic.com> * fix(mobile): escape the apostrophe in the refused-link notice Co-Authored-By: Claude <noreply@anthropic.com> * fix(mobile): decode escapes byte by byte before refusing nested pairing codes An invalid UTF-8 escape made the whole escape run fail to decode, hiding an adjacent encoded &code= that the router still decodes leniently. Co-Authored-By: Claude <noreply@anthropic.com> * fix(mobile): decode nested pairing links to any depth before refusing codes Co-Authored-By: Claude <noreply@anthropic.com> * fix(mobile): ignore URL-stripped whitespace when refusing nested pairing codes Co-Authored-By: Claude <noreply@anthropic.com> * refactor(mobile): decide pairing-link refusal on the router's own parse Stop re-implementing URL decoding. The guard now runs expo-router's own extractExpoPathFromURL and parseUrlUsingCustomBase, the functions its linking applies after +native-intent, and refuses any link from which the router would read a `code` param or reach a pairing route with a payload. A link the router cannot parse is refused. Release builds refuse expo-development-client wrappers outright. A property-style test walks wrapping depths, encoding layers, stripped control characters, case and unicode, and asserts that the guard never passes on a link from which the router would read a code. Co-Authored-By: Claude <noreply@anthropic.com> * fix(mobile): match pairing routes on the top-level segment only A host link with a segment named pair was sent to the scanner. Co-Authored-By: Claude <noreply@anthropic.com> * test(mobile): pin line-terminator fragment links as refused Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
6a303be248 |
refactor(main): agent detection, automations, skills, AI vault and ephemeral VM drop SSH provider arms (#27511)
* refactor(main): agent detection, automations, AI vault and ephemeral VM drop SSH provider arms * refactor(skills): refuse SSH skill destinations at the branch and delete the relay skill services * test(preflight): clear the local-detection mock before the SSH refusal cases --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
a4a96fee70 |
refactor(pty): delete the SSH PTY provider and its registry slot (#27499)
* refactor(pty): delete the SSH PTY provider and its registry slot Nothing in production registers an SSH PTY provider, so the registry's SSH map was always empty: every SSH lookup answered undefined or threw the 'No PTY provider for connection' error. Delete the provider stack, its consumer-session and source-recovery contracts, the registry maps and the getSshProvider dependency, keeping each caller's existing result for an absent provider. The registry now dispatches only 'local'; any other host throws PtyHostNotDispatchableError, which keeps the renderer-matched message for SSH ids and is never read as a gone or exited PTY. * test(pty): keep the no-provider SSH routing cases in local-only form * test: reword a comment naming the deleted SSH PTY provider * test: drop an import the rebase left unused --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
9c80a61199 |
feat(daemon): the host keeps its own record of old-client restores it refuses (#27496)
* feat(daemon): keep the host's own record of refused legacy restores * feat(daemon): the host keeps its own record of old-client restores it refuses --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
47b94ef64a |
fix(daemon): keep tweetnacl out of the relocated Windows daemon-host graph; guard daemon externals at build (#27504)
Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
6ffbdcd875 |
feat(runtime): device-key authentication and per-installation key migration (host) (#27493)
* feat(runtime): device-key authentication and per-installation key migration (host) * fix(runtime): corrupt registry is never a revocation verdict; keyed sockets dispatch without a token; forks keep their Relay binding * fix(runtime): keep an unparseable device registry aside; an unlisted key is never 'revoked' while it exists * fix(runtime): a set-aside corrupt registry keeps verdicts doubtful even when the registry file is missing * test(runtime): registerDeviceKey reaches the bound socket through the dispatcher for runtime and mobile rows --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
6e9dc090a5 |
feat(daemon): one automatic-restore budget per execution host (#27492)
* refactor(daemon): keep the gate's host share in its own module * test(daemon): pin that an observing desktop's restores count against serve's budget --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
ce028b15e5 |
feat(daemon): cap terminals per execution host (#27488)
* feat(daemon): cap terminals per execution host * fix(daemon): never let a ledger cleanup throw into a spawn * fix(daemon): recreate the host terminal ledger dir after a /tmp cleaner * test(daemon): drop a default-parameter case from the ledger dir test --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
08b65edc5c |
feat(serve): let the owner resume paused automatic restores (#27459)
* feat(serve): let the owner resume paused automatic restores The terminal daemon now answers automaticWorkStatus and resumeAutomaticWork. Resume clears a recorded same-boot crash pause or a pressure pause, writes the cleared state durably, and wakes restores that were waiting. The runtime exposes runtime.automaticWork.status and runtime.automaticWork.resume (host-admin; advertised as runtime.automatic-work.v1), the CLI adds 'orca serve restore resume', and a sleeping pane paused by its host offers 'Resume automatic restores'. A daemon that predates the requests reports no gate. * fix(serve): a stale daemon cannot resume, and an old one says so Resume now re-reads endpoint ownership right before it runs, so a daemon replaced by a successor cannot rewrite the successor's run marker. A gate-bearing daemon from before resume existed is reported as 'terminal host predates resume' instead of 'no gate'; the CLI says to run the command again after the terminal host restarts, and a sleeping pane still resumes when the user asks for it. --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
8bc4cef855 |
fix(serve): decide a duplicate or doomed Electron serve before exec (#27472)
On macOS a second Electron main aborts inside _RegisterApplication before any Orca JavaScript can apply the one-Orca-per-profile rule, so supervisors and agents retrying 'orca serve' loop on SIGABRT (#14541). The CLI now checks the profile before it execs Electron serve. A runtime that answers, or whose socket accepts, is live; one whose socket neither accepts nor refuses, or that refuses this CLI's credentials, is unverifiable; both refuse with exit code 3, the single-instance code systemd units already key off. Only a definitive refusal (ENOENT or ECONNREFUSED) lets it start. A fresh profile keeps the old synchronous launch. A macOS startup abort is remembered per environment, so a later separate 'orca serve' from the same launchd session refuses for 30 minutes with exit 78 instead of aborting again; --force overrides, and a serve that reaches readiness clears it. Adapted from #14768; the Launch Services diagnostic wording and the exit code 3 contract come from #14542. Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: thiagomsoares <5190162+thiagomsoares@users.noreply.github.com> |
||
|
|
d3417b257f |
feat(terminal): declare restore intent and keep deferred panes sleeping (#27455)
* feat(terminal): declare restore intent and keep deferred panes sleeping The visible pane's agent restore is sent as user-resume and hidden ones as automatic-restore, to hosts that advertise terminal.spawn-intent.v1 (and always on the local spawn path). When the host keeps a restore asleep, the pane shows 'Sleeping — click to resume' with the reason, keeps a sleeping record so the session stays listed and resumable, and resumes as the user's own when it is opened or clicked. A deferred pane is not remounted, which would only be deferred again. * refactor(shared): one restore spawn intent type * fix(terminal): a deferred pane owns its session against wake sweeps A deferred pane has no PTY, so the activation and mobile wake sweeps read its unfinished record as unowned and could launch the session in a new tab while the pane still held a resume. The sweep now treats a deferred pane as the owner, the remembered record is a quit-style record a sweep never discards, and a retry declines to resume a session that is already live in another pane. * fix(terminal): keep a deferred pane's claim through its sweep and retry The wake sweep now leaves a deferred pane's record alone, since that pane's own queued resume matches it and is not a duplicate. A retry keeps the pane registered as the session's owner until its PTY binds, and declines to resume when another tab already has the same session queued. --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
5d7f48e00b |
fix(serve): keep the isolated profile off the login keychain (#27463)
* fix(serve): keep the isolated profile off the login keychain The E2E/pairing runtime hands the app a fresh HOME, so there is no login keychain for OSCrypt to read. On the serve path the secret-protection report runs inline before readiness and probes safeStorage on the main thread, where Chromium's keychain lookup creates the item and waits on an authorization reply that never arrives, so `orca serve` never advertises (#25453). The sidecar already launches with --use-mock-keychain for exactly this profile; share that decision and give the foreground serve process the same switches. * test(serve): read spawn args without type assertions Co-authored-by: Paolo <paovalletta@hotmail.it> * fix(serve): only an isolated userData profile drops the keychain The sidecar always runs on a fresh profile, so either E2E variable is enough there. The foreground serve process opens the selected profile's sealed secrets, and a HOME override alone still selects the user's real profile, so it now needs ORCA_E2E_USER_DATA_DIR. Co-authored-by: Paolo <paovalletta@hotmail.it> --------- Co-authored-by: Paolo <paovalletta@hotmail.it> Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
10d5108671 |
feat(runtime): accept a declared spawn intent from paired clients (#27452)
* feat(runtime): accept a declared spawn intent from paired clients Paired clients can now say whether a terminal restore is the pane the user is looking at (user-resume) or a hidden one (automatic-restore). The host forwards the intent to its fork gate, advertises terminal.spawn-intent.v1 so clients only send it to hosts that accept it (terminal.ensureAgentSession params are strict), and reports a deferred fork as terminal_spawn_deferred with its reason. No client sends the field yet; that lands next with the sleeping-pane handling. * fix(daemon): a claimed agent resume cannot declare itself interactive Interactive forks skip the gate, and paired clients can now declare intent, so the host keeps inferring from the claim when a request says interactive. * fix(runtime): clients may only declare restore intents A daemon kept running across an update still lets a declared interactive request skip its gate, even for a claimed resume. The RPC and local IPC now accept only user-resume and automatic-restore, and the daemon request boundary turns a claimed interactive into an automatic restore. --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
061cb226e2 |
refactor(orcad): shared serve-option validation; no daemon barrel (#27443)
orcad now checks its flags with the same validator Electron serve and the CLI use, so contradictory pairing flags fail with the same message instead of one silently winning. The CLI launcher drops its duplicate recipe check (the CLI handler already ran the shared validator). Removes the daemon-init and cli/runtime/index re-export barrels and the parsePtySessionId / OrcadInstanceLockError re-exports; callers and mocks import the defining modules. Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e2339f27a5 |
fix(daemon): count a claimed agent resume as automatic even without a history seed (#27442)
Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
c2624abcfc |
feat(daemon): process-growth breaker that pauses automatic restores (#27437)
* feat(daemon): process-growth breaker that pauses automatic restores per host and names the agent * fix(daemon): breaker trips only on sustained growth, pauses in memory, observes on desktop * fix(daemon): sample fast only while a tree is rising or tripped --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
0418773106 |
feat(daemon): pause automatic restores after a same-boot crash (#27433)
* feat(daemon): pause automatic restores after a same-boot crash * fix(daemon): per-version crash marker, owner-only writes, orderly login retirement, no Windows boot-time guess --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
6ad1a670a3 |
refactor(rpc): delete the SSH-bridge caller scope (#27440)
Nothing mints an SSH-bridge credential since the relay client was removed (#27214), so the 'ssh-bridge' caller kind, its host binders, result filter, the CLI bridge-mode policy and the ORCA_SSH_BRIDGE_CREDENTIAL env read could never run. The allowRemoteCliControl target field stays for older peers. Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
5651141700 |
chore: delete verified-unused modules and exports (#27438)
Removes seven modules that only their own tests import, dead orcad exports and protocol constants, spells orcad's configuration exit code once, reuses the shared errorMessage helper, and drops links to the deleted docs/reference folder. Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
be59462134 |
feat(daemon): pace automatic terminal restores on headless hosts (#27428)
* feat(daemon): pace automatic terminal restores on headless hosts * fix(daemon): cap the paced-restore window below remote client timeouts --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
f8ea867124 |
feat(daemon): observe-only fork gate for automatic terminal restores (#27424)
* feat(daemon): observe-only fork gate for automatic terminal restores * fix(daemon): keep a canceled fork charged until reaped; never strand a user resume --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
0024735e82 |
fix(ssh): remove the relay-only remote workspace sync so reconnect never sticks on pulling (#27409)
* fix(ssh): remove the relay-only remote workspace sync so reconnect never sticks on pulling * refactor(ssh): drop snapshot apply tokens and terminal finalization only the sync used * test: drop leftover sync-status mock --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
e9928c3542 |
feat(mobile): let the desktop list its servers and their workspaces for the phone (#26442)
* feat(mobile): show SSH host health on the workspace row host badge
Rows on an SSH host the desktop cannot reach looked healthy on the phone. The badge now
adds the desktop sidebar's own word ("devbox · Disconnected"), derived by the same shared
function from the connection status ssh.listTargetSummaries already sends; old desktops keep today's badge.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): name an unhealthy SSH host even in a single-host list
The desktop card shows SSH status on any host count, so the phone now reads target summaries
whenever a repo is on SSH and badges an unhealthy host alone too. Labels and health share one
target-to-host keying, and a hook-level test pins the reply-to-row wiring.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): clear stale SSH host badges and announce them without the dot
A catalog that no longer spans hosts or holds an SSH repo now clears the health and label maps,
so a host that left cannot keep badging rows. The badge gets a screen-reader label ("devbox,
Disconnected") instead of reading the middle dot.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(runtime): let a paired desktop register its phones as delegated mobile devices
A desktop paired to this runtime can now call pairing.delegatedMobileDevice.sync to make each of
its phones an ordinary mobile-scope device it owns, so the phone mirrors that host through the
desktop. Children are never pending, inherit the parent's reach, and die with the parent's grant.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(runtime): move the mobile device revoke primitive below dispatch
Dispatch binds the caller-bound delegated-device context, so the revoke path it needs now lives in
a layer under it as a pure move; this drops the abstract method seam. No behaviour change.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(runtime): keep a runtime grant until its delegated phones are revoked
A failed child cleanup now leaves the parent in place so a retry can still find the children, and
revokeRuntimeAccess lives beside the revoke it cascades to. Adds restart, rename and bounds coverage,
shares credential minting, and drops the timing-dependent census.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(runtime): fail a delegated sync whose dropped phone cannot be revoked
Matches the parent revoke: a child whose cleanup could not be saved is kept and the sync errors
before minting anything, so the desktop retries instead of believing the server matches its list.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): relay a paired phone's server-workspace requests through the desktop
A phone mirrors its desktop, including workspaces on the desktop's configured servers. The desktop
now forwards a request that names `executionHost: runtime:<env>` to that server over one socket per
(phone, server), signed in as the phone's delegated device; replies pass through untouched except
stream ids. Hidden until the phone client ships: the relay capability is not advertised yet.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): type the single-host health fixtures inline so the test tsc passes
Fixtures hoisted into consts widened their health maps to string, which the now-required
hostHealthById rejects under the tests-typecheck ratchet; passing them inline keeps the union.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* docs(mobile): state the desktop relay contract in its entry module
main dropped docs/reference, so the relay's contract lives beside the code. Also pins that a
server whose delegated sync fails (e.g. delegated_device_revoke_failed) is unavailable until a
later request syncs it again.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): pin the relay's configured-server adapter
The production hosts adapter was only typechecked; this pins that it resolves configured servers
by exact id only (never a name or a removed server), fences by pairing revision and runtime
identity, and stops reporting retirements once unsubscribed.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): re-record the repo-metadata goldens for SSH host health
The settled metadata state now carries hostHealthById, and an SSH-only catalog now reads the
target summaries so a lone unhealthy host can be badged. Scoped re-record via rpc-recording.mts.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): tighten the desktop relay after elegance review
Reuses the shared E2EE socket opener, folds relay state into fewer fields, never sends a request
whose link ended while opening, passes failure codes through, relays only this desktop's own
phones, and drops the unused host-state reader until S2 needs one.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): bound relay frames and stop sticky relay verdicts
Restores the 8 MiB frame cap on every remote runtime socket, re-checks an update-needed server on
each new link so an in-place upgrade relays, answers a phone the server just declined without
syncing again, and ends a link whose mirrored capability update cannot be queued.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): run targeted desktop-owned calls on the desktop
The phone tags every call on a workspace screen with its execution host, so a
paired-desktop method carrying `runtime:<env>` now runs locally instead of
failing 'forbidden'. The Mac stays the only reader of the routing table.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): list the desktop's configured servers and their last workspace rows for the phone
The phone needs the same host list the desktop shows, including servers that are offline or
too old to relay to. mobileRelay.hosts.list/worktrees answer from the desktop's own status and
its own worktree.ps fetches, cached per server and fenced by pairing, never from relayed replies.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): list server rows through one desktop fetch, with no side effect on listing
The phone now always lists a server's rows through mobileRelay.hosts.worktrees, so the
desktop's own fetch is both the live source and the cache, and stamping stays in main.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): fetch a server's full workspace list within a bound, and forget removed servers
The desktop now asks for the phone's full-catalog limit, answers within 5s with the last rows
when a server hangs, drops a server's rows when it is removed or re-paired, and checks the
server's identity after the fetch so a reply from before a re-pair is never served.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): list managed servers as the desktop sidebar does, and hide rows mid-delete
A server Orca manages on an SSH host now lists once under that host's name, or not at all while
the host is reached over the SSH relay, using the sidebar's own folding. Rows the server is
deleting are left out at reply time, as a phone asking the server itself would see.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): keep a disconnected server's last rows, and never join a re-paired server's fetch
Retirement also fires on a manual disconnect or managed-server update, which wrongly emptied
the phone's list; rows are now pruned only once a server is no longer configured. Fetches are
shared per server identity, so a poll after a re-pair asks the new server right away.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(ci): run the remote-runtime WebSocket opener test on Node
Under the bun vitest project, `ws` resolves to Bun's built-in client, which
ignores maxPayload, so the frame-cap test hung. The opener only ships on
Node/Electron, so run its test in the Node runtime project like its siblings.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile-relay): derive the phone allowlist from the routing table
Adding a phone method now takes one edit (its route). A server-targeted call on a
legacy non-E2EE socket is refused instead of running locally, and only
execution-host methods enter the relay.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile-relay): drop unread freshness fields from server workspace lists
The phone reads only the rows, so stale, fetchedAt, totalCount and truncated
were dead wire weight. The desktop still serves a server's last rows while
the server is unreachable.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(runtime): classify the mobileRelay RPC namespace as host-owned
main now requires every runtime RPC namespace in the host-operation ownership table; the relay-hosts methods are served by the desktop the phone is paired to.
|
||
|
|
ff5c284cc1 |
feat(mobile): relay a paired phone's server-workspace requests through the desktop (hidden) (#26435)
* feat(runtime): let a paired desktop register its phones as delegated mobile devices A desktop paired to this runtime can now call pairing.delegatedMobileDevice.sync to make each of its phones an ordinary mobile-scope device it owns, so the phone mirrors that host through the desktop. Children are never pending, inherit the parent's reach, and die with the parent's grant. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(runtime): move the mobile device revoke primitive below dispatch Dispatch binds the caller-bound delegated-device context, so the revoke path it needs now lives in a layer under it as a pure move; this drops the abstract method seam. No behaviour change. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(runtime): keep a runtime grant until its delegated phones are revoked A failed child cleanup now leaves the parent in place so a retry can still find the children, and revokeRuntimeAccess lives beside the revoke it cascades to. Adds restart, rename and bounds coverage, shares credential minting, and drops the timing-dependent census. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(runtime): fail a delegated sync whose dropped phone cannot be revoked Matches the parent revoke: a child whose cleanup could not be saved is kept and the sync errors before minting anything, so the desktop retries instead of believing the server matches its list. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): relay a paired phone's server-workspace requests through the desktop A phone mirrors its desktop, including workspaces on the desktop's configured servers. The desktop now forwards a request that names `executionHost: runtime:<env>` to that server over one socket per (phone, server), signed in as the phone's delegated device; replies pass through untouched except stream ids. Hidden until the phone client ships: the relay capability is not advertised yet. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile): state the desktop relay contract in its entry module main dropped docs/reference, so the relay's contract lives beside the code. Also pins that a server whose delegated sync fails (e.g. delegated_device_revoke_failed) is unavailable until a later request syncs it again. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): pin the relay's configured-server adapter The production hosts adapter was only typechecked; this pins that it resolves configured servers by exact id only (never a name or a removed server), fences by pairing revision and runtime identity, and stops reporting retirements once unsubscribed. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): tighten the desktop relay after elegance review Reuses the shared E2EE socket opener, folds relay state into fewer fields, never sends a request whose link ended while opening, passes failure codes through, relays only this desktop's own phones, and drops the unused host-state reader until S2 needs one. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): bound relay frames and stop sticky relay verdicts Restores the 8 MiB frame cap on every remote runtime socket, re-checks an update-needed server on each new link so an in-place upgrade relays, answers a phone the server just declined without syncing again, and ends a link whose mirrored capability update cannot be queued. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): run targeted desktop-owned calls on the desktop The phone tags every call on a workspace screen with its execution host, so a paired-desktop method carrying `runtime:<env>` now runs locally instead of failing 'forbidden'. The Mac stays the only reader of the routing table. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(ci): run the remote-runtime WebSocket opener test on Node Under the bun vitest project, `ws` resolves to Bun's built-in client, which ignores maxPayload, so the frame-cap test hung. The opener only ships on Node/Electron, so run its test in the Node runtime project like its siblings. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile-relay): derive the phone allowlist from the routing table Adding a phone method now takes one edit (its route). A server-targeted call on a legacy non-E2EE socket is refused instead of running locally, and only execution-host methods enter the relay. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
38aa54f716 |
fix(doc-preview): serve authorized web fonts as binary bytes (#26849)
Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: noan98 <187374956+noan98@users.noreply.github.com> |
||
|
|
08c8d30f10 |
fix(e2ee): stop dropping terminal output on a backed-up legacy connection (#26528)
* fix(browser): retry a screencast frame a viewer's socket refused An idle page sends no next frame, so a refused last frame left the viewer on a stale picture. The desktop's relay leg to a server uses legacy E2EE, which drops on a full socket where a direct v2 phone queues; retrying the newest pending frame restores parity. Retries never count toward ghost eviction. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(e2ee): queue legacy binary replies with text instead of dropping them Legacy E2EE (every direct phone, the desktop's server link) sent binary straight to the socket and silently dropped it once the outbound budget was full, so terminals lost output and binary could overtake queued text. Binary now shares the ordered text queue and closes on overflow like v2; the lossy screencast opts into dropWhenBacklogged and its retry delivers the newest frame after a drain. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(e2ee): keep a backlogged screencast viewer instead of evicting it as a ghost A lossy frame skipped on a busy socket returned false like a dead socket, so a long backlog evicted a live viewer. Skips now return 'backlogged', which retries without counting toward eviction; legacy judges idleness on the sealed size before encrypting, so skipped frames cost no encryption. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(browser): expect the ready gate to forward screencast send options The pre-ready gate now passes the per-frame send options through to the socket sender, so the replayed startup frame arrives with an options slot. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(browser): share the streamed-guest double across screencast tests Main's renderer throttle lease now calls setBackgroundThrottling on the guest, so the bare { isDestroyed } guest in two branch tests threw. One shared double keeps all three screencast suites on the shape the lease needs. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
de00a4f03c |
feat(runtime): let a paired desktop register its phones as phone devices (#26427)
* feat(runtime): let a paired desktop register its phones as delegated mobile devices A desktop paired to this runtime can now call pairing.delegatedMobileDevice.sync to make each of its phones an ordinary mobile-scope device it owns, so the phone mirrors that host through the desktop. Children are never pending, inherit the parent's reach, and die with the parent's grant. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(runtime): move the mobile device revoke primitive below dispatch Dispatch binds the caller-bound delegated-device context, so the revoke path it needs now lives in a layer under it as a pure move; this drops the abstract method seam. No behaviour change. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(runtime): keep a runtime grant until its delegated phones are revoked A failed child cleanup now leaves the parent in place so a retry can still find the children, and revokeRuntimeAccess lives beside the revoke it cascades to. Adds restart, rename and bounds coverage, shares credential minting, and drops the timing-dependent census. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(runtime): fail a delegated sync whose dropped phone cannot be revoked Matches the parent revoke: a child whose cleanup could not be saved is kept and the sync errors before minting anything, so the desktop retries instead of believing the server matches its list. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(runtime): refuse pairing admin to phones delegated through a paired desktop A delegated child phone could call the mobile-allowlisted pairing-admin methods (push registration, relay provisioning) that its parent desktop is never granted. The caller scope now marks delegated phones and denies them that permission. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
7abee44e78 |
feat(mobile): show SSH host health on the workspace row badge (#26415)
* feat(mobile): show SSH host health on the workspace row host badge
Rows on an SSH host the desktop cannot reach looked healthy on the phone. The badge now
adds the desktop sidebar's own word ("devbox · Disconnected"), derived by the same shared
function from the connection status ssh.listTargetSummaries already sends; old desktops keep today's badge.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): name an unhealthy SSH host even in a single-host list
The desktop card shows SSH status on any host count, so the phone now reads target summaries
whenever a repo is on SSH and badges an unhealthy host alone too. Labels and health share one
target-to-host keying, and a hook-level test pins the reply-to-row wiring.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): clear stale SSH host badges and announce them without the dot
A catalog that no longer spans hosts or holds an SSH repo now clears the health and label maps,
so a host that left cannot keep badging rows. The badge gets a screen-reader label ("devbox,
Disconnected") instead of reading the middle dot.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): type the single-host health fixtures inline so the test tsc passes
Fixtures hoisted into consts widened their health maps to string, which the now-required
hostHealthById rejects under the tests-typecheck ratchet; passing them inline keeps the union.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): re-record the repo-metadata goldens for SSH host health
The settled metadata state now carries hostHealthById, and an SSH-only catalog now reads the
target summaries so a lone unhealthy host can be badged. Scoped re-record via rpc-recording.mts.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
|
||
|
|
2b4b60a87e |
refactor(relay): delete the SSH relay server, its build assets and tests (#27215)
* wip: delete the SSH relay server and its build, assets and tests * wip: drop main/relay plugin conformance; WSL reader lookup test * fix: drop the emptied relay bench; prune baselines * test: keep the native halves of tests that also covered the relay * chore(ci): drop shard timings for the deleted relay server tests * fix: keep the directory listing budget that darwin terminal names now import --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
c2c63e7f9a |
Avoid downloading unchanged paired-runtime session history (#27383)
* perf: avoid retransferring unchanged paired-runtime session history Keep selected finite-depth history refreshes content-conditional while preserving scans, full old-peer replies, force refreshes and the existing cache lifetime. Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com> * perf(ai-vault): keep full host list behind unchanged replies for cache readers --------- Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com> |
||
|
|
d07ac4b1bc |
perf(worktrees): skip unchanged lineage after sidebar order saves (#27384)
* perf(worktrees): skip unchanged lineage after sidebar order saves Mark order-only remote events and skip redundant lineage downloads only after a successful current-host read. Preserve independent cold, failed, pending and structural refreshes, including reconnect and re-pair. Addresses #27268. Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com> * fix(worktrees): notify after recording SSH-created worktree lineage Paired clients now skip lineage refetch on sort-order-only events, so the pre-lineage create notify on the SSH route could leave lineage stale until the next structural change. --------- Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com> |
||
|
|
ff27338299 |
refactor(ssh): connect fails for hosts orcad can't serve; remove the relay client path (#27214)
* wip: connect fails for hosts the managed server can't serve; remove relay session, deploy and relay UI * wip: delete relay-only client modules left unreachable, prune their tests and gates * wip: format * wip: exhaustive unserved message * wip: drop relay attestation test for the deleted ownership proof * fix: prune stale baselines; type the test transport stub * fix(ssh): census proves leftover relay leases; name the remedy for older terminals; translate every connect-failure surface * test(ssh): leftover leases defer to the host census * test(e2e): remove relay-path SSH e2e specs and their CI lanes * test(e2e): drop the auto-convert case that built its relay era through a live relay * test(e2e): mark relay-era browser and catalog journeys fixme until they seed a profile * test(e2e): seed relay-era browser and catalog journeys from a saved profile The three journeys built their old-version state through a connect without an orcad template, which now refuses the host. They now start from a seeded relay-era profile, so the first connect converts the host as an upgrading user's would. * chore(ci): drop shard timings for the deleted relay tests * test(e2e): carry a relay-era login cookie across the browser conversion; own every launch * test(ci): drop the deleted SSH layout oracle from its route * test(e2e): open the retained browser after conversion, retrying a pre-connect card * test(e2e): bind the relay-era browser partition before seeding its cookie A relay-era build always recorded the partition binding before a page stored data in it. Seeding the cookie into an unbound partition made the binding store refuse it (browser_route_partition_binding_store_invalid), so the retained tab showed the generic routing card and Retry could never clear it. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude <noreply@anthropic.com> |