Repository navigation
feat(native-chat): Codex structured chat — full migration (host runtime, journal, wire, desktop + mobile clients) - #13438
brennanb2025 wants to merge 131 commits into
Conversation
…types Adds the shared contract for a durable agent session: an execution-location scoped record (host + WSL distro + workspace id, folder workspaces and git worktrees alike), a provider handle CHAIN keyed per provider, the pinned account home, and the single-writer lease with its monotonic fence. Adjudication is pure and fails closed: expiry alone never grants a second owner, a bare pid match is not proof of life, and a conflicted claim stays conflicted. The client-operation ledger's replay/conflict/expiry/capacity rules move here so they can be persisted instead of held in memory. Nothing is wired yet; these types are dormant until later parts adopt them.
Stores records, leases, and the client-operation ledger in one host-scoped file written atomically (previous state kept as a backup, temp file fsynced and renamed), so a kill during an rc-update restart leaves either the old or the new committed state and never a torn lease. Reservation and operation row commit in the same transaction, and mutations serialize through one queue so two compare-and-swaps cannot observe the same pre-state. Restart reconciliation marks every loaded lease unreconciled and grants no writer until it is adjudicated: a proven-live owner is re-adopted with the fence unchanged, a proven-dead one bumps the fence, and anything unverifiable lands in recovery. Identity probing is PID-reuse-safe and fails closed — Windows has no start time here, so a child that cannot echo its spawn token qualifies for manual recovery only. Dormant: no caller constructs the store yet.
95 tests over the durable record, the lease, and the probes. The invariants worth naming: a lapsed deadline never grants a second owner (only observed exit, an absent pid, or an identity mismatch does), a bare pid match is not proof of life, the loser of a concurrent compare-and-swap is refused and never spawns, records and the operation ledger survive a restart with the fence monotonic across it, a conflicted claim stays conflicted, and native, WSL, SSH, and folder-workspace sessions never share a scope. Each test was mutation-checked: 22 plausibly-wrong implementations (stale steal, re-adoption bumping the fence, trusting loaded leases, forks recorded as resumes, dropped tombstones, guessed Windows start times, unrolled failed transactions) were applied one at a time and every one turns the suite red.
…ceipts and epoch cursors (phase 1c) Adds the host-side append-only journal that a native chat session will read and write: one durable log per agent session, one reducer shared by live events and replay, and epoch-qualified cursors so a reconnecting client either resumes exactly or takes a clean snapshot reload. Nothing is wired up yet. No RPC, no adapters, no UI: the journal is dormant until a later phase feeds it, so behavior for existing sessions is unchanged. - Append-only JSONL log plus an atomic snapshot that carries its own retained tail, so compaction is a single durable step and a crash mid-compaction leaves the log a recoverable superset. - Sequences are assigned inside the same serialized append that makes the row durable, giving the (epoch, sequence) space exactly one writer with no gaps and no reuse. Every write carries the runtime fence; a stale fence is refused. - A write-ahead submission row is durable before dispatch and doubles as the optimistic user bubble, so an accepted provider echo upserts into the bubble the user already sees instead of adding a second copy of their own message. - Dispatch is a state machine: pending, accepted, rejected, or unknown. Unknown is a displayed state meaning "delivery unconfirmed"; Orca never re-sends on the user's behalf and never silently drops the message. - Items upsert by stable provider identity with revisions and tombstones. Identity keying never uses text equality, so asking the same question twice stays two messages. - Payload bounds keep a head plus the byte length and digest inline and push the remainder to a content-addressed blob store; crossing a bound is always marked, never a silent truncation. - A row written by a newer schema puts the host into read-only mode rather than being skipped, so a newer client's rows are never dropped or compacted away. - Legacy import hydrates a journal by running the existing transcript decoders unchanged and reading identity off the same raw lines they consume.
…store A digest reaches the filesystem after a round trip through a row on disk, so a corrupt or crafted row could name a path outside the store. Blob names are now required to be a bare sha256, checked on both reads and writes.
…quence Raw 0x00 bytes made git classify both files as binary, so their PR diffs were unreviewable. The separator stays NUL; only its spelling changes.
…b2025/native-chat-structured-base
…native-chat-structured-base
Route every byte that can reach a PTY through the single-writer lease adjudicator, so a session owned by one runtime cannot be typed into by another. Nothing binds a PTY to a durable session record yet, so the gate short-circuits and today's shells, legacy agent terminals, and unbound sessions are untouched. Gated paths: terminal.send RPC, the renderer IPC write paths (pty:write / pty:writeAccepted), plugin sendTerminalText, the agent-prompt paste, and ptyController.write as a backstop for runtime writes that never passed a typed gate. Every yield inside a write - inter-chunk, the text/suffix pause, the agent-prompt submit delay, deferred size measurement - re-checks the fence, so a handoff mid-paste stops the remaining chunks instead of interleaving two owners. Refusals are typed and additive on the wire: an optional agentSessionRefusal on RuntimeTerminalSend and on the existing pty:writeUnavailable payload, refusal codes drawn only from the existing agent-session error codes, and stream-input refusals mapped onto the WriteUnavailable frame old clients already decode.
…e-enforce' into brennanb2025/native-chat-structured-base
Adds the request/refusal shapes every `agentSession.*` method speaks, the four-field idempotency envelope and its lease admission, the item-key inverse an upsert needs, and the `agent-session.structured.v1` capability. The protocol version stays at 3: the surface is additive, and bumping it would strand every paired device on the current release rather than degrade one feature.
One place where the lease, the journal, and the provider adapter meet: every mutating call recomputes the fingerprint, admits through the durable operation ledger, checks the lease, then acts, so no method can grow its own admission rules. Journal recovery rehydrates an unreadable journal from provider history into a fresh epoch and tells subscribers which reset they are seeing. Attach adjudicates the leases it loaded from disk before granting any writer. Without that step every lease stays unreconciled for the life of the process and a restarted host refuses every persisted session forever.
Ten methods on the runtime manifest, including a paged `agentSession.history` over the journal cursor and a subscribe that streams cursor-qualified batches. A declared client that did not advertise `agent-session.structured.v1` is told the whole surface does not exist, so an old client never sees a session it cannot render; an in-process caller negotiates nothing and is served.
Extends the cross-version harness, which covered the terminal stream only, with the structured surface. The old side's advertised capabilities and registered method names are read from the extracted release checkout rather than written by hand: a build cannot advertise a string its own source never names, so "the old side does not have it" stays a fact about a real release.
…red-base' into brennanb2025/native-chat-1d-wire
The hand-written lease drifted the moment the record grew a field. Same reason the fixture exists for the write-admission tests.
…' into brennanb2025/native-chat-structured-base
The existing app-server client is request-scoped: one deadline for the whole session, and every inbound message that is not a pending response is dropped. A structured chat session needs the opposite — a child that outlives any single call, per-request deadlines, and both traffic directions, because Codex streams a turn as notifications and asks for approvals by sending requests back. Adds a connection that spawns the child, completes the initialize handshake, and stays open until closed, keeping the same environment overlay and reap semantics as the short-lived client. A refusal is now distinguishable from a call that never settled, so callers can tell "Codex declined" from "the child died" instead of reporting both as failure.
Codex blocks a turn until every approval and tool-input request it sent is answered, but the answer arrives much later and through a durable journal item id rather than the live JSON-RPC request id. This holds the live requests for a session and turns a chosen option back into the reply Codex expects. Three cases the journal boundary forces: one request can carry several questions but takes a single reply, so answers are buffered until the request is complete; a request this build does not model is refused rather than left blocking or silently approved; and a prompt is forgotten before its reply is written, so a second answer finds nothing instead of replying twice.
Implements the structured agent-session adapter for Codex: one long-lived app-server child per session, started or resumed under the lease the host already reserved, reporting the process identity the lease re-proves and the provider handle link the journal binds to. The thread the adapter reports is the one Codex actually opened, never the one a client declared, and a resume that lands on a different thread is refused rather than recorded as a resume. A turn is named from the start response when Codex sends an id there and from the turn-started notification when it acks first; when neither names it in time the submission settles unknown, which the journal renders as delivery unconfirmed rather than failure. Only a refusal Codex actually answered becomes a rejection. Model, effort, and policy are per-turn overrides — there is no thread-settings write — so an option set between turns is applied to the next turn/start, and unlisted keys are refused instead of becoming arbitrary client-controlled parameters.
Six findings from the review of the structured Codex session, all of them about a session that is live in the map while the thing it names is not: - A prompt is addressed by its own approval id, not by the tool item it is about. A shell bridge re-asks per command under one parent item, and the second ask used to evict the first, leaving that turn blocked on a request nobody could answer. - A subagent runs its own thread over the same connection. Its `turn/started` no longer satisfies the root thread's waiter, and every event now carries the thread it actually came from. - Publication is one step at the end of acquire: the process identity is in hand, the child is still alive, and the attempt still owns the session. A failure reaps only its own child, so a replacement already published keeps running. - Codex talks before its thread is named. Those events wait in the acquisition window and are delivered in arrival order once the session is published, instead of being dropped. - A broken stdin pipe ends the transport rather than one write: the child is reaped and the owner learns its lease is worthless. - A death that reaches us through several listeners reports once, keeping the first cause rather than the generic exit that follows it.
A provider starts streaming the moment its process exists, and that moment is inside `adapter.acquire` — before the journal is open and before the host has registered the session. The adapter now receives a deferred sink whose writes queue in arrival order and drain once the journal is bound. One sink lives for the session rather than for one acquisition, so an adapter can hold it across a re-acquire without noticing the new journal object. It is bound last in `onAttached` so nothing the provider streamed can interleave with the attach result the client is about to read, and a refused attach that never reached a journal closes it instead of leaving writes nothing will drain. The host was one line under the 300-line cap before this, so the mutation admission pipeline moved to its own module. That is a straight extraction: recompute fingerprint, admit through the ledger, check the lease, run the plan — the same route, now somewhere a new method cannot sit next to.
Providers emit one notification per token. Journaling each one writes a row and wakes every subscriber per token, so one long answer costs thousands of appends and thousands of stream frames on a phone. Deltas accumulate and flush on a 60ms window, and the row carries a SNAPSHOT of the text so far rather than the increment — which is also what makes dropping intermediate frames safe for a reconnecting client. All streams share one timer, so the deadline bounds latency the same way at one wakeup per window instead of one per stream. Lifecycle bypasses the window by flushing first, so nothing can be journaled ahead of the text that preceded it.
Codex item ids are not stable across resume, so a journal keyed by them cannot reconcile a recovered thread against a live one: the same message would arrive under a new id and append a second copy of itself. The durable key is instead the position the item holds — thread, turn, and ordinal within that turn, counting message items only. Non-message items get no ordinal at all rather than a number from a second counter, because a counter a resumed history cannot reproduce is worse than no key: it looks reconcilable and reconciles wrongly. Those rows fall back to a live-session namespace that a recovered journal simply will not contain, which is correct, since Codex does not persist them either. Approvals and questions become journal rows the same way, naming the command the item already announced so the user is not asked to approve an opaque id, and one row per question rather than one row per request.
PACKAGED FINAL GATE — run 31753097102 — NO-GOTested the arm64 adhoc artifact Packaged final gate
Release blockerThe relaunch still reproduces the packaged duplicate-resume failure. Before restart there were exactly three daemon sessions: native shell ScreenshotsZero-turn guard and exact tooltip: Launchd-like first send succeeds: Switch-back adopts the existing live session: Reverse selects the Chat tab: Cross-workspace reverse leaves the other workspace active: Vault resume activates the existing structured Chat: Restart-created fresh duplicate ( Original TUI tab after restart, with the prior session scrollback followed by a zsh prompt: Overall: NO-GO. Do not send this packaged build straight to Brennan; the live structured terminal still duplicates/fresh-spawns across restart. |
PACKAGED FINAL GATE v2 — run 31759132745 — NO-GOTested the packaged
Release blockers
ScreenshotsZero-turn empty timeline Fresh session streams under stripped environment Switch-back duplicate spawn Restart duplicate and original TUI exited to shell Second agent established before the restart check Reverse focuses Chat and Vault Resume completes Verdict: NO-GO. The packaged build passes the fresh-session, toggle/reverse, Vault Resume, and zero-turn checks, but fails both exact restart adoption and switch-away/back no-spawn behavior. |
VERDICT: PASSFix SHA: Instrumented diagnosis from the exact run 31759132745 artifact (
The separate switch-away/back mismatch was also instrumented: both renderer PTY sources were absent ( The restart fix accepts only a missing persisted incarnation during the proven hydration window. A non-null incarnation mismatch still rejects, and signed claim, exact workspace/tab/leaf/PTY, daemon incarnation, unique candidate, and Codex-child PID/start-time proof remain mandatory. Captured packaged restart fixture:
Packaged validation used the exact artifact copy with stripped environment and Electron CDP. After repairing only the two duplicate, non-agent-owned tabs produced by pre-fix attempts, restart stayed at 3 terminal sessions with exactly one Gates:
|
PACKAGED FINAL GATE v3 — run 31764256105 — NO-GOTested the notarized arm64 artifact
Release blockersLive-TUI survival is still incomplete. Before restart, the structured TUI used PTY suffix The done/sleeping agent did not resume. Its pre-restart record was Reverse focus, Vault resume, and zero-turn were skipped per the tight v3 scope because they passed on two consecutive artifacts and this delta does not touch them. ScreenshotsFresh structured session, empty pre-send timeline: Structured send streaming under the stripped environment: Second agent completed before being slept: Switch-back cycle 1, no duplicate spawn: Switch-back cycle 2, no duplicate spawn: Live TUI immediately before restart: Same adopted PTY/scrollback after restart, but Codex exited to the shell: Sleeping-agent activation produced only a fresh shell: Overall: NO-GO. The claim-hydration delta stops duplicate spawning, but the required live Codex process and the separate sleeping agent do not survive/resume. Do not send this build directly to Brennan. Full local report: |
PACKAGED FINAL GATE v4 — run 31767793897 — NO-GOTested the notarized arm64 artifact
Release blockersThe live TUI itself now survives exactly, but recovery still duplicates it. Before restart the structured TUI was PTY suffix The structured handoff then reported The separate sleeping agent still does not resume. Its durable record contained provider session ScreenshotsZero notification rows before the turn: Completed streamed turn, still zero notification rows: Switch-back before restart retains one TUI and the exact session inventory: Original live TUI and scrollback survive restart, but a second Sleeping-agent activation yields only a shell: Reverse toggle is blocked by manual recovery: Overall: NO-GO. Do not send this packaged build to Brennan. Full local report: |
…ructured-mobile-write # Conflicts: # mobile/src/transport/mobile-relay-rpc-session.ts # mobile/src/transport/mobile-relay-rpc-streams.ts # src/main/ipc/pty.ts # src/main/runtime/orca-runtime.ts # src/preload/api-types.ts # src/renderer/src/components/WorktreeJumpPalette.tsx # src/renderer/src/components/native-chat/native-chat-file-link.test.ts # src/renderer/src/components/native-chat/native-chat-view-types.ts # src/renderer/src/components/sidebar/worktree-agent-row-selectors.test.ts # src/renderer/src/components/tab-bar/tab-bar-props.ts # src/renderer/src/components/tab-group/useTabGroupWorkspaceModel.ts # src/renderer/src/hooks/resolve-zoom-target.ts # src/renderer/src/i18n/locales/en.json # src/renderer/src/lib/browser-palette-page-entries.ts # src/renderer/src/lib/simulator-palette-search.ts # src/renderer/src/lib/workspace-tab-palette-search.ts # src/renderer/src/runtime/web-session-tabs-sync.ts # src/shared/tui-agent-startup.ts # src/shared/types.ts
packaged final gate v5NO-GO for artifact Restart matrix:
Other matrix checks:
Structured stream and persisted model: Ownership handoff failure with verifier detail: Live foreground Codex TUI restored: Sleeping-session miss: fresh plain |
Overnight exploratory QA — Codex structured chat candidate (artifact run 31776657104, adhoc v1.4.183-adhoc.20260814063414)Automated overnight QA of the packaged candidate (pristine bundle, fresh profile, CDP). Everything tested EXCEPT the two known-failing seams (chat→TUI handoff, sleeping-session resume). ANOMALIESA1 (P1) — Closing a structured chat tab is a silent no-op after app restart A2 (P1) — Rapid sequential sends duplicate the last user message in the transcript A3 (P2) — Split terminal becomes orphaned after restart A4 (P2) — A5 (P2) — Attachments permanently disabled in structured sessions A6 (P3) — No thinking/streaming indicator during long first-token latency A7 (P3) — Codex TUI tab title polluted by typed input Not reachable in this config (not bugs)
Passed (repeatedly)Create/multi-turn/streaming/cancel-mid-stream/model+effort-changes/session-reopen/concurrent sessions (3+1 cross-worktree)/worktree switching/splits (pre-restart)/3× full restarts with full restore (tabs, PTYs, transcripts, post-restart send+stream+cancel all green). |
|
Screenshot evidence for the batched anomaly report above. Full set (27 pngs + REPORT.md) is in the worktree at mobile/qa-evidence-114eee22dd/overnight-codex-20260814/. |
|
A1 follow-up — root-cause evidence found: while re-checking after posting, |





































Summary
This is stack part C2 and contains only the mobile write spine on top of C1 / #13416. It adds a persisted, kill-safe outbox with one in-flight dispatch, same-client-message-id retries, editable queued messages, and delivery-unknown bubbles that remain visible as unconfirmed rather than failing or disappearing.
It also wires durable approval and question cards through the existing native-chat components with expected-revision compare-and-set responses, Codex free-text answers, turn cancellation, model and option changes, and mobile authorization for the new mutation RPCs. Attachments reuse the existing bounded chunked clipboard upload RPCs; large or high-resolution images are resized on device through a three-rung dimension/compression ladder before any raw full-size base64 read.
End-to-end trace
use-mobile-structured-session-entry.tscreates or resumes the structured tab, hydrates the read reducer, outbox, option controller, and attachment controller.use-mobile-structured-session-writes.tspersists the optimistic submission before accepting the composer send, dispatches exactly oneagentSession.send, and reconciles journal submissions by client message id.mobile-structured-mutation-envelope.tsreproduces the host canonical SHA-256 fingerprint and uses the client message id as the send operation id, so every retry reaches the same idempotency slot.use-mobile-structured-agent-session.tscontinues C1 cursor-resumed streaming and pagination; the optional history fence supplies the write checkpoint when a subscription resumes silently at head.mobile-structured-session-timeline.tscombines durable journal items, prompt cards, and optimistic outbox rows while reusingMobileNativeChatMessage,MobileNativeChatComposer,MobileNativeChatPermission, andMobileNativeChatQuestion.use-mobile-structured-session-mutations.tssends approval/question responses withexpectedRevision, callsagentSession.cancel, and updates options withagentSession.setOption.use-mobile-structured-attachments.tsandmobile-image-source-picker.tsdownsample before the existing start/append/commit chunked upload flow, preserving bounded transport frames.mobile-structured-outbox-store.tsrestores queued rows and converts interrupted dispatches to unconfirmed; the user explicitly retries with the original client message id.Adversarial review follow-through
I rechecked every posted finding on #13381 and #13394 against this stacked tip. The inherited lower stack now includes thread-scoped prompt and translation state, acquisition cancellation/rollback and shutdown cleanup, sink unbinding during replacement, bounded geometric stream checkpoints and graceful flush, free-text questions and request-specific approval detail, image/provider echo and array reasoning translation, and production runtime teardown; their focused regressions are present on the branch. C2 also caught and fixed the production mobile RPC allowlist gap for the new send/cancel/respond/setOption methods.
Validation
pnpm run typecheck: passed.pnpm run lint: passed, including native/type-aware quality, reliability, max-lines, skill, and localization gates; only pre-existingWorktreeJumpPalettehook warnings were printed by the non-deny first lint stage.Stack / retarget plan
Base is
brennanb2025/codex-structured-mobile-readso the review diff is C2-only. After #13416 lands, retarget this PR to the branch that received C1 (normallymain) without rebasing away the review history.Deliberate deferrals
Push notifications, desktop structured-chat polish, and general availability remain later rollout work. This PR is the requested mobile write spine and does not change the legacy/native-chat send path when structured mode is not selected.
Pre-RC adversarial fix round
Fixing commits:
bbc6aba927resolves the inherited C1 review blockers.8778fdedadresolves the C2 adversarial blockers.ec917184c3clears the exact changed-line React Doctor gate after the C2 refactors.19df0ced4cupdates the root integration and cross-version fixtures to exercise the mobile create-by-intent contract.C2 blocker-to-regression mapping:
Inherited C1 P1 evidence:
bbc6aba927: authentication remains the transcript-bound exact shape and capabilities move to a post-auth encrypted control, covered on both mobile and desktop.bbc6aba927, with full-suite fixture alignment in19df0ced4c: mobileagentSession.createaccepts only create intent; host-owned attach fields are rejected.bbc6aba927: a settled page unlatches immediately and the second page is admitted without a momentum event.bbc6aba927: request command wins over a sibling reason, with a command-plus-reason regression.bbc6aba927(read stream),8778fdedad(attachments/options), andec917184c3(changed-line React Doctor): the final changed-code gate reports 0 standard, 0 type-aware, and 0 React Doctor findings.Validation after the fix round:
WorktreeJumpPalettehook warnings were printed.static analysisjob 93345261515 is green.The PR remains based on
brennanb2025/codex-structured-mobile-readso the review diff stays C2-only plus the explicitly requested inherited fixes. After C1 lands, retarget to the branch that received C1 (normallymain) without rebasing away this review history.Pre-RC fix round 2 (
dcd26ef677)Fresh-review blocker-to-regression mapping:
retryUnknowncontrol can rerun only a matching durableunknownsubmission, reuses the same client message id without appending another submission, and then releases the next outbox head. Regressions:redispatches an explicitly retried durable unknown without appending a second submissionandretries a durable unknown by the same id before dispatching later messages.isolates mutation ids and late errors across session changes.endedthrough the translator before disposal, tombstoning the durable running marker. Regressions:flushes the final coalesced text before a graceful closeplus the create/send/stream/approval/cancel/reconnect integration history assertion.projects post-auth v2 capabilities into later RPC dispatches.closeAll()fences a pending resolver before it can spawn or publish. Regression:fences an acquisition while launch resolution is still pending.drains writes captured by the old journal before acquiring its replacement.agentSession.unsubscribefrom the subscribe request id/session even though the stream emits no ready metadata; queued-unsent relay streams remain side-effect free. Regressions:unsubscribes a structured stream by its request-scoped subscription idandunsubscribes an agent session without waiting for nonexistent ready metadata.Round-2 validation:
The base remains
brennanb2025/codex-structured-mobile-readfor a C2-only review diff; after C1 lands, retarget to the branch that received C1 (normallymain) without rewriting the stack history.Gate follow-up
cbd79a61e9moves the cross-session active-context ref update into an effect after the first refreshed CI run caught render-time ref mutation. The exact base-pinned React Doctor command now completes with warnings only and no blocking errors. Refreshed static analysis, typecheck, cross-version wire compatibility, root-directory guard, and mobileverifyare green oncbd79a61e9.Pre-RC fix round 3 (
73c7f95457)Final blocker-to-regression mapping:
Round-3 validation:
The base remains
brennanb2025/codex-structured-mobile-readfor the C2-only review diff. After C1 lands, retarget to the branch that received C1 (normallymain) without rewriting the stack history.Refreshed GitHub static analysis is green on
73c7f95457.Pre-RC fix round 4 (8623f12)
Root cause: paired capability negotiation completed correctly and the Chat session action did invoke agentSession.create, but mobile minted durable operation IDs as mobile-structured: (and prefix:base36-time:uuid for later writes). The host authority accepts only <13-digit-ms>-<32-lowercase-hex>, so durable admission refused create before adapter acquisition and tab publication; the error then appeared only inside the no-tabs empty state, leaving QA with existing tabs a silent no-op.
Fix and regression mapping:
Round-4 validation:
The base remains brennanb2025/codex-structured-mobile-read for the C2-only review diff. After C1 lands, retarget to the branch that received C1 (normally main) without rewriting stack history.
Round-4 teardown addendum (5435cf7)
Pre-RC parity round 5 (
9202192f10)Per-item fix and fail-before regression mapping:
agentSession.options; the Codex adapter pages the app-servermodel/listcatalog, filters hidden entries, and hydrates current model/default plus model-specific effort choices. Host tests cover paging/current values and mobile hook/picker tests prove the real catalog drivesagentSession.setOption; the paired-mobile RPC allowlist regression covers the remote seam.turn/startalongside model. Adapter tests assert hydrated and changed effort reaches the next turn; mobile option tests assert selection dispatch and model-dependent effort reconciliation./modeland/effortopen or apply through the structured option flow; every other advertised Codex command renders an explicit “not available in chat sessions” response, while unknown unadvertised slash text remains a prompt. Command and render-seam regressions prove advertised commands never enter the outbox or call chatonSend.A later parity audit also exposed that structured create bypassed normal selected-account launch preparation. The same commit now routes create through
prepareCodexRuntimeHomeForLaunch, so the durable record pins the selected managed account and receives the existing project-trust, hook-install, and rollout-bridge preparation;orca-runtime-structured-agent-session-create-intent.test.tsfails on the old configured/system-home behavior and proves the selected prepared home wins. Desktop/status/orchestration projections remain outside this mobile C2/RC round, consistent with the instruction not to start part D.Validation:
request_user_inputin Plan mode while Default-mode support remains behind its upstream under-development feature; the durable translation/reply tests remain green and no account feature flag was changed silently.The base remains
brennanb2025/codex-structured-mobile-readfor the C2-only review diff. After C1 lands, retarget to the branch that received C1 (normallymain) without rewriting the stack history.