Repository navigation
feat(mobile): structured native Claude chat - #18741
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Team Run ID: 📥 CommitsReviewing files that changed from the base of the PR and between c5830e5c51dd963c0f4988d104bf75dc7cc60f78 and 9d5afd9bf2644363369d0b30e4d6233965f0a9cd. 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review. 📝 WalkthroughWalkthroughThe mobile structured agent-session flow now supports recognized providers such as Claude instead of only Codex. Shared helpers build provider-specific session identifiers and replayable creation envelopes. Mobile capability advertisements include Claude support. Grouped structured questions now project one question at a time, preserve draft answers, validate responses, and submit encoded answer groups. Tests cover provider handling, capability limits, session launch behavior, and grouped-question flows. Merge Risk: ⚪ Minimal · up to Mobile Claude structured chat now supports provider-aware sessions and grouped question responses. Duplicate-label selections preserve row identity, including tokenless options, with no remaining actionable merge risk. 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Full details: Description checkExplanation The description provides detailed change rationale and verification, but it omits required template sections and information, including a linked issue, visual proof or an explicit N/A explanation, the testing checklist, AI disclosure, review section, agent skill boundary checklist, and final checklist. Resolution Update the description to follow the repository template. Add the required linked issue, provide before/after visual proof or write N/A with a reason, complete the Testing section and checkboxes, address AI Disclosure, Review, Agent skill upstream boundary, Notes, and Checklist items, and retain the existing technical details. ✨ Finishing Touches 💡 1📝 Generate docstrings 💡
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Team
Run ID: f8231cd0-9312-4352-8b75-27227b697763
📒 Files selected for processing (19)
mobile/src/session/mobile-native-chat-eligibility.test.tsmobile/src/session/mobile-native-chat-eligibility.tsmobile/src/session/mobile-session-route-parity.test.tsmobile/src/session/mobile-session-route-types.tsmobile/src/session/mobile-structured-agent-prompts-grouped.test.tsmobile/src/session/mobile-structured-agent-prompts.tsmobile/src/session/mobile-structured-agent-session-launch.test.tsmobile/src/session/mobile-structured-agent-session-launch.tsmobile/src/session/mobile-structured-agent-session-rpc.tsmobile/src/session/mobile-structured-grouped-question.test.tsmobile/src/session/mobile-structured-grouped-question.tsmobile/src/session/use-mobile-session-terminal-create-actions.tsmobile/src/session/use-mobile-structured-agent-session.tsmobile/src/session/use-mobile-structured-prompt-responses.tsmobile/src/transport/mobile-runtime-client-capabilities.test.tsmobile/src/transport/mobile-runtime-client-capabilities.tsmobile/src/transport/rpc-client-capabilities.test.tssrc/renderer/src/lib/launch-structured-agent-session.tssrc/shared/structured-agent-session-create.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 3 remain after this review.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Capability gate —
MOBILE_RUNTIME_CLIENT_CAPABILITIESadvertisesagent-session.structured.claude.v1, with a new test pinning the list against the host's fail-closed parser bounds (≤64 entries, ≤128 chars, no duplicates). - Eligibility + route type —
resolveMobileNativeChatresolves anyisAgentSessionHandleProvideragent foragent-sessiontabs, andMobileSessionTabwidensagent: 'codex'→AgentSessionHandleProvider. - Agent-agnostic launch —
createMobileStructuredCodexSessionbecomescreateMobileStructuredAgentSession(client, worktreeId, agent), routing bare Claude launches throughagentSession.createSupportand naming the refusing agent in failure copy. - Shared create envelope — new
structured-agent-session-create.tsdedupes the renderer/mobileagentSession.createenvelope build. I verified it is byte-behavior-preserving:structuredAgentSessionCreateFingerprintdelegates tostructuredAgentSessionPayloadFingerprint({ method: 'agentSession.create', sessionId, fields: { worktree, agent } }), matching both old inline sites. - Grouped Claude
AskUserQuestion—projectStructuredQuestionroutesbody.questionsthrough the newmobile-structured-grouped-question.ts, stepping one question at a time and submitting the whole group via the sharedencodeAgentSessionQuestionAnswers. This fixes a genuine stall (empty flatoptions: []card) and reuses the host-accepted encoding/validation. - State split — prompt responses + grouped draft move to
useMobileStructuredPromptResponses.
I traced the grouped-answer wire path end to end: option ids are the journal encodeClaudeQuestionOptionId('qN','choice-M') ids, questionId is the qN address, and free text keys on freeTextQuestionId; the host's performPrompt/groupedQuestionResponse accept this shape via isValidAgentSessionQuestionAnswers. The token encoding is collision-safe (percent-encoded JSON keeps the : separator and ', ' multi-select join unambiguous), and single-question Claude prompts route correctly through the grouped branch since the host always sets body.questions.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (since prior pullfrog review @ 8dc782d)
- Derived grouped-question draft —
useMobileStructuredPromptResponsesnow stores{ sessionKey, draft }and derivesgroupedDrafton read, dropping theuseEffect/useLayoutEffect/useRefmachinery that cleared the draft on session switch.
I verified the refactor is behavior-preserving and correct: respondQuestion reads the derived groupedDraft and lists it (plus sessionKey) in its dependency array, so the stale-closure problem the old groupedDraftRef handled is now solved by React's own memoization. The sessionKey check on read ensures a draft collected in another session is never projected into the current one, and the per-prompt promptKey (itemId:revision) check still discards drafts against a superseded revision. A revised prompt or a resolved question still resets to step one, and a pending group still starts from the first unanswered question. No functional regression.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Team
Run ID: 8d93ff77-b129-4331-a262-23fc99e81eea
📒 Files selected for processing (2)
mobile/src/session/mobile-structured-grouped-question.test.tsmobile/src/session/use-mobile-structured-prompt-responses.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 0 remain after this review.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
mobile/src/session/MobileNativeChatQuestion.tsx (1)
56-56: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPreserve the selected option index in the single-select fallback.
Line 56 calls
formatQuestionAnswer(question, [option])when the selected option has no token. That formatter usesoptions.indexOf(label). If duplicate labels exist and only the first duplicate has a token, selecting a later plain-bullet row submits the first row token.Use the index-based formatter for this path. Add a test with duplicate labels and
optionTokens: ['first-token', null].Proposed fix
- const token = question.optionTokens[optionIndex] - await sendAnswer(token && token.length > 0 ? token : formatQuestionAnswer(question, [option])) + await sendAnswer(formatQuestionAnswerByIndexes(question, [optionIndex]))
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Team
Run ID: af72e16e-eb2e-446d-9be3-3bdee0f3d864
📒 Files selected for processing (10)
mobile/src/session/MobileNativeChatQuestion.test.tsxmobile/src/session/MobileNativeChatQuestion.tsxmobile/src/session/mobile-native-chat-question.tsmobile/src/session/mobile-structured-agent-prompts-grouped.test.tsmobile/src/session/mobile-structured-agent-prompts.tsmobile/src/session/mobile-structured-agent-session-launch.test.tsmobile/src/session/mobile-structured-agent-session-launch.tsmobile/src/session/mobile-structured-grouped-question.test.tsmobile/src/session/mobile-structured-grouped-question.tsmobile/src/session/use-mobile-structured-prompt-responses.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 1 remains after this review.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (since prior pullfrog review @ 138b298de)
- Position-based option selection —
MobileNativeChatQuestionnow tracks selected option indexes instead of labels, so a repeated label (Claude can ask the same question twice in one group) submits the correct row by position. NewformatQuestionAnswerByIndexes/formatQuestionAnswerWithOtherByIndexeshelpers mirror this, with tests covering duplicate-label and multi-select-plus-free-text submissions. - Hardened grouped-answer parsing —
answerFromResponsenow validates every comma-joined part'spromptKey(newly added to each token payload) andquestionId, checks option ids against the offered set, enforces single-vs-multi-select cardinality, and accepts option-plus-free-text answers it previously dropped.decodeGroupedFreeTextAnsweralso guardsdecodeURIComponentin a try/catch. - Option descriptions surfaced — flat and grouped question projections both pass per-option
optionDescriptionsthrough to the card. - Transient
createSupportretry — the launch path retriesagentSession.createSupportonselector_not_foundwith[50, 150, 300]msbackoff, matching the renderer'shostSupportsCreate, so a worktree that is only transiently unresolvable right after creation is no longer mistaken for a refusal. - Test pinning — grouped steps assert distinct card keys when the questions read identically, plus new launch-retry and grouped-answer validation tests.
I verified the retry loop terminates (bounded to four createSupport attempts), handles both the thrown-error and ok:false response shapes via hasRuntimeRpcErrorCode, and mirrors the renderer's delays and selector_not_found predicate exactly. The token payload change is client-internal: tokens are decoded locally and re-encoded through the shared encodeAgentSessionQuestionAnswers, so nothing the host reads changed.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
Review status: cleanReviewed and pushed two focused fixes to this branch. PR head is now Fixes added
Review and verification
Visual proofValidated on an iPhone 17 Pro simulator against an isolated Electron dev host: mobile created a native structured Claude session, rendered a two-step grouped Remaining QA gaps
No merge-blocking issues remain from this review. |
1a9fd82 to
4aaed2e
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (since prior pullfrog review @ df628c2)
- Compact grouped-answer encoding —
encodeAgentSessionQuestionAnswersnow escapes only%(replaceAll('%', '%25')) instead of fully percent-encoding the JSON, keeping Claude's maximum 4×4 choice group within the host's 512-char bound while the shareddecodeURIComponent-based decoder still reads both representations. - Host option-id bound widened to 1024 —
RespondParams.optionIduses a newmaxLengthargument onIdentifier, so a legacy fully-encoded 610-char payload from an already-deployed desktop renderer is still admitted. - Grouped-draft clear race fixed —
setCollectedclears the draft only when the completing submission'ssessionKeyandpromptKeystill match the collected draft, so an in-flight submission resolving after a session switch or revision bump no longer wipes the newer draft. - New coverage — encoder round-trip/legacy-compat/Unicode/size-bound tests, a schema-boundary test admitting the 610-char legacy optionId and rejecting 1025, and a react-test-renderer hook test covering both race scenarios.
I traced the wire-compat both directions and ran the touched suites (encoder, schema, hook, plus the mobile/host grouped-question tests) — all green. The %-only escape is a bijection (decodeURIComponent never throws, and literal %, %xx, and Unicode round-trip), so mixed-version clients stay compatible: a new client's compact payload fits an old host's 512 cap, and a new host's 1024 cap admits an old client's 610-char payload.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (since prior pullfrog review @ c5830e5)
- Tokenless single-select now submits by position —
MobileNativeChatQuestion.answerSinglewas narrowed to(optionIndex)and its fallback switched fromformatQuestionAnswer(question, [option])(labelindexOf) toformatQuestionAnswerByIndexes(question, [optionIndex]). Selecting the second of two identically-labelled options when only the first carries a token now submits the correct row instead of the first duplicate's token. This closes the open coderabbit thread from the prior round.
The new submits a tokenless duplicate-label row by position test is exact and correctly ablate-red: with the old code it would submit 'first-token' (via options.indexOf('Choice') → 0) rather than the expected 'Choice'. The token-preserving branch is unchanged, and the remaining fallback path is equivalent to the correct by-index formatting, so the fix is behavior-preserving elsewhere.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
Exact-head mobile QA evidence (
|
Review status: clean at
|
9d5afd9 to
b987f42
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (since prior pullfrog review @ 9d5afd9)
- Launch tests retargeted to the generalized create API —
mobile-structured-agent-session-launch.test.tsnow callscreateMobileStructuredAgentSession(client, worktreeId, agent), matching the rename from earlier commits. The rebase onto #18697 left these tests calling the pre-renamecreateMobileStructuredCodexSession, and mobile tsc excludes test files, so the stale reference wasn't caught until the tests ran. - Refusal-code correction — the "names the refusing agent" test now uses
method_not_found(a definitive refusal per the shared allowlist) instead ofagent_session_refused, whichisDefinitiveAgentSessionCreateRefusalcorrectly leavesunknown, so it never reached the failure copy the test asserted.
No production code changed in this delta; the isDefinitiveAgentSessionCreateRefusal classification on the launch path is the rebased merge of #18697 (already reviewed on main). The it.each tables already pin the definitive codes (structured_agent_session_unsupported, method_not_found) against the unknown codes (agent_session_operation_unknown, agent_session_ownership_unknown, runtime_error, future codes) exactly as the shared allowlist defines them. Verified the retargeted suite passes (19 tests).
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
Mobile emulator QA — PASS at
|
| Claude Chat tab | Grouped Q 1 of 2 | Q 2 of 2 multi-select | Group answered |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
Desktop Electron QA — BLOCKED by a pre-existing main defect, not by this PR
The Electron renderer never mounts in dev mode: #root stays empty and the window is blank.
ReferenceError: process is not defined
at src/shared/process-table-snapshot.ts:2:24
process-table-snapshot.ts evaluates process.platform at module top level, and it is now pulled into the renderer bundle via src/renderer/src/components/terminal-pane/agent-completion-poll-interval.ts (#18742). process is undefined in the browser, so the module throws during evaluation and React never mounts.
Not from this PR — proven, not inferred: both files are byte-identical to main here, neither is in this PR's diff, and I checked the same rig out at plain origin/main (12e05203a4c) and reproduced the identical crash, same file and line. Filing separately; flagging here because it blocks pnpm dev desktop QA generally.
Gaps
- Android not exercised (iOS Simulator only).
- Duplicate-label grouped options not produced by this live prompt (covered by unit test instead).
- Desktop-side rendering of the mobile-created structured tab could not be checked, for the reason above.
Mobile already spoke the structured agent-session protocol for Codex, and the host already had a Claude capability gate — mobile just never advertised it, so `projectAgentSessionTabsOut` stripped every Claude tab before it left the desktop. The structured lane in mobile/ turned out to be agent-agnostic already (shared reducer, message projection, option catalog, prompt tokens), so this opens the gate rather than building a second lane: - advertise `agent-session.structured.claude.v1` - resolve any structured provider in `resolveMobileNativeChat` via the shared `isAgentSessionHandleProvider`, instead of a `'codex'` literal - widen the `agent-session` route type off `'codex'` - route bare Claude launches through `agentSession.createSupport` like Codex, which still degrades to a terminal when the host refuses (remote, WSL, win32, managed-account mismatch, or structured chat switched off) Deduplicate the create envelope. Renderer and mobile each assembled the `agentSession.create` params by hand; the fingerprint has to be computed over the same fields the host recomputes, so both now build it in one shared `structuredAgentSessionCreateParams`. Mobile's Codex-only launcher becomes `createMobileStructuredAgentSession(client, worktreeId, agent)` and reuses the shared display-name map; two copies of a random-UUID fallback collapse into one. Answer grouped Claude questions. A Claude AskUserQuestion carrying more than one question — or one multi-select question — is emitted with the real content in `body.questions` and the flat `options` left EMPTY, so mobile rendered a card with nothing to tap and the turn stalled with no way out. Codex never emits this shape. The phone has room for one question at a time, so the group is answered as steps and submitted once, reusing the shared `encodeAgentSessionQuestionAnswers` / `isValidAgentSessionQuestionAnswers` rather than a second encoding. Prompt responses move into `useMobileStructuredPromptResponses` because grouped questions carry a multi-step draft the rest of the session does not touch, and the session hook was at the 300-line cap. Pin the mobile capability list against the host's parser bounds: it fails closed to NO capabilities when the array exceeds 64 entries, which would look exactly like an old client. Re-pin mobile-session-route-parity: the create-actions edit drops one runtime string literal and changes one nested function body. Ablated to confirm that file is the sole cause.
… in an effect The React Doctor gate flagged the session-change reset as a state adjustment after a prop change, which renders the stale draft for a frame. Store the session the answers were collected in alongside them and check it on read, so a session switch drops the draft during render with no effect at all.
…d identically Claude can ask the same text twice in one group (once per file, say). The view keys the question card by its projected content, so identical wording must still key apart or step 1's checkboxes would be submitted as step 2's answer.
The rebase onto #18697 brought its definitive-refusal tests in cleanly, but they call the pre-rename createMobileStructuredCodexSession, and mobile tsc excludes test files so nothing caught it. Retarget them and give the agent-copy test a code that is actually in the definitive allowlist - agent_session_refused now correctly stays unknown, so it never reached the failure copy it asserted.
Main moved its own runtime-string pin to 547; this branch drops the 'codex' literal from the create-actions gate. Ablated against main's pins to confirm that file is the sole cause before re-deriving.
b987f42 to
b51aaf1
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes (since prior pullfrog review @ b987f42)
- Rebase onto
main— all 8 prior commits were re-applied on currentmain(da48ad2), gainingfix(mobile):/test(mobile):subject prefixes with no production content change; no file overlap with the commits that landed onmainsince. - Route-parity test re-pin —
mobile-session-route-parity.test.tsre-pinned:mainhad moved its own runtime-string pin to 547, and this branch's'codex'literal drop (from the create-actions gate) brings the count to 546, withHEAD_NESTED_FUNCTION_SHA256andHEAD_RUNTIME_STRING_SHA256re-derived.
The sole new commit is test-only and correctly scoped: the parity test is a snapshot over extracted route strings, and the count/hash drift is fully explained by main's own bump plus the previously-reviewed agent === 'codex' → isAgentSessionHandleProvider(agent) gate change dropping the 'codex' literal. No production behavior changed, and the ablation described in the commit message is consistent with the three touched lines.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
Merges #18741, which opens the structured agent-session lane to Claude on mobile. Three conflicts, all in mobile/src/session: - mobile-native-chat-eligibility.ts: import block only. Kept the branch's MobileWebNativeChatAgentStatus/AgentWorkingMode pair and added main's isAgentSessionHandleProvider. Main's AgentStatusEntry import is dropped because the branch no longer references it. Main's generalized agent-session resolution auto-merged unchanged. - use-mobile-session-terminal-create-actions.ts: kept the branch's hosted page-adapter prelude, then main's generalized bare-launch gate whole (isAgentSessionHandleProvider + createMobileStructuredAgentSession). - mobile-session-route-parity.test.ts: took the branch's pins and recomputed from the test's own printed values. Ablated first: use-mobile-session-terminal-create-actions.ts is the only changed file in MOBILE_SESSION_ROUTE_SOURCE_FILES, matching main's own ablation. Runtime strings 476 -> 475 (the dropped 'codex' literal) and the nested function body re-froze; JSX, style, identity, navigation and capability digests were all unaffected. The hybrid page path carries no agent-session surface at all, for Codex or Claude, so nothing in src/shared/mobile-web or mobile/src/mobile-web needed a wire change. Left as an open parity item. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
Correction to my QA comment aboveTwo claims in that comment turned out to be wrong. Correcting the record rather than editing, so what I asserted and what proved true both stay visible. 1. The 2. "Desktop New Tab → Claude produced a TUI pane" was not a product defect — retract it. That was my test fixture. The QA repo was a bare That also explains the part I found puzzling at the time: the host answered Still accurate from that comment: the capability round trip, the host recording Genuinely open: Android was never exercised. |
* feat(mobile): structured native Claude chat Mobile already spoke the structured agent-session protocol for Codex, and the host already had a Claude capability gate — mobile just never advertised it, so `projectAgentSessionTabsOut` stripped every Claude tab before it left the desktop. The structured lane in mobile/ turned out to be agent-agnostic already (shared reducer, message projection, option catalog, prompt tokens), so this opens the gate rather than building a second lane: - advertise `agent-session.structured.claude.v1` - resolve any structured provider in `resolveMobileNativeChat` via the shared `isAgentSessionHandleProvider`, instead of a `'codex'` literal - widen the `agent-session` route type off `'codex'` - route bare Claude launches through `agentSession.createSupport` like Codex, which still degrades to a terminal when the host refuses (remote, WSL, win32, managed-account mismatch, or structured chat switched off) Deduplicate the create envelope. Renderer and mobile each assembled the `agentSession.create` params by hand; the fingerprint has to be computed over the same fields the host recomputes, so both now build it in one shared `structuredAgentSessionCreateParams`. Mobile's Codex-only launcher becomes `createMobileStructuredAgentSession(client, worktreeId, agent)` and reuses the shared display-name map; two copies of a random-UUID fallback collapse into one. Answer grouped Claude questions. A Claude AskUserQuestion carrying more than one question — or one multi-select question — is emitted with the real content in `body.questions` and the flat `options` left EMPTY, so mobile rendered a card with nothing to tap and the turn stalled with no way out. Codex never emits this shape. The phone has room for one question at a time, so the group is answered as steps and submitted once, reusing the shared `encodeAgentSessionQuestionAnswers` / `isValidAgentSessionQuestionAnswers` rather than a second encoding. Prompt responses move into `useMobileStructuredPromptResponses` because grouped questions carry a multi-step draft the rest of the session does not touch, and the session hook was at the 300-line cap. Pin the mobile capability list against the host's parser bounds: it fails closed to NO capabilities when the array exceeds 64 entries, which would look exactly like an old client. Re-pin mobile-session-route-parity: the create-actions edit drops one runtime string literal and changes one nested function body. Ablated to confirm that file is the sole cause. * fix(mobile): derive the grouped-question draft instead of clearing it in an effect The React Doctor gate flagged the session-change reset as a state adjustment after a prop change, which renders the stale draft for a frame. Store the session the answers were collected in alongside them and check it on read, so a session switch drops the draft during render with no effect at all. * test(mobile): pin that grouped steps key apart when the questions read identically Claude can ask the same text twice in one group (once per file, say). The view keys the question card by its projected content, so identical wording must still key apart or step 1's checkboxes would be submitted as step 2's answer. * fix(mobile): harden grouped Claude question answers * fix(mobile): retry transient structured support probes * fix(mobile): preserve grouped prompt response compatibility * fix(mobile): preserve tokenless duplicate choice identity * fix(mobile): point the launch tests at the generalized create API The rebase onto stablyai#18697 brought its definitive-refusal tests in cleanly, but they call the pre-rename createMobileStructuredCodexSession, and mobile tsc excludes test files so nothing caught it. Retarget them and give the agent-copy test a code that is actually in the definitive allowlist - agent_session_refused now correctly stays unknown, so it never reached the failure copy it asserted. * test(mobile): re-pin route parity after the rebase onto main Main moved its own runtime-string pin to 547; this branch drops the 'codex' literal from the create-actions gate. Ablated against main's pins to confirm that file is the sole cause before re-deriving. --------- Co-authored-by: Merge Sim <sim@local>













What
Orca mobile now supports the restructured Claude native chat, on the same code paths Codex already uses.
Why it didn't work
The host was already Claude-ready — it defines
agent-session.structured.claude.v1and filters Claude structured tabs out of any client that doesn't advertise it (session-tab-agent-status-projection.ts:34). Mobile simply wasn't on the other side of that gate.The structured lane in
mobile/turned out to be almost entirely agent-agnostic already: the journal reducer, message projection, options catalog and prompt tokens all work off shared types and key on tabtype === 'agent-session', never on agent identity. So this opens the gate rather than building a second lane.Gates opened
agent-session.structured.claude.v1.resolveMobileNativeChataccepts any structured provider via the sharedisAgentSessionHandleProvider, instead of atab.agent === 'codex'literal.agent-sessionroute type off'codex'.agentSession.createSupportlike Codex. It still degrades to a terminal whenever the host refuses — remote, WSL, win32, managed-account mismatch, or structured chat switched off.Deduplication
Renderer and mobile each assembled the
agentSession.createparams by hand. The fingerprint must be computed over the same fields the host recomputes, so both now build it in one sharedstructuredAgentSessionCreateParams(src/shared/structured-agent-session-create.ts). Mobile's Codex-only launcher becamecreateMobileStructuredAgentSession(client, worktreeId, agent)and reuses the existingTUI_AGENT_DISPLAY_NAMESmap; two copies of a random-UUID fallback collapsed into one.A real bug this fixes
Claude's
AskUserQuestionhas a grouped form — more than one question, or one multi-select question. For those the host deliberately emits the flatoptions: []with no free-text id and puts the real content inbody.questions(claude-structured-prompt-items.ts:114-129). Mobile read only the flat shape, so it would have rendered a card with nothing to tap and stalled the turn with no way out. Codex never emits this shape, which is why the Codex lane never hit it.Mobile now steps through the group one question at a time (the phone has room for one) and submits the whole group once, reusing the shared
encodeAgentSessionQuestionAnswers/isValidAgentSessionQuestionAnswersrather than inventing a second encoding.I ablated the fix to confirm the test goes red without it — it fails on the exact
"2 grouped questions from Claude"placeholder.Also
useMobileStructuredPromptResponses. Grouped questions carry a multi-step draft the rest of the session doesn't touch, and the session hook was at the 300-line cap — split along that seam rather than bumpingmax-lines.parseRuntimeClientCapabilitiesfails closed to no capabilities at all past 64 entries, which would look exactly like an old client; mobile advertises 11.Verification
tscclean; 4095 mobile tests pass (505 files, 8 new).no-cyclewarnings inmobile/src/transport/are pre-existing — proven by reverting my files and re-running.main(0a821e5bc8e) and re-ran everything; no file overlap with the five commits that landed since.mobile-session-route-parity.test.tsis re-pinned: the create-actions edit drops one runtime string literal and changes one nested function body. I ablated to confirm that file is the sole cause before re-pinning.Known gaps, deliberately left
experimentalStructuredNativeChatmust be enabled in desktop settings for mobile to see any structured tab. Pre-existing mobile-only gate that applies to Codex identically.reason: 'wsl'when the active Claude account is WSL-bound. Mobile degrades correctly, but the reason string is misleading. Not introduced here.agentSession.requestHandoffis absent from the mobile method allowlist, so mobile can't initiate native↔TUI handoff. Pre-existing for Codex too.