Skip to content

fix(native-chat): a paired server admits structured chat by client capability, not its own chat setting - #24203

Merged
brennanb2025 merged 18 commits into
mainfrom
brennanb2025/structured-chat-capability-admission
Oct 2, 2026
Merged

brennanb2025 merged 18 commits into
mainfrom
brennanb2025/structured-chat-capability-admission

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 31 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​954 $\color{#cf222e}{\Huge{\mathbf{−}}}$​456 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​498
Prod 43 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​410 $\color{#cf222e}{\Huge{\mathbf{−}}}$​323 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​87

ELI5

Orca can show a Claude or Codex agent as a structured chat: Orca runs the agent itself and draws its messages as a chat, instead of drawing a chat over an agent running in a terminal (the older terminal-backed chat). A paired Orca server is another computer running Orca that your phone or desktop is connected to.

Until now, a paired server used its own owner's chat setting to decide whether anyone could use structured chats on it, and whether chats that already existed there were shown at all. After this PR, the server admits a structured chat when the connecting app says it can handle one. The chat setting only decides what new agents open as. Chats that already exist always show.

This is step 1 of a 3-PR plan to make structured chat work for workspaces on a paired Orca server:

  1. This PR (fix(native-chat): a paired server admits structured chat by client capability, not its own chat setting #24203): the server admits structured chats based on what the connecting app supports, not on its own chat setting. The setting now only picks what new launches open as.
  2. fix(native-chat): the desktop declares structured chat support to paired Orca servers #24204: the desktop tells paired servers it supports structured chat, so chats running there show up on the desktop and can be used.
  3. feat(native-chat): open structured chats on the paired Orca server that owns the workspace #24205: the desktop opens new structured chats on the paired server that owns the workspace.

This PR is safe to land alone. Older desktops, released phones, the browser client and older servers all keep working (see "Wire compatibility").

What Changed

The problem, as a user saw it

The server owner's setting "Use updated structured native chat" (experimentalStructuredNativeChat) controlled three things it was never meant to control:

  • Paired apps were refused. With the setting off on the server, every structured chat request from a phone or desktop was refused (structured_agent_session_unsupported). The user's own choice on their own device didn't matter.
  • Existing chats vanished from phones. The server hid structured chat tabs from every paired app while its setting was off. A chat opened while the setting was on disappeared from phones as soon as it was turned off.
  • Existing chats vanished from the server's own window. Turning the setting off removed this machine's chat tabs from its window. After a restart they were not restored, the "continue your chats" offer was hidden, and so were the chat behaviour settings.

Why it happened: the server treated its own setting as admission control for every caller and as a filter on what exists. But the setting is the owner's launch preference: how they want new agents to open. Whether a new agent is a chat is the launching app's call, and a chat that exists is a chat that exists.

Scope

  • Surfaces:
    • how a paired Orca server answers structured chat requests (agentSession.*) and which tabs it lists (session.tabs.*) for phones and desktops;
    • how this machine's own window shows its own chats: the chat list mirror, restore at startup, the "continue your chats" offer after a restart, and the chat settings section;
    • the setting's description.
  • When you would notice: only on a machine whose structured chat setting is off and that holds structured chats, or where a paired app creates one. With the setting on, nothing changes.
  • Not affected:
    • terminal agents and the terminal-backed chat;
    • SSH and WSL workspaces, which stay terminal-backed;
    • what a new agent launched on this machine opens as, which still follows the setting;
    • apps that never said they support structured chat (older desktops, the browser client, older phones), which are refused and shown fallback rows or nothing, as before;
    • older servers, which keep today's behaviour.
  • On its own, this PR does not let a desktop use chats on a paired server. The desktop does not tell paired servers it supports them until fix(native-chat): the desktop declares structured chat support to paired Orca servers #24204.

After this PR, as a user sees it

Before After
A phone or desktop asks a paired server (setting off) to drive a structured chat Refused Admitted, if the app says it supports structured chat
Chats that exist on a server whose setting is off Hidden from phones and from the server's own window Shown everywhere that can read them
Turning the setting off Removed this machine's chat tabs and skipped restoring them Changes only what new agents open as. The setting now reads: "Open new Codex and Claude agents as structured chats. Off opens them in the terminal-backed chat. Chats that already exist stay as they are." (5 languages)
"Continue your chats" offer after a restart, and the chat behaviour settings Only with the setting on Also whenever this machine holds chats
A released phone's "+" on a server whose setting is off Opens a terminal Unchanged: opens a terminal
Startup on a machine that has never had a chat No chat work Still no chat work. The only addition is one in-memory yes/no question to the main process.

Mechanism

  • Admission by what the app supports.
    • One check, supportsStructuredAgentSessions (structured-agent-session-policy.ts), now decides every agentSession.* method, tab projection and restore of structured tabs: did the connecting app advertise agent-session.structured.v1?
    • The host setting is no longer an input. Calls from inside the same Orca process (its own CLI socket) negotiate nothing and are always admitted, as before.
    • The listener that re-sent tabs to phones whenever the setting changed is removed, because no projection depends on the setting any more.
  • New two-way capability agent-session.structured.client-launch-mode.v1.
    • When the server advertises it, it means "I admit by what the app supports, not by my own setting". Older servers advertised agent-session.structured.v1 while still gating on their setting, so that capability alone could not tell an app its chat would be admitted.
    • When an app advertises it, it means "I choose each launch's mode myself".
    • In this PR the server and the desktop's own window (DESKTOP_RENDERER_RUNTIME_CLIENT_CAPABILITIES) advertise it. With it, retrying a failed existing chat in the server's own window works with the setting off. fix(native-chat): the desktop declares structured chat support to paired Orca servers #24204 adds it for paired desktops.
  • Legacy rule for released phones.
    • Phones released before the server-side agent.launch existed ask agentSession.createSupport "should this '+' be a chat?". They read any refusal as "open a terminal".
    • For an app that does not advertise the launch-mode capability, requireStructuredCreateSupportAdmission still answers createSupport with the server's setting. It returns the same error code and reason as before (clientCapabilityMissing), and an unreadable settings store still reads as off.
    • Every other method ignores the setting.
    • When it can be removed: once the oldest supported phone build launches agents through agent.launch. The code comment names this exit.
  • Named cleanup gate.
    • Close, cancel, unsubscribe and release keep their own gate, requireStructuredCleanupHost. It asks only for the capability and a running chat host.
    • Today it matches the gate for new work. It stays separate so a condition added later for new work can never block someone from stopping a chat they own.
  • "This machine holds structured chats" signal.
    • The main process derives it live from the chat record store: at least one chat record, or a records file still waiting to be imported. Building the chat host alone does not count, because session history and phone launches build it too.
    • The renderer reads it through a new IPC, app:holdsStructuredAgentSessions (ask), plus app:structuredAgentSessionsHeldChanged (push when it changes, for example when a paired app creates the first chat here).
    • This window's chat mirror, startup restore, "continue your chats" offer and chat settings run when the setting is on or the machine holds chats (src/renderer/src/runtime/local-structured-chats.ts). The browser client has no runtime of its own and always answers no.
    • The old turn-the-setting-off teardown and its now-dead code are removed.
  • seedOptions on the createSupport reply.
  • Capability list moved to its own module.
    • ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES moved, byte for byte, from protocol-version.ts to src/shared/electron-remote-runtime-client-capabilities.ts.
    • The new constant would have pushed protocol-version.ts over the file-length limit.

Why

The launching app decides how a new agent opens. The server refuses only what it cannot run. Its own preference fills in only the choices an app leaves to it. Alternatives considered:

  • Keep the server setting as a veto. Someone else's preference on another machine would silently override your choice, and it hides chats that exist.
  • Add a second server setting, such as "allow remote structured chat". It adds a setting nobody asked for, and still hides existing chats.
  • Send the launch mode as a field on each request instead of a capability. Older servers reject unknown request fields, so an app would still need a capability to know when it may send the field. The capability alone is simpler.
  • Run the window's chat mirror whenever the chat host exists, or always. Many non-chat paths build the host. The mirror also rides the replication stream paired phones use, so every desktop would pay for it.
    • Keying on "a records file exists on disk" is almost the same fact, but less precise.
    • Deriving it in the renderer from saved tabs misses a chat a paired app creates here while no local tab exists.
    • So the signal is a main-process fact plus a change push.
  • Split this PR. The renderer half cannot land first, because the server would still hide the chats from it. The server half alone would show a server's chats on phones while its own window hid them, which is a state nobody chose.

Differences from the common pattern

  1. Mixed versions are negotiated with one capability string per feature, not one protocol version that blocks on mismatch.
    • Intended. Paired apps and servers update independently, and released phones cannot be forced to update.
    • The server-advertised half of the new capability follows the common form: the server publishes an optional feature flag, and its absence means an older server.
  2. The server reads the connecting app's capabilities to change one answer.
    • agentSession.createSupport follows the server's setting for apps that do not choose the launch mode. The common pattern never branches on the client.
    • Temporary. It exists only for released phones that ask createSupport to choose. It can be removed once the oldest supported phone launches through agent.launch. A removal ticket is still to be filed.
  3. The server's setting still picks the mode for launches that leave the choice to the server.
    • These are phones' agent.launch, the CLI, orchestration workers, and the legacy createSupport answer.
    • Intended. This matches the common order: an explicit client choice always wins, and the server's saved preference fills only what the client left open.
    • Orca detects "the client chose" from a capability on the connection instead of a field on each request (see "Why").
  4. The legacy refusal keeps the reason clientCapabilityMissing although the real cause is the server's setting. The common pattern names the actionable cause.
    • Intended. It is byte-identical to what released phones already receive, and they read only "not supported". Newer apps never get it.
  5. The window's own chat mirror runs only when the setting is on or this machine holds chats. It uses a new main-process signal and IPC, where the common pattern keeps one always-on session list.
    • Temporary. It exists because the window's mirror rides the replication stream built for paired phones, which would cost every desktop that never used chat.
    • Safe meanwhile: the signal is re-read from the record store on every subscribe, and a change push corrects it.
    • Records are never deleted, so a machine that once held a chat keeps the mirror on (accepted cost).
    • Removed by the follow-up that gives the window a cheap local chat feed.
  6. The server's saved model and options for a new chat ride the createSupport reply, not a read model.
    • Intended. The launch path already makes this call right before create, and both read one resolver. So the picker shows what create will run, with no extra round trip.
    • Move it to a read method if a picker ever needs it before a launch.
  7. One capability string carries two independent statements: "I admit by client capability" from the server, and "I choose each launch's mode" from an app.
  8. Stopping work (close, cancel, unsubscribe, release) has its own gate that never takes admission conditions.
    • Intended. It equals the work gate today and is kept separate so a future admission condition can never strand a running agent.

Wire compatibility

Only a new optional capability and a new optional reply field are added. The new IPC is between the main process and the renderer of one build and never crosses the wire.

  • Older server, any app: unchanged. It does not advertise the launch-mode capability, ignores it from apps, and never sends seedOptions.
  • Older desktop paired to a new server: it never advertised structured chat, so it is refused and structured tabs are hidden from it, as before. Desktops read only supported from createSupport.
  • Released phone from before the server-side agent.launch existed:
    • createSupport gives the same answer as before, so "+" opens a terminal while the server's setting is off.
    • New: it now sees and can use the chats that already exist on that server.
  • Current phone: launches through agent.launch, where the server's setting decides. It calls createSupport only against old servers.
  • Phones without structured chat support:
    • Existing chats show as "Update to view" rows, now also on servers whose setting is off (before, they were hidden).
    • Those phones still cannot drive or delete the chats.
    • Phones that support only Codex chats see Claude chats as "Open on desktop" rows.
  • Browser client: advertises no structured support, so it is refused as before. It never runs the local chat mirror.
  • seedOptions: sent only when a chat is supported and the seed is not empty. Phones and older desktops read only supported.

Linked Issue

None. This is a maintainer PR with no pre-existing issue.

Visual Proof

Live run on a separate Mac (dev build of this branch at 7fdebc8aab3, isolated home and app data, real Claude). Every scenario passed, with no console errors.

Scenario What the user sees
Setting off, machine never had a chat "+" → Claude opens a terminal, as before
Setting turned on (no restart) A structured chat opens and answers
Setting turned off again The existing chat still shows; a new launch opens a terminal
Restart with the setting off The chat is restored, no "Session restore failed"

Setting on: a structured chat answers, with no restart, on a machine that had never held one
q2-structured-chat-pong.png

Setting off again: the existing chat still shows as a chat
q3-existing-chat-still-shows.png

Setting off: a new Claude launch opens the terminal-backed chat
q3-new-claude-opens-terminal.png

Chat settings stay reachable while the machine holds chats
q3-settings-off-chat-settings-visible.png

After a restart with the setting off: the chat is restored, and a follow-up still gets an answer
q4-restored-chat-after-relaunch.png
q5-restored-chat-followup-setting-off.png

Retrying a failed chat could not be reached in the UI without breaking the Claude binary; it is covered by desktop-renderer-runtime-capabilities.test.ts and the admission tests instead.

Testing

New and updated tests, and what they prove:

  • structured-agent-session-admission.test.ts
    • With the server setting off, every agentSession.* method is served to an app that advertised the capability and refused to one that did not.
    • create works for a paired app. Close stops the agent and retires its tab, and cancel lands.
    • An app without the launch-mode capability gets the old createSupport answer, and an unreadable settings store reads as off.
    • seedOptions matches what create uses and is absent when the chat is unsupported.
  • structured-agent-session-policy.test.ts, structured-agent-session.test.ts: the single admission rule and the legacy-rule predicate, for phones, desktops and in-process callers.
  • session-tab-agent-status-projection.test.ts, session-tabs-structured-restore.test.ts: tabs and restore follow the app's capability, not the setting. Phones without support get fallback rows, and paired desktops without support get nothing.
  • structured-agent-session-held-signal.test.ts, structured-agent-session-registry.test.ts
    • The "holds chats" signal stays false when session history or a phone launch builds the host.
    • It turns true on the first chat without a restart, and is true as soon as saved chats are restored.
    • A failing listener never breaks the host install.
  • local-structured-chats.test.ts, local-structured-session-tabs-sync-hook.test.ts
    • A machine that never had a chat runs no startup chat check and holds no subscription.
    • Chats this machine holds are restored and mirrored with the setting off, and stay mirrored when it is turned off.
    • The mirror starts when a paired app creates the first chat here.
    • A change pushed during the first query is kept.
    • The browser client never runs the mirror.
  • ExperimentalPane.test.tsx, NativeChatResumeStatusSegment.test.tsx: the chat settings and the "continue your chats" offer appear when the machine holds chats, whatever the setting says. A paired server's chats do not make this machine ask its own runtime.
  • desktop-renderer-runtime-capabilities.test.ts, runtime-availability.spec.ts: the desktop's own window and the server advertise the launch-mode capability.
  • remote-runtime-client.test.ts: covers the moved capability list.
  • tests/e2e/cross-version-wire/cross-version-agent-session-wire.unit.test.ts: against the real released build's capability list, a released phone asking a server whose setting is off still gets exactly the old refusal, and a client that chooses its own launch mode is told yes plus seedOptions; the server advertises the new capability.

Results:

  • pnpm tc:node and pnpm tc:web pass; the targeted test files (275 tests) and the CI cross-version job (66 tests) pass locally. The last commit only adds cross-version test cases.

  • A separate review proof ran the real dispatcher and projection for 10 kinds of app × setting on/off. Every row came out as expected; that proof is not committed.

  • All runs were on macOS. The change has no platform-specific code.

  • I manually tested these changes (live run on a separate Mac; see Visual Proof)

  • Automated tests added/updated, or explained why not below

AI Disclosure

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Security:
    • App capabilities are self-declared and were never an authorization boundary. Any paired app can already run any command on the server through terminal.create.
    • A structured chat is narrower than that. Its environment, permission mode and account come from the server's own settings, and create ignores options sent by the app.
  • Cross-platform: no platform-dependent code.
  • SSH: unaffected. SSH workspaces have no Orca runtime of their own and stay terminal-backed.
  • Mobile: see "Wire compatibility". Released phones keep today's launch behaviour.
  • Performance:
    • Projecting each tab update is slightly cheaper, because it no longer reads settings.
    • On a server whose setting is off, a phone's first tab list now runs the saved-chat restore once per process. A server with no saved chats returns before building anything.
    • A machine that once held a chat keeps the window's mirror on every launch, because records are never deleted.

Known follow-ups (deferred, not in this PR)

  • A failed startup chat check can block session saving.
    • If restoring this machine's chats fails at startup (for example, a database read error), Orca shows "Session restore failed / Changes won't be saved until restart".
    • That was already true with the setting on. It now also applies to anyone whose machine holds chats with the setting off.
    • It has not been reproduced. The fix is to make the check non-fatal without letting the session saver drop the saved chat tabs.
  • Remove the legacy createSupport rule once the oldest supported phone launches through agent.launch. A ticket is to be filed.
  • Give this window a cheap local chat feed instead of riding the paired-phone replication stream. That removes the "holds chats" signal, its IPC, and the cost of keeping the mirror on forever after a first chat.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…ity, not its own chat setting

A host's experimentalStructuredNativeChat decided whether any paired client could reach
agentSession.* at all, and whether session.tabs.* showed it structured tabs. That setting is the
host user's own launch preference: whether a new agent opens as a chat or a terminal is decided by
whoever launches it. Using it as admission control meant a client whose own preference was
"structured chat" was refused on a host whose preference was "terminal", and chats opened while
the setting was on were withheld from mobile once it was turned off.

The gate now asks one thing: did the client advertise agent-session.structured.v1 (in-process
callers negotiate nothing and are always admitted). Tab projection and restore follow the same
rule. With the setting no longer gating anything, the separate cleanup gate (close, cancel,
unsubscribe, release), which existed only so those kept working after the setting was switched
off, is identical to the main gate and is folded into it. The settings listener that republished
tabs when the setting changed is removed, since projection no longer depends on it.

The host setting still picks the default for launches that start on the host itself
(agent.launch from mobile, orchestration worker-start).
…nes and old servers keep terminals

Hosts advertise agent-session.structured.client-launch-mode.v1: they admit
structured sessions by client capability alone. A remote client that does
not advertise it (phones released before agent.launch) asks createSupport
to pick the launch mode, so the host keeps answering that with its own
setting, exactly as before. Cleanup methods keep their own named gate so a
future admission condition cannot make close or cancel refusable.
…chat setting says

The structured chat setting decides only what new agents open as. With it
off, this machine's structured chats used to be hidden while the host,
which no longer reads the setting, still reported them to the workspace
activation gate, so a workspace holding only a chat opened empty. The
local chat mirror and its startup restore now run whatever the setting
says, the continue-after-restart offer follows the chats that exist, and
the setting's copy says it applies to new agents.
…ed-chat-capability-admission

# Conflicts:
#	src/main/runtime/rpc/methods/structured-agent-session-admission.test.ts
Round 1 ran the local chat mirror for everyone so existing chats show
whatever the setting says. That gave every desktop a permanent
session-tabs listener, which turns on the runtime's phone replication
paths, plus two full session-tab censuses at startup, and made the
browser client mirror its remote host a second time.

The runtime now says whether it holds structured chats: its structured
host is built only when saved chats were restored at startup or a client
created one here, and it announces the moment one is built. The mirror,
the startup restore and the continue-after-restart offer run only when
the setting launches chats or the host holds some, and never in the
browser client. A chat a paired client creates here with the setting off
still appears at once. The chat behaviour settings show wherever chats
exist, and the setting's copy says it picks what new agents open as. The
toggle-off teardown this made dead is removed.
… not once its host is built

Session history, resume preparation, terminal resume commands and replay-safe phone launches all
build the structured host for users who never had a chat, which turned on the chat mirror and the
structured-only settings rows until the next restart. The signal is now derived from the host's
records (or a records file still owed its import) and pushed when the first chat is restored or
created. A throwing listener no longer fails the install that fired it.
…at on this host starts with

A chat on a paired server starts with the server's saved model and options, which the desktop could
not read, so its picker showed a guess. createSupport's answer, which the desktop already waits for
before a paired launch, now also carries that seed as a new optional field (older clients ignore it).
Create and createSupport read it through one resolver so they cannot drift.
…nto its own module

Merging main left protocol-version.ts one line over the max-lines limit on this branch. The list of
capabilities the desktop advertises to a paired host moves, unchanged, into
electron-remote-runtime-client-capabilities.ts, the module the next PR in the stack already uses
for it; importers point there.
…ed-chat-capability-admission

# Conflicts:
#	src/main/ipc/desktop-renderer-runtime-capabilities.test.ts
… mode, so retrying an existing chat works with the setting off
…eateSupport answer; a launch-mode client gets supported plus the seed
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review status: #24203 (step 1 of 3)

The problem. A paired Orca server used its own owner's chat setting to decide whether anyone could use structured chats on it, and to hide chats that already existed. With the setting off, a desktop or phone asking for a structured chat was refused, and existing chats vanished from phones and from the server's own window.

What changes for users. The server now admits a structured chat when the connecting app says it supports one. The chat setting only decides what new agents open as; chats that already exist always show, locally and on paired apps (owner's decision). Released phones keep today's behaviour: "+" still opens a terminal on a server whose setting is off. A machine that has never had a chat does no new work at startup.

Fixed during this review (on top of the fixes from the earlier whole-stack review)

  • The cross-version compatibility CI check was failing: its test server stub lacked the new saved-model lookup that createSupport now calls. Fixed, and main merged in.
  • On its own, this PR left the server's own window not saying "I choose each launch's mode", so with the setting off, retrying an existing chat that had failed was refused, contradicting "existing chats stay". The desktop's own window now advertises it (one line moved here from fix(native-chat): the desktop declares structured chat support to paired Orca servers #24204). Every new launch is still decided by the desktop's setting before the server is asked, so no new launch can turn into a chat because of this.
  • The cross-version suite now runs the case the new capability exists for, against the real released build's capability list: a released phone asking a server whose setting is off gets exactly the old refusal; a client that picks its own mode gets "yes" plus the server's saved model.
  • The compatibility comment now names when the released-phone rule can be removed: once the oldest supported phone launches agents through agent.launch.
  • PR description rewritten to cover the two-way capability, the released-phone rule, the new setting meaning, the "holds chats" signal and its IPC, seedOptions, and the moved capability list.

Deferred (not in this PR)

  • If restoring this machine's chats fails at startup, Orca shows "Session restore failed" and stops saving. That was already true with the setting on; it now also applies to people holding chats with the setting off. Not reproduced. Making it non-fatal has to keep saved chat tabs from being dropped, so it is a separate fix.
  • A ticket to remove the released-phone createSupport rule.
  • Giving this window a cheap local chat feed, which would remove the "holds chats" signal and the always-on mirror after a first chat.
  • Small test and naming cleanups: stale "settings-gated" test comments, a gate-classification fixture that misses 7 methods, a test option that no longer does anything, a counter only tests change, a misnamed file, chat behaviour settings still nested under "Chat UI", and the Spanish catalog missing these keys (as on main).

Verified

  • Three independent reviews (server admission across 10 kinds of app × setting on/off, run through the real request handling; setting meaning and startup cost; architecture against the common pattern). No P0 or P1. The architecture review recommends landing this PR whole.
  • Readiness check passes: lint clean on all changed files, all translation checks pass, no platform-specific code.
  • Typecheck (main and renderer) passes; 275 targeted tests and the 66-test cross-version job pass locally.
  • Live run on a separate Mac at 7fdebc8aab3 (screenshots in the description): never-used machine opens a terminal; turning the setting on opens a chat that answers without a restart; turning it off keeps the existing chat and its settings while new launches open terminals; after a restart the chat is restored and still answers.
  • The live run used a Claude worker because the Grok worker was out of usage.

Not verified

  • Retry of a failed chat in the live app: the UI offers no way to reach it without breaking the Claude binary. It is covered by unit tests.
  • A real released phone or a real older server against this build. Covered by the cross-version suite, not run on devices.
  • Startup time on a loaded machine.
  • The final commit only adds cross-version tests and was not part of the live run.

CI at the final head ccf37e3d046: all checks pass (typecheck, static analysis, cross-version wire compatibility, all 5 test shards, verify). The branch merges cleanly with main.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 1, 2026 06:32
@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Changes how paired clients launch structured chat sessions.

The PR appears safe to merge; no actionable regression was identified.

Summary

This PR separates structured-chat admission and visibility from the server owner’s launch preference.

  • Structured-session RPCs and tab projection now follow the connecting client’s capability, while legacy createSupport retains the host-setting answer for clients that leave launch mode to the server.
  • A record-backed held-chats signal keeps existing local chats mirrored, restored, and available in restart and settings UI when the setting is off.
  • The server advertises the new launch-mode capability and optionally returns saved launch seed options.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Client[Connecting client] --> Cap{Structured capability?}
  Cap -- No --> Refuse[Refuse session RPCs]
  Cap -- Yes --> Session[Admit session RPCs]
  Client --> Support[createSupport]
  Support --> Mode{Chooses launch mode?}
  Mode -- Yes --> Host[Check host/workspace support]
  Mode -- No --> Setting[Apply server launch preference]
  Records[Saved or new chat record] --> Held[Held-chats signal]
  Held --> UI[Local mirror, restore, restart offer and settings]
Loading

Reviews (1) · Last reviewed commit: "test(cross-version): a released client s..."

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (2)
docs/reference/remote-wire-compatibility.md — configured
AGENTS.md — auto-discovered
📝 Walkthrough

Walkthrough

The change adds a client launch-mode capability and updates structured-session admission, launch seed responses, and session-tab projection. The runtime now reports whether it holds structured sessions through IPC. The renderer uses that state with the structured-chat setting to control startup restoration, tab synchronization, and restart offers. Electron remote-runtime capabilities move to a dedicated module.

Priority: ➖ Normal

Merge Risk: 🟡 Moderate · up to ccf37

With saved structured chats and the setting off, a startup restore failure can now disable session saving until restart. Contain that optional restore failure before merging.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to ccf37

The access-policy change does not demonstrate an authorization bypass. The main concern is failure containment: restoring an existing chat can now leave workspace-session saving disabled after a startup error, even when structured-chat launches are turned off.

Retained concerns

  • Medium · reliability · observed: Held chats now trigger blocking startup restoration even with structured-chat launches disabled. If the inventory request fails, restoration rejects before workspace-session persistence is enabled, leaving unrelated workspace-session changes in the existing no-save recovery mode. This broadens the failure-containment boundary compared with the setting-only base behavior. The recovery intentionally preserves previous disk state and keeps the UI usable; no attacker-triggered failure was established.
Security review details

Security Blast Radius

  • inferred — The expanded surface concerns structured-session records, journals and provider work reachable through an accessed runtime when its launch setting is off. Sensitive effects include provider-process acquisition and conversation mutations; the inspected evidence does not establish additional unauthenticated, cross-tenant or cross-environment reach.

Trust Boundaries and Controls

  • observed — Structured-session gates continue requiring wire capability for remote callers. Caller identity is passed into host mutation admission, which checks a host-computed payload fingerprint and a durable operation ledger and distinguishes refusal, replay and execution. These controls were not replaced by the new launch-mode capability.

Resilience and Maintainability Implications

  • observed — Attach is tracked from queue admission so shutdown can drain it before eviction. Provider support is rechecked after durable reservation at the effect boundary, and failed acquisition attempts settle reservation evidence and operation outcome. Held-state changes do not themselves spawn a provider or erase durable ownership when runtime resources close.
🚥 Pre-merge checks | ✅ 4 | ❓ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ❓ Inconclusive Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 40 functions across 50 files. (20 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: paired-server structured-chat admission now depends on client capability instead of the server setting.
Description check ✅ Passed The description is complete and on topic. It includes the user impact, implementation details, rationale, compatibility notes, visual proof, testing results, known limitations, and completed checklist…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 40 functions across 50 files. (20 skipped: 5 unsupported, 15 over the file limit.)

✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 4281c2c9-4b6f-4c61-8e25-1290015d9da6

📥 Commits

Reviewing files that changed from the base of the PR and between 12b8ef8 and ccf37e3.

📒 Files selected for processing (74)
  • src/main/ipc/desktop-renderer-runtime-capabilities.test.ts
  • src/main/ipc/desktop-renderer-runtime-capabilities.ts
  • src/main/ipc/runtime-environment-federated-read-routing.test.ts
  • src/main/ipc/runtime-environment-pairing-verification.ts
  • src/main/ipc/runtime-environment-request-connections.ts
  • src/main/ipc/runtime-environment-status-owner.ts
  • src/main/ipc/runtime-environment-support-routing.ts
  • src/main/ipc/runtime-environment-transport-routing.ts
  • src/main/ipc/runtime-environments-call-routing.test.ts
  • src/main/ipc/runtime-environments-pairing.test.ts
  • src/main/ipc/runtime-environments-status-diagnostics.test.ts
  • src/main/ipc/runtime-environments-subscription-lifecycle.test.ts
  • src/main/ipc/runtime-environments-subscription-routing.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-host.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-registry.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-registry.ts
  • src/main/runtime/agent-session-record-store.ts
  • src/main/runtime/orca-runtime-get-structured-agent-session-create-support.ts
  • src/main/runtime/orca-runtime-state-fields.ts
  • src/main/runtime/orca-runtime-structured-agent-session-create-intent.test.ts
  • src/main/runtime/orca-runtime-structured-native-chat-settings.test.ts
  • src/main/runtime/orca-runtime-tests/runtime-availability.spec.ts
  • src/main/runtime/rpc/methods/session-tab-agent-status-projection.test.ts
  • src/main/runtime/rpc/methods/session-tab-agent-status-projection.ts
  • src/main/runtime/rpc/methods/session-tab-browser-placement-projection.test.ts
  • src/main/runtime/rpc/methods/session-tab-close-methods.ts
  • src/main/runtime/rpc/methods/session-tab-mutation-methods.ts
  • src/main/runtime/rpc/methods/session-tabs-inventory-census-race.test.ts
  • src/main/runtime/rpc/methods/session-tabs-inventory.ts
  • src/main/runtime/rpc/methods/session-tabs-structured-restore.test.ts
  • src/main/runtime/rpc/methods/session-tabs.ts
  • src/main/runtime/rpc/methods/structured-agent-session-admission.test.ts
  • src/main/runtime/rpc/methods/structured-agent-session-gate-classification.test-fixture.ts
  • src/main/runtime/rpc/methods/structured-agent-session-gate.ts
  • src/main/runtime/rpc/methods/structured-agent-session-hold.test.ts
  • src/main/runtime/rpc/methods/structured-agent-session-policy.test.ts
  • src/main/runtime/rpc/methods/structured-agent-session-policy.ts
  • src/main/runtime/rpc/methods/structured-agent-session-rpc.test-fixture.ts
  • src/main/runtime/rpc/methods/structured-agent-session-send-compatibility.test.ts
  • src/main/runtime/rpc/methods/structured-agent-session.test.ts
  • src/main/runtime/rpc/methods/structured-agent-session.ts
  • src/main/runtime/rpc/methods/structured-session-tab-restore.ts
  • src/main/runtime/rpc/methods/worktree-rm-background-reply.test.ts
  • src/main/runtime/rpc/worktree-removal-marker-projection.test.ts
  • src/main/runtime/structured-agent-session-held-signal.test.ts
  • src/main/runtime/structured-agent-session-host-refusal.test.ts
  • src/main/startup/main-process-ipc-bootstrap.ts
  • src/preload/api/app-api.ts
  • src/preload/api/app-bridge.ts
  • src/renderer/src/app-shell/use-app-startup-hydration.ts
  • src/renderer/src/components/NativeChatResumeOnRestartModal.tsx
  • src/renderer/src/components/native-chat-restart-offer-gate.ts
  • src/renderer/src/components/settings/ExperimentalPane.test.tsx
  • src/renderer/src/components/settings/NativeChatExperimentalSetting.tsx
  • src/renderer/src/components/status-bar/NativeChatResumeStatusSegment.test.tsx
  • src/renderer/src/components/status-bar/NativeChatResumeStatusSegment.tsx
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/runtime/local-structured-chats.test.ts
  • src/renderer/src/runtime/local-structured-chats.ts
  • src/renderer/src/runtime/local-structured-session-tab-retirement.ts
  • src/renderer/src/runtime/local-structured-session-tabs-sync-hook.test.ts
  • src/renderer/src/runtime/local-structured-session-tabs-sync.test.ts
  • src/renderer/src/runtime/local-structured-session-tabs-sync.ts
  • src/renderer/src/runtime/local-structured-session-tabs-sync/inventory-generation-fence.ts
  • src/renderer/src/runtime/local-structured-session-tabs-sync/snapshot-apply.ts
  • src/renderer/src/web/preload-api/web-app-api.ts
  • src/shared/electron-remote-runtime-client-capabilities.ts
  • src/shared/protocol-version.ts
  • src/shared/remote-runtime-client.test.ts
  • tests/e2e/cross-version-wire/cross-version-agent-session-wire.unit.test.ts
💤 Files with no reviewable changes (4)
  • src/main/runtime/orca-runtime-structured-native-chat-settings.test.ts
  • src/main/runtime/orca-runtime-state-fields.ts
  • src/renderer/src/runtime/local-structured-session-tab-retirement.ts
  • src/renderer/src/runtime/local-structured-session-tabs-sync.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.

Comment on lines +284 to +286
await restoreLocalStructuredChatsAtStartup(useAppStore.getState().settings, (restore) =>
timeRendererStartupStep('project-structured-session-tabs', restore)
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

git diff 12b8ef8c0be518ccdd71fe5b2d1cccaa744347b8 ccf37e3d04629378268530e96811bc53d0b5f16b -- src/renderer/src/app-shell/use-app-startup-hydration.ts
sed -n '210,360p' src/renderer/src/app-shell/use-app-startup-hydration.ts
sed -n '1,160p' src/renderer/src/runtime/local-structured-session-tabs-sync/inventory-refresh.ts
sed -n '1,100p' src/renderer/src/startup/startup-diagnostics.ts

Repository: stablyai/orca

Length of output: 15511


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- helper locations ---'
rg -n --glob '*.ts' --glob '*.tsx' 'restoreLocalStructuredChatsAtStartup|function recoverFromDegradedStartup|recoverFromDegradedStartup|setHydrationSucceeded|setTerminalStartupRestorationReady|session writer|sessionSaving|setSession' src/renderer/src
printf '%s\n' '--- helper definitions and callers ---'
rg -n -C 12 --glob '*.ts' --glob '*.tsx' 'restoreLocalStructuredChatsAtStartup' src/renderer/src
printf '%s\n' '--- startup recovery definitions ---'
rg -n -C 20 --glob '*.ts' --glob '*.tsx' 'function recoverFromDegradedStartup|const recoverFromDegradedStartup|recoverFromDegradedStartup =' src/renderer/src
printf '%s\n' '--- relevant files at head ---'
git diff --name-only 12b8ef8c0be518ccdd71fe5b2d1cccaa744347b8 ccf37e3d04629378268530e96811bc53d0b5f16b -- src/renderer/src
printf '%s\n' '--- base/current helper source ---'
helper=$(rg -l --glob '*.ts' --glob '*.tsx' 'restoreLocalStructuredChatsAtStartup' src/renderer/src | head -1)
printf 'helper=%s\n' "$helper"
if [ -n "$helper" ]; then
  sed -n '1,260p' "$helper"
fi
printf '%s\n' '--- current startup tail ---'
sed -n '260,390p' src/renderer/src/app-shell/use-app-startup-hydration.ts
printf '%s\n' '--- base startup corresponding section ---'
git show 12b8ef8c0be518ccdd71fe5b2d1cccaa744347b8:src/renderer/src/app-shell/use-app-startup-hydration.ts | sed -n '240,350p'

Repository: stablyai/orca

Length of output: 41626


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- degraded recovery ---'
cat -n src/renderer/src/startup/startup-degraded-recovery.ts
printf '%s\n' '--- persistence gate ---'
cat -n src/renderer/src/lib/workspace-session-persistence-gate.ts
printf '%s\n' '--- gate usages and hydration flags ---'
rg -n -C 10 --glob '*.ts' --glob '*.tsx' 'hydrationSucceeded|terminalStartupRestorationReady|workspace-session-persistence-gate|shouldPersist|session writer' src/renderer/src/store src/renderer/src/lib src/renderer/src/hooks
printf '%s\n' '--- recovery tests around save/readiness behavior ---'
cat -n src/renderer/src/startup/startup-degraded-recovery.test.ts
printf '%s\n' '--- local structured restore tests ---'
cat -n src/renderer/src/runtime/local-structured-chats.test.ts

Repository: stablyai/orca

Length of output: 5706


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- gate files ---'
rg --files src/renderer/src | rg 'workspace-session|persistence-gate|session-persistence'
printf '%s\n' '--- hydration writer consumers ---'
rg -n -C 12 --glob '*.ts' --glob '*.tsx' 'hydrationSucceeded|workspaceSessionReady|session writer|persist.*session|session.*persist' src/renderer/src

Repository: stablyai/orca

Length of output: 45661


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- session persistence hook ---'
cat -n src/renderer/src/app-shell/use-app-session-persistence.ts
printf '%s\n' '--- persistence gate test ---'
cat -n src/renderer/src/lib/workspace-session-persistence-gate.test.ts
printf '%s\n' '--- gate implementation references in hook imports ---'
rg -n -C 8 'hydrationSucceeded|workspaceSessionReady|terminalStartupRestorationReady|persist' src/renderer/src/app-shell/use-app-session-persistence.ts

Repository: stablyai/orca

Length of output: 22332


Keep structured-chat restore failures out of degraded startup.

When the setting is off but the runtime holds saved structured chats, the new helper runs session.tabs.listAll. A non-OK response throws, and the timing wrapper rethrows it. The outer recovery then leaves hydrationSucceeded false. The session writer stays disabled until restart.

The merge base skipped this call when the setting was off. Catch only this optional restore failure so the normal hydration path can enable session saving.

Suggested fix
-          await restoreLocalStructuredChatsAtStartup(useAppStore.getState().settings, (restore) =>
-            timeRendererStartupStep('project-structured-session-tabs', restore)
-          )
+          try {
+            await restoreLocalStructuredChatsAtStartup(useAppStore.getState().settings, (restore) =>
+              timeRendererStartupStep('project-structured-session-tabs', restore)
+            )
+          } catch (error) {
+            console.warn('Structured chat startup restore failed:', error)
+          }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await restoreLocalStructuredChatsAtStartup(useAppStore.getState().settings, (restore) =>
timeRendererStartupStep('project-structured-session-tabs', restore)
)
try {
await restoreLocalStructuredChatsAtStartup(useAppStore.getState().settings, (restore) =>
timeRendererStartupStep('project-structured-session-tabs', restore)
)
} catch (error) {
console.warn('Structured chat startup restore failed:', error)
}

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

  • Admission by capability. supportsStructuredAgentSessions now ignores the host's experimentalStructuredNativeChat and admits a remote caller only when it advertises agent-session.structured.v1; in-process callers are admitted as before. Every agentSession.* gate, tab projection and structured restore flows through it.
  • Legacy createSupport rule. A remote client that does not advertise the new launch-mode capability still gets the old setting-based createSupport answer (same error code and reason); every other method ignores the setting. Name and exit comment are explicit.
  • New agent-session.structured.client-launch-mode.v1 capability. Advertised by the server and by the desktop's own window; deliberately not by mobile or the Electron remote list yet. createSupport now carries an optional seedOptions from the same resolver create uses.
  • "Holds chats" signal and renderer mirror. A main-process fact (records.size || unreadableRecords.size, plus an onFirstRecord push) drives a new IPC pair; the window's chat mirror, startup restore, restart offer and chat settings now run when the setting is on or the machine holds chats. The toggle-off teardown and its now-dead helpers are removed.
  • Mechanical extraction. ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES moved to its own module (file-length limit); all imports updated. i18n structuredCopy updated in the five catalogs that hold the key.

The core policy change is centralized in one predicate per concern, the legacy path is confined to createSupport, and the tests pin the failure modes that matter (capability-less clients refused, released phones keep the old refusal byte-for-byte, in-process callers admitted, held-signal push-vs-read race, browser client excluded). tc:node, tc:web, oxlint and the targeted suites all pass locally. One pre-existing, environment-dependent failure in runtime-availability.spec.ts (advertises browser screencast…) is untouched by this diff and unrelated.

ℹ️ The documented startup-check follow-up widens its blast radius

The PR notes that a failed startup chat check can surface "Session restore failed / Changes won't be saved until restart", and that this now applies to machines that hold chats with the setting off, not just settings-on users. It is flagged as a known, unreproduced follow-up rather than fixed here — worth confirming a tracking ticket exists before this ships, since it is a data-saving symptom rather than a cosmetic one.

Technical details
# Startup chat restore failure now reaches a wider population

## Affected sites
- `src/renderer/src/app-shell/use-app-startup-hydration.ts` — the restore step now runs via
  `restoreLocalStructuredChatsAtStartup` whenever the machine holds chats, not only when the
  setting is on.
- `src/renderer/src/runtime/local-structured-chats.ts` — `localStructuredChatsInUse` returns true
  on `holds` independently of `experimentalStructuredNativeChat`.

## Required outcome
- A failure of that restore must not leave the session saver in a state where subsequent local
  changes are dropped until restart. At minimum, file/track the ticket the PR body references.

## Open questions for the human
- Is there an existing issue for the non-fatal-restore fix, or should one be filed?

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@brennanb2025
brennanb2025 merged commit 7577366 into main Oct 2, 2026
66 checks passed
brennanb2025 added a commit that referenced this pull request Oct 2, 2026
…odule (#24494)

#24203 moved ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES out of protocol-version.ts into
electron-remote-runtime-client-capabilities.ts. Three files that landed on main while it was in
review (SSH access links and managed orcad server work) still import it from protocol-version.ts,
and a test #24203 added predates main making the structured host's logger option required, so
main's node typecheck fails. Point the three imports at the new module and pass the logger.
Jinwoo-H added a commit that referenced this pull request Oct 2, 2026
…o main passes lint (#24496)

The host file reached 302 lines after #24203 and #24311, over the 300-line limit,
so the static-analysis job failed on every commit to main. The five chat-tab
members now come from createStructuredAgentSessionTabSurface in the existing
structured-agent-session-host-tabs module; behaviour is unchanged.
sj900211 added a commit to sj900211/orcinus that referenced this pull request Oct 13, 2026
Measured from the post-stage-3 main: 794 commits, 22 conflicting files, only 2 of
them in the PTY delivery subsystem. The wall this staged plan existed to isolate
is behind us; this stage is identity, usage accounting and renderer surface.

Three upstream commits produce most of the conflicts: `9700030689` (headless
renderer delivery), `82412dab8b` (profile state in SQLite), `d443320af2`
(terminal handoff removal), plus `d2dbe2c385` and `f1463683ee` below.

## The two decisions this merge turned on

**OpenCode field meanings: adopt upstream's.** The fork's distinguishing
behaviour was counting cache writes in token totals. Upstream `f1463683ee` now
does that itself — its `scanner.test` expects 1775, which is the fork's number.
What remained was bucketing: upstream has `inputTokens` = uncached+read+write,
`cachedInputTokens` = read+write, `cacheWriteInputTokens` for the write subset.
Keeping the fork's narrower meanings would have left four cleanly-merged upstream
files silently wrong, one of them producing a negative telemetry `input_tokens`
that the schema rejects — and the divergence would be invisible to typecheck,
since the field names match and only the meanings differ. All five cluster files
are therefore byte-identical to `b601b98644`, and every asserted total in
`cache-write-tokens.test.ts` is unchanged (1120, 2000, 3125): the feature is
preserved, only its composition moved. Fork files adapted: `snapshot-rollups.ts`,
`usage-provider-normalization.ts` (restored to upstream — otherwise cache is
counted as new input and writes are counted twice), `usage-overview-model.test.ts`,
`cache-write-tokens.test.ts`.

**`agent-state-rules` URL: keep the fork repo.** Upstream stablyai#24387 downloads rules
from `${MAIN_RELEASE_REPO}`, which the fork rebranded to `sj900211/orcinus`. A
404 is non-fatal — the updater warns and serves the bundled rules — so the fork
keeps pointing at itself rather than fetching runtime data from a repository it
does not control. Upstream tests that pinned `stablyai/orca` had a premise that is
false by a ratified decision (`release-channel.ts:27`), so their expectations were
corrected, following the precedent of rebrand commit `222baedec6`. Each edited
assertion carries a `// Why` marker so a future sync does not revert it. This is
correcting a stale premise, not weakening a guard: the tests still pin how the URL
is built.

## The Windows CLI launcher was replaced wholesale

Upstream `d2dbe2c385` deleted `OrcaCliLauncher.cs` and shipped a Rust crate. The
fork's `Orcinus.exe` fix (hidden quest ③) is ported into
`native/windows-cli-launcher/src/main.rs` at the four call sites; the `.cs` is
removed, which a cross-host test now requires because its MSIL image was
vendor-flagged (stablyai#23383). The three `Orca CLI` strings and every
`ORCA_*` environment name stay — the CLI is still named `orca`. Without this port
the packaged `orca` CLI fails on every Orcinus Windows install, which is the
precondition for all orchestration work in this repo.
`.github/workflows/orcinus-cd.yml` gains a Rust toolchain step on the Windows leg.
Deferred by decision: rebranding the launcher's `build.rs` metadata and icon
(cosmetic, and the three pieces must move together).

## Defects found that no conflict marker could show

Six files merged cleanly and were still wrong:
- `pty/session.ts` family — carried from stage 3's pattern, handled there.
- `usage-web-client-fallback.test.ts` lost two slice imports upstream narrowed.
- `ui-slice-preference-actions.ts` breached `max-lines` at 302 (fork +10, upstream
  +12). Resolved without a disable by moving the fork's number-format action to a
  sibling module; the file is now identical to upstream.
- `startup-window-role-reconnect.ts` and `folder-workspace-initial-terminal.ts`
  are fork-only extractions made for `max-lines`; upstream rewrote the originals,
  so stablyai#24203 and stablyai#23693 were ported into them by hand.
- Upstream's new Reset Terminal bypassed owner-window routing; it now routes.
- Upstream's new `terminal-intentional-stop-exit.test.ts` saw zero `pty:exit`
  sends until given a routed main window.
- `satellite-open-interception.ts` would have raised a satellite window on a
  background CLI open, breaking the "change nothing on screen" guarantee stablyai#24244
  added.

## Review

An independent critic returned REJECT with **no production defect**: every fork
behaviour present, both decisions correctly applied, every gate reproducible. The
rejection was for 14 tests in 6 files that the gate's directory scope never ran —
all of them new upstream tests pinning upstream identity, the same class the
`agent-state-rules` decision had ruled on and which it had been applied to too
narrowly. All six are fixed, test-only, each with a `// Why` marker. The critic
also corrected a claim in the implementation report: the fork already carries
upstream's `agent-state-rules-publish.yml`, so live rule updates need only an
`agent-state-rules` environment and a `publish-next` dispatch, not new machinery.

`sftp-upload.test.ts` deserves a note: upstream's new cases assert a direct
`createWriteStream`, but the fork uploads atomically to `.orcinus-part-<uuid>` and
publishes by rename (`sftp-upload.ts:61-63`). The fork's contract stands; the
expectations moved to it. One of those cases is `skipIf(win32)` and could not be
observed here, but fork CI runs on ubuntu, so it was fixed from the code.

## Gates

Cold typecheck (after deleting `config/*.tsbuildinfo`): 0 errors, measured twice
by different workers. oxlint: 0 warnings, 0 errors across 29,465 files.
verify-orcinus-identity: OK, `upstreamRefs=0`. Unsigned smoke: OK,
`icon=orcinus.icns`. Fork contract tests: 24/24. Windows CLI launcher tests: the
Rust build actually runs here and passes.

Attribution, using the critic's harness across three trees (this merge, main, and
`b601b98644`): a 1,236-file fork-adjacent sweep and a 54-file identity sweep went
from 14 merge-only failures to 1. That one is the release-tag census entry for the
fork-only `verify-orcinus-release-assets.mjs`: the census builds its keys with
`join()`, so on win32 they carry backslashes. Six upstream sibling cases fail the
same way on the upstream baseline, a direct POSIX evaluation of the key logic
passes, and a negative control confirms the entry is detected. The census's
upstream key logic was deliberately left alone.

Known and not addressed here: `updater-prerelease-feed.ts:156` matches only
`stablyai/orca` asset URLs — present on `main`, not merge-caused, and invisible to
the identity verifier. The release path has not been exercised with this merge
(`build:orcad` moved from bun to node); an `orcinus-cd` dry run is advisable before
the next fork release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant