Skip to content

feat(mobile): structured native Codex chat - #18074

Merged
brennanb2025 merged 7 commits into
mainfrom
brennanb2025/codex-native-chat-mobile
Sep 3, 2026
Merged

brennanb2025 merged 7 commits into
mainfrom
brennanb2025/codex-native-chat-mobile

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 43 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2551 $\color{#cf222e}{\Huge{\mathbf{−}}}$​57 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2494
Prod 79 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​3115 $\color{#cf222e}{\Huge{\mathbf{−}}}$​706 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2409

ELI5

Before this, using Codex on your phone meant looking at a terminal. The phone streamed the raw text of a command-line program and you typed into it. It worked, but it was a terminal pretending to be a chat — text reflowed oddly, there were no real messages, and anything the terminal lost was just gone.

This PR gives the phone a real chat. The phone now asks the desktop for an actual Codex session, and the desktop sends back structured messages instead of a stream of characters. You get proper message bubbles, streaming replies, an interrupt button, and a conversation that survives closing and reopening the app.

The hard part wasn't drawing the chat UI — it was making it honest about what it knows. A phone on a train loses signal mid-sentence. Most of the work here is about that: not showing a stale conversation from a session you already closed, not sending your message twice when the network hiccups, and not giving up on a connection just because the desktop was slow to answer. Those three problems are the bulk of the code and nearly all of the review.

Before / after

Before After
Codex on mobile A terminal view streaming raw TUI text A native chat with real messages
Sending a message Typed into a terminal Chat input, with streaming reply
Interrupting Terminal control keys Interrupt button; ends cleanly, no stuck state
Reopening the app Terminal reattaches, transcript can be lost Session resumes with its history
Flaky network A dropped reply could send twice on retry De-duplicated; each message lands exactly once
Slow desktop — Still connects instead of dropping the socket
Old desktop version — Falls back to the old terminal chat, no breakage

Scope

Mobile structured native Codex chat.

Also included, and deliberately called out because it is broader than the mobile lane: a fix to the shared PR CI config (.github/workflows/pr.yml, .github/actions/install-node-dependencies/action.yml, config/scripts/pr-code-change-scope.mjs). The static analysis job lints changed mobile files and resolves their types from mobile/node_modules, but CI installed only the root workspace. Mobile is a separate pnpm project, so every mobile type degraded to an error type and the gate emitted phantom findings. The fix installs mobile dependencies in that job only when mobile files changed, gated on a new tested classifier output. This affects every PR, not just this one.

Exact head

  • Branch: brennanb2025/codex-native-chat-mobile
  • Head: c0fd2dbd1efb7c6aa37a65d9fe6713d760e43d29

What changed beyond the feature

  • src/renderer/src/runtime/local-structured-session-tabs-sync.ts was split into five cohesive modules (inventory-generation-fence, inventory-refresh, snapshot-apply, snapshot-projection, subscription) with the original path kept as a barrel. No max-lines suppression and no per-file cap bump.
  • Local structured-session inventory and subscription responses are fenced by a sync generation so a toggle-off clear, reconnect restore, or retry cannot apply a mirror from a superseded instance.
  • The host-session settle census was repaired and repointed; it was already red before this branch.

Review findings and fixes

Two independent review lanes at the exact head. No P0 or P1 outstanding.

  • P1 (fixed): the mobile capability handshake treated a timeout as fatal and force-closed the socket, while an explicit rejection was tolerated and allowed to proceed. Because negotiation runs on every authentication, a slow link could become a reconnect loop that never reached connected. Direct and relay paths now share one settle helper that proceeds on any ambiguous outcome and fails only when the frame never reached the wire.
  • P2 (fixed): retained operation IDs were capped at 128 by count, so eviction could drop an unconfirmed send's dedupe ID and produce a duplicate message on retry. Retention is now bounded by the host's own expiry window.
  • Both fixes are ablation-proven: reverting each turns the new tests red (direct 3 failed, relay 1 failed).

Validation at this head

  • CI: green (28 success, 5 skipped, 0 failures). One Git compatibility failure was a confirmed flake — it passed on rerun with no code change, and that job only runs here because touching the PR workflow forces the full job matrix.
  • Renderer QA via CDP: 5/5 scenarios pass. Structured tabs hydrate and survive a full renderer reload at exactly one tab (the fence does not over-fence), toggle-off clears with nothing stale reappearing, toggle-on restores exactly once across a 6-step burst, and after an 8× unsubscribeAll storm a newly launched session still reached the mirror. Console clean.
  • iOS Simulator QA: 6/6 pass with screenshot evidence. Messages appeared exactly once across a send → interrupt → restart → resumed-send cycle, interrupt left no stuck state, and all connection lifecycles reached connected.
  • Physical iPhone: pairing, feature toggle, and spawning a Codex agent all produced structured chat.

Mixed-version behavior

No required client/host update order. Mobile probes agentSession.createSupport before mutating; a host that does not implement it degrades to normal terminal chat. An unknown result deliberately does not create a legacy sibling session. Old clients cannot advertise the capability, and old hosts reply forbidden, which both transports tolerate.

Known limitations

  • The P1's timeout branch is covered by unit tests and ablation, but was not reached end-to-end in device QA — the host answered forbidden every time, exercising the explicit-rejection branch instead.
  • All QA connections were Direct-via-LAN, so the relay transport is unexercised outside unit tests.
  • The changed-code type-aware scan is pre-existing vacuous for mobile/ (proven with a positive control); mobile's real type coverage is tsc.
  • Remaining P2s, none blocking: a restore gate arm (clientKind === undefined) that no caller reaches today, and CI store-cache/gating nits.

@coderabbitai

coderabbitai Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: 79b2cd2f-52a9-4d37-b910-0c21fb01e545

📥 Commits

Reviewing files that changed from the base of the PR and between ee09a0f and f29fdfc.

📒 Files selected for processing (2)
  • mobile/src/transport/mobile-relay-rpc-session.ts
  • mobile/src/transport/relay-pending-requests.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

This change adds structured Codex agent sessions to mobile native chat. It adds new mobile session types, prompts, options, attachments, and send flows for agent-session tabs. It adds runtime capability negotiation after authentication. It gates structured session projection and restoration on the experimental native chat setting and negotiated capabilities. It also adds renderer tab sync fencing and retirement for structured sessions. CI now detects mobile dependency changes and installs mobile dependencies when required.

Merge Risk: 🟡 Moderate · up to f29fd

Structured mobile chat can fail to establish a relay connection promptly when capability negotiation is unanswered, and previously identified structured-chat behavior issues remain open. Resolve these issues before merge.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides detailed scope, rationale, testing results, limitations, and review context. However, it omits required template sections and information, including a linked issue, explicit T… Add a valid issue reference under Linked Issue, complete or mark N/A for each required template section, include explicit Testing checklist results and platforms tested, provide before-and-after visual attachments or write N/A with a reason…
Docstring Coverage ⚠️ Warning Docstring coverage is 8.51% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 94 functions across 58 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: structured native Codex chat for mobile.
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: Description check

Explanation

The description provides detailed scope, rationale, testing results, limitations, and review context. However, it omits required template sections and information, including a linked issue, explicit Testing checklist entries, AI Disclosure, Agent skill upstream boundary, Notes, and Checklist. The required visual proof is described but no attached before-and-after evidence is shown in the provided description.

Resolution

Add a valid issue reference under Linked Issue, complete or mark N/A for each required template section, include explicit Testing checklist results and platforms tested, provide before-and-after visual attachments or write N/A with a reason, and complete the Agent skill upstream boundary, Notes, and Checklist sections.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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: 4

🧹 Nitpick comments (2)
mobile/src/session/use-mobile-structured-agent-session.test.tsx (1)

1-2: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Migrate this suite away from react-test-renderer.

This test calls create and act from react-test-renderer while the mobile package uses React 19.2.8, where this API is deprecated and emits warnings. If React Native Testing Library is the supported harness, add and configure its v14+ setup before migrating this suite.

mobile/src/session/use-mobile-native-chat-image-attachments.ts (1)

173-173: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add structuredNativeChat to the sendNativeChat dependency array.

sendNativeChat reads structuredNativeChat at Line 173 to choose between the structured send and the terminal paste path. The dependency array at Lines 313-326 omits it. Today scopeKey changes on the same tab switch that flips structuredNativeChat, so the callback is recreated and the stale value is not observable. That safety depends on two separately derived props staying coupled.

List the value it reads so the routing decision cannot go stale if scopeKey derivation changes later.

♻️ Proposed dependency addition
       readSeededLaunchDraft,
       scopeKey,
-      sleep
+      sleep,
+      structuredNativeChat
     ]

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: d29aadc5-93ae-4bd1-9eeb-f23c1d463fe1

📥 Commits

Reviewing files that changed from the base of the PR and between 058e618 and 8cbade2.

📒 Files selected for processing (89)
  • mobile/src/session/MobileNativeChatQuestion.tsx
  • mobile/src/session/MobileSessionActiveContent.tsx
  • mobile/src/session/MobileSessionHeader.tsx
  • mobile/src/session/MobileSessionSheets.tsx
  • mobile/src/session/mobile-file-tap-open.ts
  • mobile/src/session/mobile-native-chat-controller-contract.ts
  • mobile/src/session/mobile-native-chat-eligibility.test.ts
  • mobile/src/session/mobile-native-chat-eligibility.ts
  • mobile/src/session/mobile-native-chat-image-scope-state.ts
  • mobile/src/session/mobile-native-chat-question.test.ts
  • mobile/src/session/mobile-native-chat-question.ts
  • mobile/src/session/mobile-session-route-parity.test.ts
  • mobile/src/session/mobile-session-route-types.ts
  • mobile/src/session/mobile-structured-agent-prompts.ts
  • mobile/src/session/mobile-structured-agent-session-rpc.ts
  • mobile/src/session/mobile-terminal-records.test.ts
  • mobile/src/session/mobile-terminal-records.ts
  • mobile/src/session/mobile-terminal-tab-agent.test.ts
  • mobile/src/session/mobile-terminal-tab-agent.ts
  • mobile/src/session/opened-mobile-session-tab.test.ts
  • mobile/src/session/opened-mobile-session-tab.ts
  • mobile/src/session/use-mobile-file-tap-handlers.test.ts
  • mobile/src/session/use-mobile-file-tap-handlers.ts
  • mobile/src/session/use-mobile-native-chat-active-resolution.ts
  • mobile/src/session/use-mobile-native-chat-controller.test.ts
  • mobile/src/session/use-mobile-native-chat-controller.ts
  • mobile/src/session/use-mobile-native-chat-image-attachments.ts
  • mobile/src/session/use-mobile-native-chat-image-upload.ts
  • mobile/src/session/use-mobile-native-chat-session-option-controller.ts
  • mobile/src/session/use-mobile-session-attachments.ts
  • mobile/src/session/use-mobile-session-file-actions.ts
  • mobile/src/session/use-mobile-session-image-attachments.test.tsx
  • mobile/src/session/use-mobile-session-image-attachments.ts
  • mobile/src/session/use-mobile-session-native-chat-dictation.ts
  • mobile/src/session/use-mobile-session-screen-state.ts
  • mobile/src/session/use-mobile-session-tab-action-targets.ts
  • mobile/src/session/use-mobile-session-tab-switching.ts
  • mobile/src/session/use-mobile-session-terminal-send-actions.ts
  • mobile/src/session/use-mobile-structured-agent-options.ts
  • mobile/src/session/use-mobile-structured-agent-session.test.tsx
  • mobile/src/session/use-mobile-structured-agent-session.ts
  • mobile/src/session/use-mobile-structured-agent-state.ts
  • mobile/src/session/use-mobile-structured-native-chat-send-bridge.ts
  • mobile/src/transport/direct-rpc-client.ts
  • mobile/src/transport/mobile-relay-rpc-session.test.ts
  • mobile/src/transport/mobile-relay-rpc-session.ts
  • mobile/src/transport/mobile-runtime-client-capabilities.ts
  • mobile/src/transport/rpc-client-capabilities.test.ts
  • mobile/src/transport/rpc-client.test.ts
  • src/main/runtime/mobile-rpc-allowlist.test.ts
  • src/main/runtime/orca-runtime-close-structured-agent-session-tab.ts
  • src/main/runtime/orca-runtime-state-fields.ts
  • src/main/runtime/orca-runtime-structured-native-chat-settings.test.ts
  • src/main/runtime/orca-runtime-structured-session-restore.test.ts
  • src/main/runtime/rpc/core.ts
  • src/main/runtime/rpc/dispatcher-stream-options.ts
  • src/main/runtime/rpc/dispatcher.ts
  • src/main/runtime/rpc/methods/client-ui.test.ts
  • src/main/runtime/rpc/methods/index.ts
  • src/main/runtime/rpc/methods/runtime-client-capabilities.test.ts
  • src/main/runtime/rpc/methods/runtime-client-capabilities.ts
  • src/main/runtime/rpc/methods/session-tab-agent-capability-mutations.test.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-close-methods.ts
  • src/main/runtime/rpc/methods/session-tab-mutation-methods.ts
  • src/main/runtime/rpc/methods/session-tabs-inventory.ts
  • src/main/runtime/rpc/methods/session-tabs.test.ts
  • src/main/runtime/rpc/methods/session-tabs.ts
  • src/main/runtime/rpc/methods/structured-agent-session-gate.ts
  • src/main/runtime/rpc/methods/structured-agent-session-policy.ts
  • src/main/runtime/rpc/methods/structured-agent-session.test.ts
  • src/main/runtime/rpc/methods/structured-session-tab-restore.ts
  • src/main/runtime/rpc/rpc-streaming-dispatcher.ts
  • src/main/runtime/runtime-client-settings.ts
  • src/main/runtime/runtime-rpc/runtime-rpc-mobile-method-allowlist.ts
  • src/main/runtime/runtime-rpc/runtime-rpc-websocket-dispatch.ts
  • src/main/runtime/runtime-store-contract.ts
  • src/renderer/src/app-shell/use-app-startup-hydration.ts
  • src/renderer/src/app-startup-routing.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-message-projection.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-hold.ts
  • src/renderer/src/runtime/local-structured-session-tabs-sync.ts
  • src/renderer/src/store/slices/runtime-status-recheck.ts
  • src/shared/structured-agent-session-holder.ts
  • src/shared/structured-agent-session-message-projection.ts
  • src/shared/structured-agent-session-reducer.ts
  • tests/e2e/cross-version-wire/cross-version-agent-session-wire.unit.test.ts
  • tests/e2e/cross-version-wire/versioned-agent-session-wire.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Comment on lines +62 to +66
attachments?: readonly {
id?: string
path: string
previewUri: string
}[]

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 3 '\b(StructuredMobileAttachment|handleNativeChatSendWithOutcome)\b' mobile src

Repository: stablyai/orca

Length of output: 7191


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- contract ---'
cat -n mobile/src/session/mobile-native-chat-controller-contract.ts | sed -n '1,110p'

printf '%s\n' '--- structured session imports and send contract ---'
cat -n mobile/src/session/use-mobile-structured-agent-session.ts | sed -n '1,175p'

printf '%s\n' '--- attachment type declarations and exports ---'
rg -n -C 4 '\bStructuredAgentSessionAttachment\b|\bStructuredMobileAttachment\b' mobile/src

printf '%s\n' '--- applicable repository conventions ---'
find /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4 -type f -name '*.md' -print

Repository: stablyai/orca

Length of output: 19582


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- shared attachment contract ---'
rg -n -C 8 '\bexport\s+(type|interface)\s+StructuredAgentSessionAttachment\b|\bStructuredAgentSessionAttachment\b' src/shared mobile/src/session

printf '%s\n' '--- mobile conventions ---'
cat /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/conventions/mobile.md

printf '%s\n' '--- attachment consumers and controller assignment ---'
rg -n -C 5 'attachments|structuredNativeChat\.sendWithOutcome|handleNativeChatSendWithOutcome' mobile/src/session src/shared/structured-agent-session-outbox.ts

Repository: stablyai/orca

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- attachment aliases ---'
rg -n -C 3 'type (PendingNativeChatImage|StructuredNativeChatAttachment)|interface (PendingNativeChatImage|StructuredNativeChatAttachment)|StructuredNativeChatAttachment' mobile/src/session/use-mobile-native-chat-image-attachments.ts mobile/src/session/use-mobile-structured-native-chat-send-bridge.ts

printf '%s\n' '--- controller implementation and structured bridge wiring ---'
cat -n mobile/src/session/use-mobile-native-chat-controller.ts | sed -n '215,245p;315,340p'
cat -n mobile/src/session/use-mobile-native-chat-image-attachments.ts | sed -n '1,75p;160,215p'

Repository: stablyai/orca

Length of output: 14172


Define one shared mobile attachment type.

mobile-native-chat-controller-contract.ts, use-mobile-structured-agent-session.ts, and use-mobile-structured-native-chat-send-bridge.ts define equivalent { id?: string; path: string; previewUri: string } shapes. Move this mobile type to a shared module and use it in all three contracts. Keep StructuredAgentSessionAttachment as the outbox type because it intentionally omits id.

Comment on lines +326 to +328
handleNativeChatQuestionAnswer: activeChatStructured
? structuredNativeChat.respondQuestion
: legacyHandleNativeChatQuestionAnswer,

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear the held send error after an accepted structured question response.

structuredNativeChat.respondQuestion bypasses useNativeChatAcceptedAction. If a prior send failed, an accepted question answer does not call onSendResolved, so the stale failure banner remains visible. Wrap the structured question handler before returning it.

Proposed fix
+  const handleStructuredNativeChatQuestionAnswer = useNativeChatAcceptedAction(
+    structuredNativeChat.respondQuestion,
+    onSendResolved
+  )
+
     handleNativeChatQuestionAnswer: activeChatStructured
-      ? structuredNativeChat.respondQuestion
+      ? handleStructuredNativeChatQuestionAnswer
       : legacyHandleNativeChatQuestionAnswer,
📝 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
handleNativeChatQuestionAnswer: activeChatStructured
? structuredNativeChat.respondQuestion
: legacyHandleNativeChatQuestionAnswer,
const handleStructuredNativeChatQuestionAnswer = useNativeChatAcceptedAction(
structuredNativeChat.respondQuestion,
onSendResolved
)
handleNativeChatQuestionAnswer: activeChatStructured
? handleStructuredNativeChatQuestionAnswer
: legacyHandleNativeChatQuestionAnswer,

[mutate, optionState]
)

const invokeStructuredOption = useCallback(async () => false, [])

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Implement action-option dispatch.

invokeStructuredOption always returns false. optionSurface.invokeAction does not call it and only returns the current snapshot. An action option can never send its session mutation or report success. Forward the action ID to the structured-session mutation path and wire that handler into invokeAction.

Also applies to: 148-149

): Promise<MobileNativeChatSendOutcome> => {
const origin = captureSendOrigin(text.trimEnd())
if (!origin) {
onSendError('Message not sent (disconnected)')

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Localize the new structured-send error messages.

Replace these new user-facing literals with intent-named localization IDs. This keeps structured-chat errors translatable.

Based on learnings: “For newly added user-facing strings in stablyai/orca TypeScript and TSX files, follow config/i18n-translation-source.md: use intent-named localization IDs with named interpolation placeholders.”

Also applies to: 73-73

Source: Learnings

@brennanb2025
brennanb2025 marked this pull request as draft September 2, 2026 05:29

@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.

ℹ️ Minor suggestions only — this is a large but well-scoped feature with strong test coverage, and I found no blocking issues.

Reviewed changes

  • Structured session state + hold lifecycle — new useMobileStructuredAgentState/useMobileStructuredAgentSession hooks subscribe to agentSession.* over RPC, take a connection-scoped hold, cache up to 32 per-session transcripts keyed by source identity, and keep the transcript visible across reconnect/outage.
  • Prompt cards and options — approval/question journal items project to tappable cards whose responses carry opaque structured-agent-prompt: tokens (itemId/revision/optionId); model/effort options flow through agentSession.setOption.
  • Capability negotiation — mobile advertises agent-session.structured.v1 (plus hold capability) post-auth via the new runtime.clientCapabilities.update method, gated to mobile scope and further gated on the host-owned experimentalStructuredNativeChat setting.
  • Session-tab routing — agent-session tabs are threaded through session-tab inventory, close, and mutation projection, all now keyed on both capability and the host setting.

The reconnect-retention behavior, prompt-token round-trip, and post-auth capability handshake are all verified by focused unit tests, and the cross-version wire tests cover the additive RPC method against an old host. The one thing I would tighten is the option-surface stubs in use-mobile-structured-agent-options.ts, noted inline.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

[mutate, optionState]
)

const invokeStructuredOption = useCallback(async () => false, [])

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.

invokeStructuredOption is a hardcoded async () => false, and the optionSurface you build below stubs invokeAction/setOption (the latter returns the pre-commit optionSnapshot). Today this is unreachable because mobile structured snapshots are built with liveTransport: 'agent-session', so actionForApply never emits an action row. But the hook still advertises a working SessionOptionsSurface with invokeAction/setOption, so the moment Codex surfaces an agent-picker or toggle-command option the picker will render a row that silently no-ops. Either implement these two paths (or delete optionSurface from the return type if it is genuinely unused) so the advertised contract and the behavior agree.

@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 in the incremental commit.

Reviewed changes

  • SSH termination result contract — ssh:terminateSessions now returns SshTerminateSessionsResult { terminated, unverifiable } instead of void, with the verdict threaded through SshApi, ssh-bridge, the web-terminal-api stub, and the shared ssh-types. The handler counts successful/non-isSshPtyNotFoundError shutdowns as terminated and unreachable (no-provider) expired leases as unverifiable, and the updated tests pin each branch (no-provider, empty, live-kill, tombstone, failure-preserves-lease).

The change is type-consistent across the preload and web surfaces and the verdict vocabulary matches docs/reference/ssh-execution-boundary.md. I have one scope note below, not a correctness concern.

ℹ️ Scope note — this commit is outside the PR's stated scope

The PR body states the scope is "Mobile structured native Codex chat lane only" with "no broad integration branch," but 6afbe28 ("align SSH termination bridge result contract") touches only the desktop SSH termination surface, which nothing in the structured-chat lane consumes. The new return value is not read by any caller yet (ssh-session-termination.ts and ssh-target-remove.ts both ignore it), so this reads as either an accidental inclusion or a precursor to a follow-up. Worth confirming intent before merging — split it out if it is unrelated, or note the follow-up consumer if one is planned.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@brennanb2025
brennanb2025 force-pushed the brennanb2025/codex-native-chat-mobile branch from b47414b to 62aeea7 Compare September 3, 2026 06:59
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Native iOS Simulator QA — $orca-mobile-emulator-qa

Tested SHA: 62aeea7c517738523e3af205b150e8fcdb6365ab (exact PR head at time of run; gh pr view 18074 --json headRefOid matched local HEAD).

Rig — dedicated device PR18074 QA iPhone (iOS 26.5), Expo dev client on Metro from this branch's mobile/ (Connected to: http://127.0.0.1:8099, port private to this lane), paired LAN-only to an isolated Electron dev host built from the same worktree (own ORCA_DEV_USER_DATA_PATH, own renderer + CDP ports). All UI driven through orca emulator (AX tree → tap / type / long-press gesture). Three peer simulators and their helpers were left untouched. No code was edited; no evidence was committed.

Structured chat ON

App loaded and paired (Direct · LAN), fixture repo visible:

home

Bare Codex tab from the mobile New Tab sheet opens as a structured Codex Chat agent-session tab — composer with @files / /commands, Tools, Attach image, Dictate, and a live model chip:

structured empty state

Real Codex round-trip with a tool call — reasoning line, shell row with expandable command JSON + output, then the answer:

tool call turn

Also exercised on the structured path:

  • Model picker — opened, switched GPT-5.6-Sol → GPT-5.6-Luna, composer chip updated and the choice survived a settings flip and session restore.
  • Image attachment — added a photo to the simulator library, Attach image → picker → composer chip → sent; Codex read the image correctly.

image attachment

  • Tab close — long-press → Close on a second structured tab; host agent-sessions.json flipped that record's lease.claimStatus to released while the other stayed live.
  • Restore — toggling experimentalStructuredNativeChat off hid the structured tab (0 tabs); toggling it back on restored the tab with its full transcript, and a follow-up message round-tripped on the resumed session.
  • Host-side: the provider supervisor and its codex … app-server child were confirmed as descendants of the QA Electron pid.

Structured chat OFF — legacy terminal

With the host setting off, a Codex tab falls back through session.tabs.createTerminal to a legacy terminal tab running the raw Codex TUI:

legacy terminal

Long-press on that terminal tab offers Switch to chat view; switching in, sending a message, and switching back showed the same exchange in the raw TUI — the per-session toggle round-trips both directions without losing history:

switched back to terminal

Desktop Simulator window capture (Orca Computer Use, screenshot only — no OS-level input was used):

simulator window

Minor observation

On a terminal-backed chat view the model chip renders as Model with no value until the transcript resolves, then reads GPT-5.6 Sol Reasoning effort — the reasoning-effort segment has no value, unlike the structured session's GPT-5.6-Sol High. Cosmetic only; no functional impact seen.

No redbox, no "Could not connect to development server", and no failed interaction in the run.

@brennanb2025
brennanb2025 force-pushed the brennanb2025/codex-native-chat-mobile branch from 62aeea7 to 4e6197a Compare September 3, 2026 07:57
Merge Sim added 3 commits September 3, 2026 11:18
…retention

Fence local structured-session inventory and subscription responses with a
sync generation so a toggle-off clear, reconnect restore, or retry cannot
apply a mirror from a superseded instance. Bound mobile ambiguous
operation-ID retention at 128 with unmount cleanup.

Staged on the reconcile branch only: the sync module is now 312 lines and
needs a real split before this can reach the PR head.
…sis mobile types

The local structured session-tabs sync module outgrew the 300-line cap once it
took on generation fencing, so split it along its real seams instead of raising
the cap: the generation/cursor fence, snapshot projection, snapshot apply,
inventory refresh, and the subscription loop. The original path stays as a
barrel so no importer moves.

Repoint the host-session-mirror settle census at the apply module, which owns
two receipts now — the snapshot it mirrors in, and the toggle-off teardown that
retracts what it published. The teardown receipt is named rather than anonymous
so the pin says which direction it settles.

The changed-code quality gate lints mobile files and resolves their types from
mobile/node_modules, but mobile is a separate pnpm project that the root install
never populates, so every mobile type degraded to an `error` type and the gate
reported phantom findings. Install mobile dependencies in static analysis when
the diff touches mobile, gated on a new classifier output.
The mobile capability update is an advisory whose result is discarded, yet an
unanswered one was fatal while an explicit rejection was tolerated. A 5s timeout
on the direct client force-closed the socket, and on the relay path it failed
`confirmResume` before `connected` was ever published, so a consistently slow
link redialled forever. Both paths now share one helper that settles every
ambiguous outcome (timeout, mid-flight drop) like a rejection and rejects only
when the frame never reached the wire — the one case nothing else recovers from,
since the socket's own desync force-close is gated on already being connected.
The generation guard still keeps a replaced session from connecting.

Retained structured-session operation ids were capped at 128 with oldest-first
eviction, but every retained id belongs to a send whose outcome is unknown, so
eviction turned a user's retry into a second message on the host. Bound the map
by expiry against the id's own embedded timestamp instead, mirroring the host's
operation ledger, so no id is released while the host would still honour it.

Also give the mobile CI install the root install's lockfile drift guard (mobile's
lockfile carries patchedDependencies a silent rewrite would drop), gate
mobile_dependencies on should_run, and key the pnpm store cache on both lockfiles.
@brennanb2025
brennanb2025 marked this pull request as ready for review September 3, 2026 20:54
@greptile-apps

greptile-apps Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

Too many files changed for review (122 files, 100 file limit).

Bypass the limit by tagging @greptile-apps to review.

@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@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: Team

Run ID: 32102bd9-2dce-48c3-83b6-3fc05037d377

📥 Commits

Reviewing files that changed from the base of the PR and between 316ec38 and c0fd2db.

📒 Files selected for processing (121)
  • .github/actions/install-node-dependencies/action.yml
  • .github/workflows/pr.yml
  • config/scripts/pr-code-change-scope.mjs
  • config/scripts/pr-code-change-scope.test.mjs
  • mobile/src/session/MobileNativeChatQuestion.tsx
  • mobile/src/session/MobileSessionActiveContent.tsx
  • mobile/src/session/MobileSessionHeader.tsx
  • mobile/src/session/MobileSessionSheets.tsx
  • mobile/src/session/mobile-file-tap-open.ts
  • mobile/src/session/mobile-native-chat-controller-contract.ts
  • mobile/src/session/mobile-native-chat-eligibility.test.ts
  • mobile/src/session/mobile-native-chat-eligibility.ts
  • mobile/src/session/mobile-native-chat-image-scope-state.ts
  • mobile/src/session/mobile-native-chat-question.test.ts
  • mobile/src/session/mobile-native-chat-question.ts
  • mobile/src/session/mobile-session-route-parity.test.ts
  • mobile/src/session/mobile-session-route-types.ts
  • mobile/src/session/mobile-structured-agent-prompts.ts
  • mobile/src/session/mobile-structured-agent-session-launch.test.ts
  • mobile/src/session/mobile-structured-agent-session-launch.ts
  • mobile/src/session/mobile-structured-agent-session-rpc.ts
  • mobile/src/session/mobile-structured-session-operation-retention.test.ts
  • mobile/src/session/mobile-terminal-records.test.ts
  • mobile/src/session/mobile-terminal-records.ts
  • mobile/src/session/mobile-terminal-tab-agent.test.ts
  • mobile/src/session/mobile-terminal-tab-agent.ts
  • mobile/src/session/opened-mobile-session-tab.test.ts
  • mobile/src/session/opened-mobile-session-tab.ts
  • mobile/src/session/use-mobile-file-tap-handlers.test.ts
  • mobile/src/session/use-mobile-file-tap-handlers.ts
  • mobile/src/session/use-mobile-native-chat-active-resolution.ts
  • mobile/src/session/use-mobile-native-chat-controller.test.ts
  • mobile/src/session/use-mobile-native-chat-controller.ts
  • mobile/src/session/use-mobile-native-chat-image-attachments.ts
  • mobile/src/session/use-mobile-native-chat-image-upload.ts
  • mobile/src/session/use-mobile-native-chat-session-option-controller.ts
  • mobile/src/session/use-mobile-session-attachments.ts
  • mobile/src/session/use-mobile-session-file-actions.ts
  • mobile/src/session/use-mobile-session-image-attachments.test.tsx
  • mobile/src/session/use-mobile-session-image-attachments.ts
  • mobile/src/session/use-mobile-session-native-chat-dictation.ts
  • mobile/src/session/use-mobile-session-screen-state.ts
  • mobile/src/session/use-mobile-session-tab-action-targets.ts
  • mobile/src/session/use-mobile-session-tab-switching.ts
  • mobile/src/session/use-mobile-session-terminal-create-actions.test.ts
  • mobile/src/session/use-mobile-session-terminal-create-actions.ts
  • mobile/src/session/use-mobile-session-terminal-send-actions.ts
  • mobile/src/session/use-mobile-structured-agent-options.ts
  • mobile/src/session/use-mobile-structured-agent-session.test.tsx
  • mobile/src/session/use-mobile-structured-agent-session.ts
  • mobile/src/session/use-mobile-structured-agent-state.ts
  • mobile/src/session/use-mobile-structured-native-chat-send-bridge.ts
  • mobile/src/transport/cellular-connecting-label-stall.test.ts
  • mobile/src/transport/direct-connection-log.ts
  • mobile/src/transport/direct-rpc-client.ts
  • mobile/src/transport/foreground-stale-dial-restart.test.ts
  • mobile/src/transport/mobile-relay-rpc-session-liveness.test.ts
  • mobile/src/transport/mobile-relay-rpc-session.test.ts
  • mobile/src/transport/mobile-relay-rpc-session.ts
  • mobile/src/transport/mobile-runtime-capability-negotiation.test.ts
  • mobile/src/transport/mobile-runtime-capability-negotiation.ts
  • mobile/src/transport/mobile-runtime-client-capabilities.ts
  • mobile/src/transport/rpc-client-capabilities.test.ts
  • mobile/src/transport/rpc-client-connect-wait-replay.test.ts
  • mobile/src/transport/rpc-client-delivery-ambiguity.test.ts
  • mobile/src/transport/rpc-client-request-deadline.test.ts
  • mobile/src/transport/rpc-client-request-tracker.ts
  • mobile/src/transport/rpc-client-runtime-events.test.ts
  • mobile/src/transport/rpc-client-synthesized-close-diagnostics.test.ts
  • mobile/src/transport/rpc-client-terminal-reconnect.test.ts
  • mobile/src/transport/rpc-client-unauthorized-close.test.ts
  • mobile/src/transport/rpc-client.test.ts
  • mobile/src/transport/rpc-session-liveness-integration.test.ts
  • src/main/runtime/mobile-rpc-allowlist.test.ts
  • src/main/runtime/orca-runtime-close-structured-agent-session-tab.ts
  • src/main/runtime/orca-runtime-state-fields.ts
  • src/main/runtime/orca-runtime-structured-native-chat-settings.test.ts
  • src/main/runtime/orca-runtime-structured-session-restore.test.ts
  • src/main/runtime/rpc/core.ts
  • src/main/runtime/rpc/dispatcher-stream-options.ts
  • src/main/runtime/rpc/dispatcher.ts
  • src/main/runtime/rpc/methods/client-ui.test.ts
  • src/main/runtime/rpc/methods/index.ts
  • src/main/runtime/rpc/methods/runtime-client-capabilities.test.ts
  • src/main/runtime/rpc/methods/runtime-client-capabilities.ts
  • src/main/runtime/rpc/methods/session-tab-agent-capability-mutations.test.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-close-methods.ts
  • src/main/runtime/rpc/methods/session-tab-mutation-methods.ts
  • src/main/runtime/rpc/methods/session-tabs-inventory.ts
  • src/main/runtime/rpc/methods/session-tabs.test.ts
  • src/main/runtime/rpc/methods/session-tabs.ts
  • src/main/runtime/rpc/methods/structured-agent-session-gate.ts
  • src/main/runtime/rpc/methods/structured-agent-session-policy.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/rpc-streaming-dispatcher.ts
  • src/main/runtime/runtime-client-settings.ts
  • src/main/runtime/runtime-rpc/runtime-rpc-mobile-method-allowlist.ts
  • src/main/runtime/runtime-rpc/runtime-rpc-websocket-dispatch.ts
  • src/main/runtime/runtime-store-contract.ts
  • src/renderer/src/app-shell/use-app-startup-hydration.ts
  • src/renderer/src/app-startup-routing.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-message-projection.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-hold.ts
  • src/renderer/src/runtime/host-session-mirror-settle-census.test.ts
  • src/renderer/src/runtime/local-structured-session-tab-retirement.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/inventory-refresh.ts
  • src/renderer/src/runtime/local-structured-session-tabs-sync/snapshot-apply.ts
  • src/renderer/src/runtime/local-structured-session-tabs-sync/snapshot-projection.ts
  • src/renderer/src/runtime/local-structured-session-tabs-sync/subscription.ts
  • src/shared/structured-agent-session-holder.ts
  • src/shared/structured-agent-session-message-projection.ts
  • src/shared/structured-agent-session-reducer.ts
  • tests/e2e/cross-version-wire/cross-version-agent-session-wire.unit.test.ts
  • tests/e2e/cross-version-wire/versioned-agent-session-wire.ts
🚧 Files skipped from review as they are similar to previous changes (79)
  • src/main/runtime/orca-runtime-structured-native-chat-settings.test.ts
  • src/main/runtime/runtime-client-settings.ts
  • mobile/src/session/mobile-session-route-parity.test.ts
  • src/main/runtime/rpc/methods/client-ui.test.ts
  • tests/e2e/cross-version-wire/versioned-agent-session-wire.ts
  • src/renderer/src/components/native-chat/structured-agent-session-message-projection.ts
  • src/main/runtime/rpc/rpc-streaming-dispatcher.ts
  • src/main/runtime/rpc/methods/index.ts
  • mobile/src/session/mobile-session-route-types.ts
  • src/main/runtime/runtime-rpc/runtime-rpc-websocket-dispatch.ts
  • mobile/src/session/MobileSessionActiveContent.tsx
  • mobile/src/session/use-mobile-session-screen-state.ts
  • mobile/src/session/mobile-native-chat-eligibility.test.ts
  • mobile/src/session/mobile-terminal-records.ts
  • mobile/src/session/use-mobile-session-attachments.ts
  • src/main/runtime/rpc/methods/session-tabs.test.ts
  • src/main/runtime/rpc/methods/session-tab-mutation-methods.ts
  • src/renderer/src/app-shell/use-app-startup-hydration.ts
  • mobile/src/session/mobile-native-chat-question.test.ts
  • mobile/src/session/use-mobile-native-chat-image-upload.ts
  • mobile/src/session/mobile-terminal-tab-agent.test.ts
  • src/main/runtime/rpc/core.ts
  • mobile/src/session/use-mobile-file-tap-handlers.test.ts
  • mobile/src/session/mobile-terminal-tab-agent.ts
  • mobile/src/session/use-mobile-session-image-attachments.ts
  • mobile/src/session/MobileSessionSheets.tsx
  • src/shared/structured-agent-session-message-projection.ts
  • mobile/src/session/mobile-terminal-records.test.ts
  • src/main/runtime/rpc/methods/session-tab-agent-status-projection.ts
  • mobile/src/session/use-mobile-file-tap-handlers.ts
  • src/main/runtime/runtime-rpc/runtime-rpc-mobile-method-allowlist.ts
  • src/main/runtime/rpc/methods/runtime-client-capabilities.test.ts
  • src/main/runtime/rpc/methods/structured-agent-session-gate.ts
  • mobile/src/session/mobile-native-chat-image-scope-state.ts
  • mobile/src/session/use-mobile-session-tab-action-targets.ts
  • src/main/runtime/rpc/methods/structured-session-tab-restore.ts
  • mobile/src/session/opened-mobile-session-tab.ts
  • mobile/src/session/mobile-native-chat-eligibility.ts
  • src/shared/structured-agent-session-holder.ts
  • src/main/runtime/rpc/methods/structured-agent-session-policy.ts
  • src/main/runtime/rpc/methods/session-tab-close-methods.ts
  • mobile/src/session/MobileNativeChatQuestion.tsx
  • mobile/src/session/use-mobile-session-image-attachments.test.tsx
  • mobile/src/session/use-mobile-session-native-chat-dictation.ts
  • mobile/src/session/mobile-structured-agent-prompts.ts
  • mobile/src/session/mobile-native-chat-controller-contract.ts
  • mobile/src/transport/mobile-runtime-client-capabilities.ts
  • src/renderer/src/app-startup-routing.test.ts
  • mobile/src/session/use-mobile-structured-agent-session.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-hold.ts
  • src/main/runtime/rpc/methods/runtime-client-capabilities.ts
  • mobile/src/session/use-mobile-native-chat-controller.test.ts
  • src/main/runtime/orca-runtime-structured-session-restore.test.ts
  • src/main/runtime/rpc/methods/session-tab-agent-capability-mutations.test.ts
  • src/main/runtime/orca-runtime-state-fields.ts
  • mobile/src/session/use-mobile-session-file-actions.ts
  • mobile/src/session/use-mobile-native-chat-active-resolution.ts
  • mobile/src/session/use-mobile-native-chat-controller.ts
  • src/main/runtime/rpc/methods/session-tab-agent-status-projection.test.ts
  • src/shared/structured-agent-session-reducer.ts
  • src/main/runtime/rpc/methods/session-tabs-inventory.ts
  • src/main/runtime/rpc/dispatcher.ts
  • mobile/src/session/use-mobile-session-terminal-send-actions.ts
  • src/main/runtime/rpc/dispatcher-stream-options.ts
  • src/main/runtime/runtime-store-contract.ts
  • src/main/runtime/rpc/methods/session-tabs.ts
  • tests/e2e/cross-version-wire/cross-version-agent-session-wire.unit.test.ts
  • mobile/src/transport/mobile-relay-rpc-session.ts
  • mobile/src/session/opened-mobile-session-tab.test.ts
  • mobile/src/session/use-mobile-structured-agent-session.test.tsx
  • mobile/src/session/use-mobile-structured-agent-state.ts
  • mobile/src/session/mobile-native-chat-question.ts
  • src/main/runtime/mobile-rpc-allowlist.test.ts
  • mobile/src/session/use-mobile-session-tab-switching.ts
  • mobile/src/session/MobileSessionHeader.tsx
  • mobile/src/session/use-mobile-structured-agent-options.ts
  • mobile/src/session/use-mobile-native-chat-session-option-controller.ts
  • mobile/src/session/mobile-file-tap-open.ts
  • mobile/src/session/use-mobile-native-chat-image-attachments.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.

)
const enabled = useAppStore((state) => state.settings?.experimentalStructuredNativeChat === true)
useEffect(() => {
if (!ready) {

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear disabled tabs before the readiness return.

If experimentalStructuredNativeChat becomes false while startup restoration is pending, the effect returns with ready === false. The pending restore can then apply structured-session tabs before readiness completes, so disabled tabs remain visible until ready changes. Move the disable branch before the readiness branch.

@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.

ℹ️ Minor suggestions only — this pass came back clean at the current head; the P1/P2 fixes from earlier rounds are baked in, and the one remaining rough edge is a latent optionSurface contract, noted inline.

Reviewed changes

  • Post-auth capability handshake — mobile advertises agent-session.structured.v1 + hold via the additive runtime.clientCapabilities.update; direct and relay transports now share a settle helper that proceeds on any ambiguous outcome and fails only when the advisory never reached the wire.
  • Structured session state + hold lifecycle — new useMobileStructuredAgentState/useMobileStructuredAgentSession hooks subscribe to agentSession.*, take connection-scoped holds, cache up to 32 transcripts keyed by host/workspace/agent/session, and keep the transcript visible across reconnect (loading now spreads prior state).
  • Prompt cards + options — approval/question journal items project to tappable cards whose responses carry opaque structured-agent-prompt: tokens (itemId/revision/optionId); model/effort flow through agentSession.setOption.
  • Host policy + projection — mobile structured chat is gated on both the client capability and the host-owned experimentalStructuredNativeChat setting, threaded through session-tab inventory, close, mutation, and restore.
  • Operation-id retention — retained dedupe IDs are now bounded by the host expiry window instead of a 128-entry count, so an unconfirmed send's ID can't be evicted into a duplicate.
  • Launch integrity — createMobileStructuredCodexSession probes createSupport, replays the durable create envelope once, and returns unknown (never a legacy sibling) on unclassifiable outcomes.
  • Shared CI fix — static analysis installs mobile deps only when mobile files change, so its type-aware pass stops emitting phantom error-type findings.

No blocking issues. I traced the handleAuthenticated capability-settle/force-close paths, the structuredAgentSessionHolderId extraction, the structured-agent-session-policy restore gate against the previous capability-only gate, and the self-declared-capability trust boundary (client caps were already shape-validated, not allowlisted, at auth — the real gate is the host-owned setting, which is correctly not client-mutable).

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

const setOption = useCallback(
async (id: string, value: SessionOptionValue) => {
await setStructuredOption(id, value)
return { snapshot: optionSnapshot }

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.

optionSurface.setOption resolves to the pre-mutation optionSnapshot captured in this closure, and invokeAction/invokeStructuredOption are hardwired no-ops — so any future consumer of the returned SessionOptionsSurface reads a stale snapshot or silently does nothing. Nothing in the production path calls optionSurface.setOption/invokeAction today (the pickers drive setStructuredOption directly), which is why this is latent rather than observable. Minimally, return the post-commit snapshot from setStructuredOption (or drop optionSurface until an action-bearing option kind exists) instead of resolving to the stale optionSnapshot.

Resolves one conflict in mobile/src/transport/mobile-relay-rpc-session.test.ts,
where both sides added tests at the same point: this branch's capability-handshake
regressions (an older runtime rejecting negotiation, and a relay that never answers)
and main's relay dial-stage tracking from the stage-aware dial bound. Both cover
slow-link behavior in the same transport and both are kept.
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@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.

ℹ️ Merge is clean — one timing seam to reconcile at the relay/migration boundary, noted inline.

Reviewed changes

  • Merged main (d66386b) — brought the stage-aware relay dial bound (#18518) into connectMobileRelayRpcSession: the relay session now tracks RelayDialStage (opening → awaiting-hello → handshaking → confirming) and exposes RelayDialStageSource, so waitForAuthenticated re-arms a per-stage budget instead of a flat bound. The PR's capability settle and delivery-ambiguity logic, and the credential-version rejection test, all survive the merge intact.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

resumeExpiresAt = result.resumeConfirmation.resumeExpiresAt
lastConnectedAt = Date.now()
// Why: an unanswered advisory must not keep a slow relay from ever reaching connected.
await settleMobileRuntimeCapabilities((method, params) =>

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.

settleMobileRuntimeCapabilities now runs inside the relay confirming dial stage, which waitForAuthenticated bounds at 35s (relay-dial-stage.ts sized it as 30s resume-confirmation + slack). The settle serializes a second, independently 30s-bound sendRpc onto that same stage, so a resume that confirms slowly under lock contention plus an unanswered advisory can keep confirming well past 35s and get its migration cut — the same silent hang-up c0fd2db removed from the primary path, still live in the replacement path.

Technical details
# Confirming dial-stage budget doesn't account for the capability settle

## Affected sites
- mobile/src/transport/mobile-relay-rpc-session.ts:176 — `dialStage.advance('confirming')` opens the stage enclosing both the resume-confirmation RPC and the settle
- mobile/src/transport/mobile-relay-rpc-session.ts:195 — `await settleMobileRuntimeCapabilities(...)` adds up to `requestTimeoutMs` (30s) of serialized latency to `confirming`
- mobile/src/transport/relay-dial-stage.ts:59 — `RELAY_DIAL_STAGE_BUDGET_MS.confirming = 35_000` (30s resume-confirmation + slack)
- mobile/src/transport/replacement-session-authentication.ts:66 — re-arms `relayDialStageBudgetMs(stage)` on each stage and rejects at the `confirming` budget; `stable-logical-rpc-client.ts:232` (`migrateTo`) then closes `nextSession` and retries

## Required outcome
- A relay whose resume-confirmation succeeds slowly but whose capability advisory is unanswered must not have its migration cut while it is still progressing toward `connected`. The `confirming` budget and the settle timing must agree.

## Suggested approach (optional)
- Enlarge the `confirming` budget to cover resume-confirmation + settle, or advance a distinct stage after the resume-confirmation so the migration bound re-arms across the settle instead of lumping both under one 35s window.

## Open questions for the human (optional)
- Is holding `confirming` open for the advisory deliberate? `settleMobileRuntimeCapabilities` already proceeds on any ambiguous outcome, so blocking `connected` on it adds up to 30s of latency for no routing gain in the common case.

The merge composed two independently-sized changes — this branch's capability
handshake settle and main's dial-stage tracking — pushing the relay session file
to 304 lines against a 300 cap. Neither side broke it alone.

Move the in-flight request registry (id generation, tracking, settlement, and
reject-all with its delivery-ambiguity marking) into RelayPendingRequests,
matching the existing collaborator pattern alongside RelayDialStageTracker and
RpcSessionLivenessWatchdog. No behavior change.
@brennanb2025
brennanb2025 merged commit 98e77ef into main Sep 3, 2026
38 checks passed
Jinwoo-H added a commit that referenced this pull request Sep 3, 2026
Reconciles main's structured native Codex chat (#18074) and the stage-aware
relay dial bound (#18518) with the hybrid operations layer:

- native-chat controller keeps the operations/target/disconnect-retention
  seam and routes agent-session tabs through the structured hooks; the
  active-resolution and terminal-write hooks are extracted to stay under
  the line cap
- image attachments keep the hosted attachImage/pasteImages path and add
  main's structured (paste-free) send
- bare Codex launches take the structured path only on the native client;
  hosted adapter creates stay on the adapter
- file taps resolve against the source session tab when a structured chat
  has no backing terminal
- relay session advertises client capabilities after resume confirm
- package downloader contract split out to break the import cycle the
  native code-quality audit now rejects

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
* feat(mobile): finalize structured native Codex chat

* fix(mobile): close structured chat lifecycle gaps

* wip(mobile): fence stale structured inventory and bound operation-id retention

Fence local structured-session inventory and subscription responses with a
sync generation so a toggle-off clear, reconnect restore, or retry cannot
apply a mirror from a superseded instance. Bound mobile ambiguous
operation-ID retention at 128 with unmount cleanup.

Staged on the reconcile branch only: the sync module is now 312 lines and
needs a real split before this can reach the PR head.

* fix(ci): split the structured session-tabs sync and give static analysis mobile types

The local structured session-tabs sync module outgrew the 300-line cap once it
took on generation fencing, so split it along its real seams instead of raising
the cap: the generation/cursor fence, snapshot projection, snapshot apply,
inventory refresh, and the subscription loop. The original path stays as a
barrel so no importer moves.

Repoint the host-session-mirror settle census at the apply module, which owns
two receipts now — the snapshot it mirrors in, and the toggle-off teardown that
retracts what it published. The teardown receipt is named rather than anonymous
so the pin says which direction it settles.

The changed-code quality gate lints mobile files and resolves their types from
mobile/node_modules, but mobile is a separate pnpm project that the root install
never populates, so every mobile type degraded to an `error` type and the gate
reported phantom findings. Install mobile dependencies in static analysis when
the diff touches mobile, gated on a new classifier output.

* fix(mobile): let a slow capability handshake still reach connected

The mobile capability update is an advisory whose result is discarded, yet an
unanswered one was fatal while an explicit rejection was tolerated. A 5s timeout
on the direct client force-closed the socket, and on the relay path it failed
`confirmResume` before `connected` was ever published, so a consistently slow
link redialled forever. Both paths now share one helper that settles every
ambiguous outcome (timeout, mid-flight drop) like a rejection and rejects only
when the frame never reached the wire — the one case nothing else recovers from,
since the socket's own desync force-close is gated on already being connected.
The generation guard still keeps a replaced session from connecting.

Retained structured-session operation ids were capped at 128 with oldest-first
eviction, but every retained id belongs to a send whose outcome is unknown, so
eviction turned a user's retry into a second message on the host. Bound the map
by expiry against the id's own embedded timestamp instead, mirroring the host's
operation ledger, so no id is released while the host would still honour it.

Also give the mobile CI install the root install's lockfile drift guard (mobile's
lockfile carries patchedDependencies a silent rewrite would drop), gate
mobile_dependencies on should_run, and key the pnpm store cache on both lockfiles.

* refactor(mobile): extract the relay pending-request registry

The merge composed two independently-sized changes — this branch's capability
handshake settle and main's dial-stage tracking — pushing the relay session file
to 304 lines against a 300 cap. Neither side broke it alone.

Move the in-flight request registry (id generation, tracking, settlement, and
reject-all with its delivery-ambiguity marking) into RelayPendingRequests,
matching the existing collaborator pattern alongside RelayDialStageTracker and
RpcSessionLivenessWatchdog. No behavior change.

---------

Co-authored-by: Merge Sim <sim@local>
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