Skip to content

feat(native-chat): resume an Agent Session History row into a new structured chat - #19176

Merged
brennanb2025 merged 8 commits into
mainfrom
brennanb2025/resume-history-in-structured-chat
Sep 7, 2026
Merged

brennanb2025 merged 8 commits into
mainfrom
brennanb2025/resume-history-in-structured-chat

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 11 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1485 $\color{#cf222e}{\Huge{\mathbf{−}}}$​4 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1481
Prod 31 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1342 $\color{#cf222e}{\Huge{\mathbf{−}}}$​332 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1010

What users will see

Users can continue an existing Claude or Codex conversation from Agent Session History in Orca’s chat interface, including conversations originally started in the CLI.

  1. Open a supported history row’s menu and choose Resume in New Chat.
  2. A new chat tab opens with the conversation’s previous messages already visible.
  3. Send a message in that tab to continue the same Claude or Codex conversation, with its existing context.

For example, a user who started debugging with Codex in a terminal can find that conversation in history, open it in chat, read the earlier exchange, and ask a follow-up without explaining the problem again. The explicit Resume in New Chat action opens chat even when the user’s default for new agent tabs is terminal mode.

Availability and behavior

  • Available for eligible local Claude and Codex history rows when structured chat is supported. Claude must resume in its original workspace; Codex can resume in another workspace.
  • Moving a conversation into chat is one-way within Orca: Orca then reserves that conversation for structured chat and prevents terminal resume of it, even after the chat tab is closed. The expanded history-row detail explains this restriction.
  • If another chat already owns the conversation, users see “Another chat is already holding this conversation.”
  • If history is missing, invalid, empty, unreadable, or too large to import, the action fails without opening an empty chat. Users see “This conversation's history could not be loaded, so it cannot be resumed in chat.”
  • This adds no mobile entry point or support for remote/SSH/WSL history rows, other providers, or conversation forks.

Implementation and reliability

The existing create/import flow now resolves the selected conversation and account on the execution host, validates history before acquiring ownership, and imports it into the new chat. Committed retries preserve the same conversation and imported messages, including when the original history file disappears or the selected account changes. Existing single-writer checks prevent duplicate adoption; history decoding remains bounded to 16 MiB and runs once.

Ownership tracking covers Orca-managed sessions; a CLI resume started manually outside Orca remains outside that tracking.

Visual proof

Live Electron dev instance, isolated profile, structured native chat on. Both conversations were created by the CLI (codex exec / claude -p) with a planted token, then resumed from the history row. Continuity is proven by token recall, not by the model's self-report.

1. The action on a history row

menu-action.png

2. Codex — resumed, prior turns present, recalls CODEX-PROOF-7731

codex-resumed.png

3. Claude — resumed, prior turns present, recalls CLAUDE-PROOF-4482

claude-resumed.png

Both rows went from 2 to 4 messages and became structured-owned afterwards — the resume continued the same conversation rather than forking it, and the one-way ownership above is observed, not just reasoned.

Validation

  • Final same-model independent review and ending readiness review: no remaining meaningful in-scope findings.
  • All GitHub Actions checks passed at d5d70af577, including eight test shards, static analysis, typecheck, wire compatibility, and both packaging jobs.
  • Hidden Electron QA verified prior messages and continued context for both Claude and Codex, committed retry after source removal, duplicate ownership refusal, and invalid/oversized history refusal. The final error-message correction was verified on the final head.
  • Full functional evidence · Final error-message screenshot · Review status and testing limits

Live UI validation used an isolated macOS dev app. No live Windows/Linux/SSH/WSL/mobile or packaged-app smoke test is claimed.

Merge Sim added 2 commits September 6, 2026 18:40
…uctured chat

A Claude or Codex row in Agent Session History gains "Resume in New Chat": it
opens a new structured native-chat tab that continues that provider
conversation, with the prior turns already in the journal. Until now those rows
could only be resumed into a PTY terminal; the structured branch could reveal a
chat Orca already owned but could not adopt one it had never held.

Almost all of the machinery existed. Both lanes already resume from the record's
provider handle chain, the journal already has a transcript importer, and the
handle chain already models `adopted` as an origin. The gap was that a create
always minted an empty chain, so the adapters started a fresh conversation. This
seeds that chain.

The client names only the conversation. `agentSession.create` is reachable by
paired mobile clients, so the transcript path and the account home are derived
by the executing host and validated against the account homes it recognises —
a client-supplied path would choose which file the host imports and which
credential directory the provider child launches against.

Failure refuses rather than degrades. A transcript that cannot be found refuses
before anything is created; one that fails or decodes empty *after* the provider
has resumed fails the attach, tearing the child down and publishing no tab,
because an empty journal beside a context-carrying agent claims a continuity the
provider never gave.

Codex can resume into any workspace since it is handed the rollout path; Claude
resolves transcripts under a project key derived from the launch cwd, so it is
offered only for the workspace the conversation was recorded in.
…ches untouched

Three corrections from review of the first commit.

The adoption's account-home candidates now include the extra Codex homes session
discovery already scans. A row this host listed could otherwise refuse to
resume, which reads as the feature being broken rather than as a scope.

Ordinary launches call `createStructuredAgentSessionLaunchIntent` with two
arguments again. Passing the resume source unconditionally appended a trailing
`undefined` that four existing call-site assertions had to absorb; the churn was
the caller's fault, not the tests'.

The transactional adoption guard's comment claimed the self-exemption is what
lets a committed create replay. It is not: replay is settled earlier by the
operation ledger, and an adoption always arrives with a null expected fence, so
a request naming an existing session id is refused a few lines below either way.
The exemption is part of what "another record" means, and the comment now says
that instead.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Follow-up commit: three review corrections

Adopted-home discovery was narrower than session discovery. The candidate account homes now include the extra Codex homes aiVault.listSessions already scans (configuredAdditionalCodexHomePaths). Without that, a row this host listed could refuse to resume — which reads as the feature being broken rather than as a deliberate scope.

Ordinary launches call the intent builder with two arguments again. Passing the resume source unconditionally appended a trailing undefined that four existing call-site assertions had to absorb. That churn was the caller's fault, not the tests', so the fix went to the call site and those two test files are back to their original form.

A comment claimed a mechanism that isn't operating. The transactional adoption guard's self-exemption does not enable replay — replay is settled earlier by the operation ledger, and an adoption always arrives with a null expected fence, so a request naming an existing session id is refused a few lines below either way. The exemption is part of what "another record" means; the comment now says that rather than asserting a false rationale. The guard itself is unchanged and still load-bearing (ablation turns 3 reservation tests red).

Gates re-run after the change: tsc node + web clean, 2278 test files / 22898 tests green, all three lint gates clean.

@coderabbitai

coderabbitai Bot commented Sep 7, 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
📝 Walkthrough

Walkthrough

This change adds structured session resumption from existing Claude and Codex conversations. It carries resume identity through launch intents, fingerprints, validation, and launch state. The host resolves transcript locations, checks conversation ownership, seeds provider handle chains, and imports transcripts into the new journal. The AI Vault adds eligibility checks, resume actions, workspace handling, error messages, and UI controls.

Merge Risk: 🔵 Low · up to d8821

Claude sessions recorded in a directory whose name ends or begins with whitespace can be resumed from a different workspace, causing the resumed chat to miss its source conversation. Preserve exact POSIX path components before merge.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.87% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 62 functions across 38 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description explains the user-visible behavior, scope, reliability work, visual proof, and validation results. However, it omits the required Linked Issue section and Fixes # reference, and it d… Add the required ## Linked Issue section with a valid Fixes #<issue-number> reference. Complete or explicitly mark the template sections and checklist items, including AI Disclosure, Review, Agent skill upstream boundary, Notes, and Tes…
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly and concisely describes the main change: resuming an Agent Session History row in a new structured chat.
Full details: Description check

Explanation

The description explains the user-visible behavior, scope, reliability work, visual proof, and validation results. However, it omits the required Linked Issue section and Fixes # reference, and it does not explicitly complete the template checklist sections.

Resolution

Add the required ## Linked Issue section with a valid Fixes #&lt;issue-number&gt; reference. Complete or explicitly mark the template sections and checklist items, including AI Disclosure, Review, Agent skill upstream boundary, Notes, and Testing.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • 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: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: fc157234-4620-4f68-b324-a827b929bc5a

📥 Commits

Reviewing files that changed from the base of the PR and between 463cab2 and 4d5091c.

📒 Files selected for processing (32)
  • src/main/native-chat/agent-session-wire/structured-agent-session-adopted-import.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-attach-flow.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-attach.ts
  • src/main/native-chat/structured-agent-session-history-adoption.test.ts
  • src/main/native-chat/structured-agent-session-history-adoption.ts
  • src/main/runtime/agent-session-reservation-admission.test.ts
  • src/main/runtime/agent-session-reservation-admission.ts
  • src/main/runtime/orca-runtime-resolve-recovered-structured-tui-transcript.ts
  • src/main/runtime/rpc/methods/structured-agent-session-schemas.ts
  • src/main/runtime/rpc/methods/structured-agent-session.ts
  • src/renderer/src/components/right-sidebar/AiVaultPanel.tsx
  • src/renderer/src/components/right-sidebar/AiVaultSessionActionMenuItems.tsx
  • src/renderer/src/components/right-sidebar/AiVaultSessionDetails.tsx
  • src/renderer/src/components/right-sidebar/AiVaultSessionRow.tsx
  • src/renderer/src/components/right-sidebar/AiVaultSessionVirtualList.tsx
  • src/renderer/src/components/right-sidebar/SessionRowTrailingActions.tsx
  • src/renderer/src/components/right-sidebar/ai-vault-session-launch-actions.ts
  • src/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat.test.ts
  • src/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/lib/agent-launch-routing.ts
  • src/renderer/src/lib/launch-agent-structured-chat-guard.test.ts
  • src/renderer/src/lib/launch-structured-agent-session.ts
  • src/renderer/src/lib/structured-agent-session-launch-callers.ts
  • src/renderer/src/lib/structured-agent-session-launch-resume-identity.test.ts
  • src/renderer/src/lib/structured-agent-session-launch.test.ts
  • src/renderer/src/lib/structured-agent-session-launch.ts
  • src/shared/agent-session-provider-handle.test.ts
  • src/shared/protocol-version.ts
  • src/shared/structured-agent-session-create.test.ts
  • src/shared/structured-agent-session-create.ts
  • src/shared/structured-agent-session-mutation.ts

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

Comment on lines +55 to +56
normalizeRuntimePathForComparison(cwd.trim()) ===
normalizeRuntimePathForComparison(workspacePath.trim())

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

Do not trim path names before comparison.

normalizeRuntimePathForComparison preserves leading and trailing whitespace. Trimming both values can mark /repo/orca as eligible for /repo/orca, even though they are different POSIX directories. The structured Claude launch resolves the selected worktree path as cwd, so Claude can search the wrong transcript project. Pass the raw paths and change the whitespace test to expect false.

Proposed fix
-    normalizeRuntimePathForComparison(cwd.trim()) ===
-    normalizeRuntimePathForComparison(workspacePath.trim())
+    normalizeRuntimePathForComparison(cwd) ===
+    normalizeRuntimePathForComparison(workspacePath)
📝 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
normalizeRuntimePathForComparison(cwd.trim()) ===
normalizeRuntimePathForComparison(workspacePath.trim())
normalizeRuntimePathForComparison(cwd) ===
normalizeRuntimePathForComparison(workspacePath)

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

Important

The specifically-authored "conflict" / "transcript missing" toasts never render: the refusal code rides on error.code, but the matcher inspects error.message. See the inline comment — the failure surfacing also double-toasts and misclassifies a conflict as an unknown outcome.

Reviewed changes

  • Wire & capability — agentSession.create gains an optional resumeFrom: { providerSessionId }, gated by the new agent-session.structured.resume-history.v1 capability so older strict-schema hosts are negotiated against rather than probed.
  • Host adoption resolution — the executing host derives the account home and transcript path from the session id alone (never a client-supplied path), refusing a conversation it can't find or that another chat already holds.
  • Handle-chain seeding — an adopting create seeds an adopted head link so the adapter resumes instead of starting fresh; one-writer-per-conversation is enforced both pre-commit and inside the store transaction.
  • Journal import — the adopted transcript is imported post-acquisition and before publication, failing the attach (tearing the child down, publishing no tab) if it can't be read or decodes empty.
  • UI entry point — "Resume in New Chat" on history rows with per-agent eligibility (Claude bound to its recorded workspace, Codex anywhere), and launch coalescing keyed on resumeFrom so a resume is never folded into a pending blank launch.

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

* generic "could not prepare" the terminal resume reports. */
function notifyAiVaultSessionResumeInChatFailure(error: unknown): void {
const message = error instanceof Error ? error.message : String(error)
if (message.includes('agent_session_conflict')) {

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.

These two specific toasts can never fire over the real wire path. The host maps a conflict to a refusal with a generic message — precommitRefusal returns { code: 'agent_session_conflict', message: 'Orca cannot open a structured agent chat for this workspace.' } (see structured-agent-session-precommit-refusal.ts). launchStructuredAgentSession then re-throws that as StructuredAgentSessionCreateUnknownOutcomeError(message, code), so the code is on error.code and error.message is the generic sentence — message.includes('agent_session_conflict') is always false. Match on the code (e.g. error instanceof StructuredAgentSessionCreateUnknownOutcomeError && error.code === ...) or have the host surface the code in the message, instead of message.includes(...).

Technical details
# Resume refusal toasts are dead code; conflict is also misclassified

## Affected sites
- src/renderer/src/components/right-sidebar/ai-vault-session-launch-actions.ts:248 — matches on `error.message` only.
- src/main/runtime/rpc/methods/structured-agent-session-precommit-refusal.ts:46 — coded refusal keeps the generic `'Orca cannot open a structured agent chat for this workspace.'` message.
- src/renderer/src/lib/launch-structured-agent-session.ts:219 — non-definitive code rethrown as `StructuredAgentSessionCreateUnknownOutcomeError(message, code)`.

## Required outcome
- A refused resume shows the authored toast (`resumeInChatConflict` / `resumeInChatTranscriptMissing`), not a generic one.

## Observed consequences to fix alongside
1. `agent_session_conflict` / `agent_session_identity_required` are not in the definitive-refusal allowlist, so a conflict is treated as an unknown outcome: `launchAndReconcile` retries the create, refreshes tabs, and reads history before giving up, then sets `visibilityUnknown = true`. The launch state is left `unknown` (never cleaned up, since `handleResumeInNewChat` never calls `releaseCallerAfterUnknownOutcome`), which can linger in `getStructuredAgentLaunchStatus` for that (worktree, agent).
2. `trackLaunchFailureToast` in `structured-agent-session-launch.ts` also fires on the same rejection, so the user sees the generic `'Could not open {{agent}} chat'` toast *and* `notifyAiVaultSessionResumeInChatFailure`'s generic `'Could not resume this session in a new chat.'` toast — two generic toasts, no actionable one.

## Open questions for the human
- Should `agent_session_conflict` / `agent_session_identity_required` count as definitive create refusals (nothing was created, so a terminal fallback is not offered and no retry is attempted)? That would also drop the redundant retry/reconcile.

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

ℹ️ One edge case worth a look — the discovery widening is correct for local homes, but it skips the running-distro filter the scanner applies. Minor suggestion inline.

Reviewed changes

  • Widen adopted-home discovery — structuredAdoptionAccountHomeCandidates now appends configuredAdditionalCodexHomePaths() (a new export from cached-session-list.ts) so a Codex row listed from an extra home can also be resumed from it, not just listed.
  • Keep ordinary launches untouched — createStructuredAgentSessionLaunchIntent is now only called with a third argument when resumeFrom is present, reverting the undefined-arg test churn in launch-agent-structured-chat-guard.test.ts and structured-agent-session-launch.test.ts.
  • Docstring tightening — the assertAdoptedConversationUnowned comment now states the self-exemption is part of the guard's definition, not a replay mechanism.

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

...managedHomes,
// The same extra homes session discovery scans. Without these a row that this host listed
// could refuse to resume, which reads as the feature being broken rather than as a scope.
...configuredAdditionalCodexHomePaths(),

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.

The scanner filters these same homes through filterPathsToRunningWslDistrosAsync before scanning (cached-session-list.ts:91), but here they're appended raw. A stopped-WSL home in the set makes resolveSessionFilePath throw an unavailable refusal (gated WSL readdir), which aborts resolveStructuredAgentSessionAdoption's loop before it reaches getOrcaManagedCodexHomePath() / getSystemCodexHomePath() — a row whose conversation lives in one of those later local homes would refuse with "couldn't look" instead of resuming.

Technical details
# Additional Codex homes skip the running-WSL filter the scanner applies

## Affected sites
- src/main/runtime/orca-runtime-resolve-recovered-structured-tui-transcript.ts:172 — raw `configuredAdditionalCodexHomePaths()` appended to the candidate list.
- src/main/ai-vault/cached-session-list.ts:91 — the scanner filters the same set through `filterPathsToRunningWslDistrosAsync` first.
- src/main/native-chat/session-file-resolver.ts:308 — `findCodexRolloutInDirs` throws `unavailable` when a WSL root's readdir is gated/refused; each candidate is a separate `resolveSessionFilePath` call, so the throw propagates out of `resolveStructuredAgentSessionAdoption` rather than continuing to later candidates.

## Required outcome
- Resolving a row whose conversation lives under a local home must not be aborted by a stopped-WSL additional home; either filter the additional homes the same way (running-distro filter) or order them after the always-local `getOrcaManagedCodexHomePath()` / `getSystemCodexHomePath()`.

## Suggested approach
- Apply `filterPathsToRunningWslDistrosAsync` to `configuredAdditionalCodexHomePaths()` before appending (the resolver already runs in an async context), matching the scanner's view.

@coderabbitai

coderabbitai Bot commented Sep 7, 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.

ℹ️ No new issues in this merge reconciliation. Not an approval only because the must-address feedback from the first review remains open — the resumeInChat* toasts match error.message where the refusal code actually rides on error.code. That code lives in ai-vault-session-launch-actions.ts, which this commit does not touch.

Reviewed changes

  • Delegate launch routing to the shared module — resolveAgentLaunchRoute and structuredAgentLaunchSupported now go through prefersStructuredNativeChatByDefault / resolveStructuredNativeChatSupport from structured-native-chat-launch-route.ts, replacing the inline feasibility predicate.
  • Align resume eligibility with the fresh-chat route — structuredAgentLaunchSupported now inherits the executionHostId !== 'local' blocker, so "Resume in New Chat" is no longer offered for a remote target workspace where a structured chat could not run anyway. The resolveAgentLaunchRoute gate swap is behavior-preserving, since initialViewMode === 'chat' already implies the prefersStructuredNativeChatByDefault preconditions.

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

@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 in this delta — the create/retry path is now handled correctly. Not an approval because the must-address feedback from the initial review is still open: the resumeInChat* toasts match error.message where the refusal code rides on error.code (ai-vault-session-launch-actions.ts). This commit makes that path actually reachable, since forwarding resumeFrom means an adoption conflict or an unresolvable conversation now really does reach the client.

Reviewed changes

  • Skip adopted-transcript import on replay — importAdoptedTranscript moves to its own module and returns early when journal.cursor().sequence > 1, so a retried committed create keeps its journal-only messages and never re-reads a since-deleted transcript.
  • Close the provisional journal on import failure — performAttach wraps the import in try/catch and hands the journal to agentSessionJournalCloseRetries.closeOrRetain before rethrowing, closing a handle onAttachFailed cannot reach.
  • Forward resumeFrom into create resolution — prepareStructuredAgentSessionCreateForWorktree and the agentSession.create handler now pass resumeFrom to resolveStructuredAgentSessionCreateIntent, so an adopting create seeds the handle chain instead of minting a blank session.

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

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Live Electron QA evidence — History → Resume in New Chat

Tested SHA: 622cc2b165209604456761bf3f5315c2e94613c3 (worktree HEAD verified equal to the PR head; no source edits, no commits)

Isolated background dev instance (ORCA_BACKGROUND_LAUNCH=1, fresh profile /tmp/qa19176, private CDP 9347 / renderer 5187, --use-mock-keychain). Instance ownership proven four ways before any evidence was taken: app.getIdentity() (branch + worktree root), Electron argv (binary under this worktree, --user-data-dir=/tmp/qa19176), a single CDP page target on the private renderer origin, and the recorded process tree. Named playwright session throughout. The window was never shown, focused, or revealed; no computer-use or OS-level input.

All screenshots below are uncropped full-app captures of the real running app and were visually inspected.

QA note: Grok was requested but Grok Build reported a weekly limit, so this run used Claude as an explicitly authorized fallback. This is not Grok QA.

Fixtures

Disposable conversations created via CLI in a fixture folder workspace ~/orca-qa/pr19176-ws — deliberately not under /tmp, since on macOS /tmp→/private/tmp makes Claude record a realpath cwd that would confound the Claude cwd gate. No real user conversation was read, resumed, or modified.

Provider Conversation Recall token Model
Codex 01a079fd-7f63-7832-b310-9cfd93487a02 ORCA-QA-19176-CODEX-7F3A9C gpt-5.6-sol
Claude 74e2fd43-b95b-4ff9-b678-27ebf15cca98 ORCA-QA-19176-CLAUDE-B4E2D8 claude-opus-5

1. Codex — Resume in New Chat ✅

Real path: history row → More Session Actions → Resume in New Chat, then a real composer follow-up.

The menu item as offered on an unadopted row:
03-codex-menu-resume-in-new-chat.png

Old turns rendered and the token recalled by the continued provider session:
05-codex-token-recalled.png

Backing identity: record codex_e2800dd5_… → providerHandleChain[0] = { origin: "adopted", threadId: "01a079fd-…" }, host-resolved accountHome = {CODEX_HOME, ~/.codex}, lease claimStatus: live. Independently on disk, the same rollout file rollout-…-01a079fd-….jsonl now carries the new turn and its reply — the same conversation continued, not a copy.

2. Claude — Resume in New Chat ✅

Old turns rendered:
07-claude-resumed-old-turns.png

Token recalled after a real composer follow-up:
08-claude-token-recalled.png

Backing identity: record claude_fb4114a6_… → providerHandleChain[0] = { origin: "adopted", sessionId: "74e2fd43-…" }, host-resolved accountHome = {CLAUDE_CONFIG_DIR, ~/.claude}, lease claimStatus: live.

Token recall matters more than the rendered history here: the token exists only inside the pre-existing conversation, so recalling it proves the provider session actually resumed, not merely that a transcript was imported into the journal.

3. Repeat-open / duplicate ownership prevention ✅

After adoption, the same row's menu no longer offers the action (the row is already-structured and reopens its own chat). Same row, same menu, contrast with screenshot 1 above:
09-codex-adopted-no-duplicate-resume.png

4. Negative — provider asymmetry on workspace match ✅

Active workspace ~/orca-qa/pr19176-ws, rows from a different directory (/tmp/qa19176-ws), scope = All.

Claude row — action correctly absent (transcript is keyed by launch cwd, so resuming elsewhere would silently yield nothing):
10-claude-cross-workspace-hidden.png

Codex row — action correctly offered (handed the rollout plus a cwd, so any workspace works):
11-codex-cross-workspace-offered.png

5. Exclusions verified rather than expanded

  • Remote/SSH and WSL rows refuse before any structured route is considered (executionHostId !== LOCAL_EXECUTION_HOST_ID || isWslStoredAiVaultSessionFile(...) → reason: 'remote') — code-verified, no remote host in this rig.
  • No mobile surface added: the PR touches zero files under mobile/.
  • The renderer gates on readLocalRuntimeCapabilities() and dispatches the create to { kind: 'local' } — gate and RPC target agree.

6. PR unit tests ✅

vitest run --config config/vitest.config.ts over the 9 PR-touched suites: 9 files / 135 tests passed.


Investigated and dismissed (not defects of this PR)

  • location.workspaceKind: "git-worktree" recorded for a folder: workspace. A/B against a fresh, non-adopted structured chat in the same folder workspace shows the identical value on the ordinary created path — pre-existing, not introduced here.
  • A dynamic-import probe that returned an "unsupported workspace" refusal was discarded as a rig artifact (a second module instance with its own uninitialized capability cache), not a product signal.

Gaps — explicitly not verified

  • Live host-level conflict refusal (agent_session_conflict) was not reachable through the real surface, because the UI correctly removes the affordance first; guard is covered by unit tests only.
  • Outer-RPC retry replay — the "preserve history adoption through create and retries" behavior in this very commit — was not exercised live (no forced retry/crash injection); unit-test coverage only.
  • Dev build only (blind to packaged-build regressions); macOS only; no SSH/WSL execution.
  • agent_session_identity_required (transcript absent under every recognised account home) not exercised live.
  • Single run per scenario; no repeat-each stability sampling.

Evidence and full report retained at /tmp/orca-pr19176-qa/report.md.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/renderer/src/components/right-sidebar/ai-vault-session-resume-in-chat.ts (1)

55-56: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve exact POSIX path components in Claude workspace matching

AiVaultPanel can enable Claude resume when aiVaultSessionCwdMatchesWorkspace trims both paths, so distinct directories such as /repo and /repo compare equal. The structured launch uses the selected worktree, causing Claude to launch from /repo while looking for a transcript recorded under /repo , which can yield no conversation. Remove .trim() from these comparison arguments; normalizeRuntimePathForComparison already applies the required platform normalization while preserving POSIX path components.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: e74fbb52-4404-40d9-b7f8-12e2d1349d4f

📥 Commits

Reviewing files that changed from the base of the PR and between 622cc2b and d8821c1.

📒 Files selected for processing (11)
  • src/main/native-chat/agent-session-wire/structured-agent-session-adopted-import.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-adopted-import.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-attach.ts
  • src/main/native-chat/structured-agent-session-history-adoption.test.ts
  • src/main/native-chat/structured-agent-session-history-adoption.ts
  • src/main/runtime/orca-runtime-resolve-recovered-structured-tui-transcript.ts
  • src/main/runtime/rpc/methods/structured-agent-session-adoption-replay.test.ts
  • src/main/runtime/rpc/methods/structured-agent-session-create.ts
  • src/main/runtime/rpc/methods/structured-agent-session.test.ts
  • src/main/runtime/rpc/methods/structured-agent-session.ts
  • src/main/runtime/structured-agent-session-create-adoption.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/runtime/rpc/methods/structured-agent-session.test.ts

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

@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 in this delta — the replay-from-durable-identity path is sound and well-tested. Not an approval because the must-address feedback from the initial review is still open: the resumeInChat* toasts match error.message where the refusal code rides on error.code (ai-vault-session-launch-actions.ts), and this delta does not touch that file.

Reviewed changes

  • Replay committed adoption from durable identity — a retried committed adopting create now reconstructs its attach intent from the durable record and operation ledger (findCommittedStructuredAgentSessionAdoptionReplay / resolveCommittedStructuredAgentSessionAdoptionIntent) instead of re-reading a deleted transcript or re-resolving a drifted account selection.
  • Extract adoption resolution into a type-checked module — the account-home/transcript-path decision moves out of the @ts-nocheck runtime class into structured-agent-session-create-adoption.ts, keeping the client-supplied-path hazard behind type-checked code.
  • Make the adopted transcript path optional — adopt.transcriptPath is omitted on a durable replay; importAdoptedTranscript refuses a fresh journal that needs an import but got no path (agent_session_identity_required) rather than silently importing nothing.
  • Thread caller identity into create resolution — agentSession.create forwards callerKey so replay is scoped to the exact committed caller+operation namespace, covered by a new RPC-level replay test plus unit coverage for the replay finder.

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

@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 in this delta — the pre-acquisition validation and the renderer extraction are both clean, with one minor suggestion inline. Not an approval because the must-address feedback from the initial review is still open: notifyAiVaultSessionResumeInChatFailure matches error.message where the refusal code actually rides on error.code (ai-vault-session-launch-actions.ts), and this delta does not touch that matcher.

Reviewed changes

  • Validate history before claiming — prepareAdoptedTranscript now runs before reserveOwner/acquisition, refusing an unreadable, oversized, empty, or invalid source with agent_session_identity_required before a new record claims the provider conversation. importAdoptedTranscript consumes the pre-prepared items, so the transcript is read once (a source deleted after prepare still imports, pinned by a test) instead of re-read post-acquisition.
  • Extract renderer resume domains — resolveAiVaultSessionLaunchTarget/aiVaultResumeUnsupportedMessage, resolveAiVaultSessionResumeInChatForWorkspace, and AiVaultVirtualRow are pulled into their own modules (ai-vault-session-launch-target.ts, ai-vault-session-resume-in-chat-workspace.ts, AiVaultVirtualRow.tsx) with no behavior change.

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

return {
ok: false,
refusal: {
code: 'agent_session_identity_required',

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.

Every pre-acquisition failure — unreadable, oversized (the prepared.error "exceeds import bound" path), invalid JSON, and empty — collapses to the single wire code agent_session_identity_required; only error.message survives. That code means "no resolvable source", so a client matching on code would report an oversized but present transcript as "transcript not found". Consider a distinct refusal code for unreadable/oversized sources rather than folding them into the identity-missing case.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Final-SHA Electron QA — 44ea07b09f6522f763d06bc5f3ec22ab6564af12

Re-ran QA on the final SHA after fast-forwarding the QA worktree from brennanb2025/resume-history-in-structured-chat; local HEAD verified equal to the PR head. Isolated hidden dev instance (ORCA_BACKGROUND_LAUNCH=1, profile /tmp/qa19176, private CDP 9347 / renderer 5187, --use-mock-keychain), ownership proven by app.getIdentity(), Electron argv, a single CDP page target on the private renderer origin, and the recorded process tree. Never shown or focused; no computer-use. Screenshots are uncropped full-app captures, visually inspected.

Correction to my earlier comment: I described 622cc2b165 as covering retries. That commit fixed attach-level replay; the outer-RPC committed-create retry is the later change and was listed as an unverified gap on that run. It is verified here for the first time.

Rendered results — both providers continue ✅

Real path: history row → More Session Actions → Resume in New Chat → real composer follow-up. Old turns render before any input, and each session returns a token that exists only inside the pre-existing conversation, which is what proves the provider session resumed rather than a transcript merely being imported.

F02
F04
F06

Backing: records for both carry providerHandleChain[0].origin = "adopted" on the exact fixture ids, with host-resolved accountHome.

Host-layer RPC results (direct agentSession.create, labeled by layer)

Driven through window.api.runtime.call with the payload fingerprint recomputed in-page from a pure replica of the shared canonicalize + SHA-256. No renderer runtime-state module was imported, so the second-module-instance capability-cache artifact from the previous run cannot recur. The host accepting my fingerprint is itself proof the replica matches.

# Layer Scenario Result
T2 host RPC Committed create, then identical envelope replayed with the source file moved away ✅ replayed: true, same sessionId, same fence, chain not re-minted, accountHome preserved, record count unchanged, exactly one record holds the conversation
T3 host RPC Second adoption, new session id, same conversation ✅ refused agent_session_conflict — and refused before source resolution (source was absent, yet the code is conflict, not identity)
T4 host RPC Bogus providerSessionId ✅ refused agent_session_identity_required
T5 host RPC Oversized disposable transcript (19.38 MB vs the 16 MB bound) ✅ refused pre-acquire: Legacy transcript exceeds the 16777216-byte import bound

No record was created by any refusal (count held at 6 throughout). UI menu-hiding is not offered as evidence for any of these — each is a real host refusal.

Unit tests: 12 files / 179 tests passed (vitest run --config config/vitest.config.ts).


One defect found — refusal codes never reach the user 🐞

The two tailored messages this PR adds (resumeInChatConflict, resumeInChatTranscriptMissing) can never display. A user hitting a diagnosable condition gets the generic fallback instead.

Reproduced end-to-end through the real UI: with a history row already listed, its Codex rollout removed (the ordinary "user deleted the transcript after the list was cached" case), clicking Resume in New Chat shows:

F07

Could not resume this session in a new chat.
Could not open Codex chat — Orca could not open a structured Codex chat. See the logs for details.

Expected: "This conversation's transcript could not be found, so it cannot be resumed."

Two compounding causes, both confirmed live:

  1. agent_session_conflict and agent_session_identity_required are absent from DEFINITIVE_REFUSAL_CODES (src/shared/agent-session-definitive-refusal.ts, untouched by this PR, currently only structured_agent_session_unsupported). So the client raises StructuredAgentSessionCreateUnknownOutcomeError and routes into retrySameIntent rather than surfacing a refusal. Console from the live run:
    [native-chat] structured launch failed StructuredAgentSessionCreateUnknownOutcomeError: Orca cannot open a structured agent chat for this workspace. … at async retrySameIntent
    Both codes are refused before anything is created (T3/T4/T5 all left the record count unchanged), so by this file's own definition — "proves the host neither created a session nor will on a retry" — they qualify as definitive.

  2. Even on the refusal branch, notifyAiVaultSessionResumeInChatFailure matches error.message.includes('agent_session_conflict' | 'agent_session_identity_required'), but the host's message never contains the code — it is either the generic "Orca cannot open a structured agent chat for this workspace." (T3, T4) or a specific unrelated string (T5's byte-bound message). The code travels on error.code, which StructuredAgentSessionCreateRefusalError already carries.

Suggested minimal fix: add both codes to the definitive set, and match on the error's code rather than its message. Safety is unaffected — the host refuses correctly in every case; this is a diagnosability defect. The green suite does not catch it because no test exercises the renderer's refusal→toast mapping against a real host refusal payload.

Gaps — explicitly not verified

  • Account-selection change not exercised. It was dropped from the final priority list, and I would not touch real CODEX_HOME/CLAUDE_CONFIG_DIR credentials; a disposable-account-home variant was not set up. Unverified, not passing.
  • Dev build only (blind to packaged-build regressions); macOS only; no SSH/WSL execution — those refusals remain code-verified.
  • No crash injection; the retry was exercised via a repeated committed envelope, which is the intended replay path.
  • agent_session_ownership_unknown (lease present but not admitting a writer) not reached.
  • Single run per scenario.

Full report: /tmp/orca-pr19176-qa/final-report.md.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Toast finding resolved — verified at d5d70af5770de59a851fdfe798308d351d93b046

Follow-up to my evidence at #19176 (comment), where the tailored refusal toasts never displayed and users got the generic "Could not resume this session in a new chat."

Re-ran the exact same repro on the new SHA: isolated hidden dev instance rebuilt from this worktree (ORCA_BACKGROUND_LAUNCH=1, profile /tmp/qa19176, CDP 9347 / renderer 5187, --use-mock-keychain), identity proven via app.getIdentity(), Electron argv under this worktree's out/electron-dev, and a single CDP page target on the private renderer origin. Same disposable Codex fixture (01a07a2d-ef99…), listed in Agent Session History, its rollout moved aside, then Resume in New Chat clicked through the real row menu.

Observed now:

This conversation's history could not be loaded, so it cannot be resumed in chat.

T01

Previously this same trigger produced the generic fallback. Finding resolved.

Correction to my earlier comment

I suggested adding agent_session_conflict / agent_session_identity_required to DEFINITIVE_REFUSAL_CODES. That is not part of the fix and should not be done — it changes global fallback and single-writer semantics well beyond this PR. My report presented it as one of two required causes; that was wrong. Matching the typed code via the existing hasRuntimeRpcErrorCode is sufficient on its own, because it checks .code before any message text and both StructuredAgentSessionCreateRefusalError and StructuredAgentSessionCreateUnknownOutcomeError inherit that field — so the refusal classifies correctly even when it arrives as an unknown-outcome error. Only the message-matching half of my finding was real.

Scope of this check

Backend and continuity were unchanged by this delta (two renderer files: the code-matched toast plus the broader unavailable-history wording), so per instruction I did not re-run the full 179-test suite or the backend scenarios — those results stand from the prior comment. The conflict branch was not re-triggered live: the UI hides the affordance on already-adopted rows, so it is only reachable in a race. It uses the identical hasRuntimeRpcErrorCode call, so it is fixed by the same change, but I am labelling it verified-by-shared-mechanism rather than observed.

One pre-existing observation, unchanged and not a regression: a second launcher-level toast ("Could not open Codex chat… See the logs for details.") still appears alongside the specific one.

Prior validation results and gaps from the linked comment are unchanged and still stand.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review complete at d5d70af5770de59a851fdfe798308d351d93b046. Conflicts with main were resolved first and pushed. The final same-model/effort independent review is clean, and the starting/ending readiness reviews have no remaining meaningful in-scope finding.

Fixes pushed during review:

  • Preserve history-resume intent through create preparation and retain imported turns on committed retries.
  • Replay committed creates using durable identity, even when the original source disappears or account selection changes.
  • Validate bounded history before reserving/acquiring ownership; reuse one decode and close provisional journals on rejection.
  • Resolve CI file-size violations with behavior-preserving domain extractions and close the RPC test session during teardown.
  • Match typed refusal codes through the existing classifier and show accurate history-unavailable copy. Global retry/fallback policy remains unchanged.

Performance and precedent review found no remaining issue: the fix extends existing admission/import/replay paths, retains the 16 MiB source bound, and introduces no polling, subprocess fan-out, or retained transcript payload. No unrelated product expansion was added.

Validation:

  • All GitHub Actions checks passed on the final head, including all eight test shards, static analysis, typecheck, wire compatibility, both packaging jobs, and verify. CI run. External Pullfrog is still pending at posting time.
  • Focused QA suite: 179 tests across 12 files passed before the final two-file toast change; 54 relevant tests plus renderer typecheck, lint, and formatting passed for that final change.
  • Hidden Electron/CDP QA verified real Claude and Codex history rendering and continued conversation recall, durable committed replay after source removal, conflict/missing-identity refusal, and oversized-source rejection before acquisition. Functional screenshots and evidence.
  • Final-head Electron QA reproduced and verified the corrected refusal toast, with no record created. Final screenshot and resolution.

Grok was unavailable because its weekly quota was exhausted; orchestrated Claude Opus performed Electron QA instead. Live QA used an isolated macOS dev app and disposable fixtures; no live Windows/Linux/SSH/WSL/mobile or packaged-app smoke test is claimed. Account-selection drift is covered by deterministic runtime tests, and the conflict toast by the shared classifier rather than a live UI race. Generic disk-full/crash recovery remains outside this focused fix.

The branch is conflict-free and pushed, the working tree is clean, and supervised workers are settled. PR left open; not merged.

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

Important

The matcher fix is correct and finally makes the authored toasts reachable, but it leaves the two consequences from the initial review live — and the double toast is now visible for the first time.

Reviewed changes

  • Match resume refusals by wire code — notifyAiVaultSessionResumeInChatFailure now classifies failures via hasRuntimeRpcErrorCode instead of error.message.includes(...), so agent_session_conflict / agent_session_identity_required actually reach their authored toasts.
  • Widen the transcript-missing copy — resumeInChatTranscriptMissing now reads "history could not be loaded", covering the oversized / corrupt / unreadable sources that also collapse to agent_session_identity_required.

⚠️ Conflict and identity refusals still aren't definitive

The two refusal codes still prove nothing was created, yet they are absent from the definitive-refusal allowlist, so a refused resume retries the create and then leaves the launch state lingering as unknown.

Technical details
# Make conflict/identity refusals definitive so a refused resume stops retrying and lingering

## Affected sites
- src/shared/agent-session-definitive-refusal.ts:18 — `DEFINITIVE_REFUSAL_CODES` still holds only `structured_agent_session_unsupported`.
- src/renderer/src/lib/launch-structured-agent-session.ts:219 — a `!result.ok` refusal with `agent_session_conflict` / `agent_session_identity_required` throws `StructuredAgentSessionCreateUnknownOutcomeError` (non-definitive).
- src/renderer/src/lib/structured-agent-session-launch-recovery.ts:112 — `launchAndReconcile` then retries (`retrySameIntent`), re-running the create and re-reading history before giving up.
- src/renderer/src/lib/structured-agent-session-launch.ts:160 — the state is left `outcome: 'unknown'` with `visibilityUnknown = true` and never cleaned up (`handleResumeInNewChat` never calls `releaseCallerAfterUnknownOutcome`), so `getStructuredAgentLaunchStatus` reports `unknown` for that (worktree, agent) pair indefinitely.

## Required outcome
- A `agent_session_conflict` / `agent_session_identity_required` refusal flows as a definitive refusal: no create retry, no lingering `unknown` launch state.

## Suggested approach
- Add `agent_session_conflict` and `agent_session_identity_required` to `DEFINITIVE_REFUSAL_CODES`. Both mean the host refused before creating anything; a retry cannot change that outcome.

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

* cannot find under any account home it recognises. Both are actionable, and neither is the
* generic "could not prepare" the terminal resume reports. */
function notifyAiVaultSessionResumeInChatFailure(error: unknown): void {
if (hasRuntimeRpcErrorCode(error, 'agent_session_conflict')) {

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.

The specific toast now renders, but trackLaunchFailureToast in structured-agent-session-launch.ts still fires the generic "Could not open {{agent}} chat" toast on the same rejection, so a conflict or missing-transcript refusal now stacks two toasts and the second is misleading.

Technical details
# A refused resume shows two toasts

## Affected sites
- src/renderer/src/components/right-sidebar/ai-vault-session-launch-actions.ts:245 — the specific toast now fires.
- src/renderer/src/lib/structured-agent-session-launch.ts:177 — `trackLaunchFailureToast` fires the generic toast on any non-`StructuredAgentSessionCreateRefusalError` rejection (and on a refusal whose fallback was never claimed).

## Required outcome
- A refused resume shows exactly one, actionable toast.

## Suggested approach
- Have the resume-in-chat path signal that it handles its own failure (e.g. claim a no-op fallback via `claimDefinitiveRefusalFallback`, or gate `trackLaunchFailureToast` on no caller having registered a notifier), so the generic toast is suppressed when the specific one fired.

@brennanb2025
brennanb2025 merged commit 1ae7aa8 into main Sep 7, 2026
28 checks passed
nwparker added a commit that referenced this pull request Sep 7, 2026
…tured gate (#19246)

Semantic conflict between two green PRs. #19176 added this replay test while
`agentSession.*` still admitted a `runtime` client on its negotiated capability
alone; #18700 then made `experimentalStructuredNativeChat` one rule for every
caller. Neither branch saw the other, and main runs no post-merge test gate, so
`agentSession.create` started refusing at the envelope level and the test's
`ok: true` expectation broke.

#18700's rule is the intended behaviour and `create` starts work, so it belongs
behind the gate. The fixture is what is stale: it builds a real
`OrcaRuntimeService` whose client settings are unset. Enable the setting the way
#18700 already did for the sibling pre-commit fixture. The assertions about
durable-identity replay are untouched and now actually run.
ivanvykopal pushed a commit to ivanvykopal/orca that referenced this pull request Sep 7, 2026
…uctured chat (stablyai#19176)

* feat(native-chat): resume an Agent Session History row into a new structured chat

A Claude or Codex row in Agent Session History gains "Resume in New Chat": it
opens a new structured native-chat tab that continues that provider
conversation, with the prior turns already in the journal. Until now those rows
could only be resumed into a PTY terminal; the structured branch could reveal a
chat Orca already owned but could not adopt one it had never held.

Almost all of the machinery existed. Both lanes already resume from the record's
provider handle chain, the journal already has a transcript importer, and the
handle chain already models `adopted` as an origin. The gap was that a create
always minted an empty chain, so the adapters started a fresh conversation. This
seeds that chain.

The client names only the conversation. `agentSession.create` is reachable by
paired mobile clients, so the transcript path and the account home are derived
by the executing host and validated against the account homes it recognises —
a client-supplied path would choose which file the host imports and which
credential directory the provider child launches against.

Failure refuses rather than degrades. A transcript that cannot be found refuses
before anything is created; one that fails or decodes empty *after* the provider
has resumed fails the attach, tearing the child down and publishing no tab,
because an empty journal beside a context-carrying agent claims a continuity the
provider never gave.

Codex can resume into any workspace since it is handed the rollout path; Claude
resolves transcripts under a project key derived from the launch cwd, so it is
offered only for the workspace the conversation was recorded in.

* fix(native-chat): widen adopted-home discovery and keep ordinary launches untouched

Three corrections from review of the first commit.

The adoption's account-home candidates now include the extra Codex homes session
discovery already scans. A row this host listed could otherwise refuse to
resume, which reads as the feature being broken rather than as a scope.

Ordinary launches call `createStructuredAgentSessionLaunchIntent` with two
arguments again. Passing the resume source unconditionally appended a trailing
`undefined` that four existing call-site assertions had to absorb; the churn was
the caller's fault, not the tests'.

The transactional adoption guard's comment claimed the self-exemption is what
lets a committed create replay. It is not: replay is settled earlier by the
operation ledger, and an adoption always arrives with a null expected fence, so
a request naming an existing session id is refused a few lines below either way.
The exemption is part of what "another record" means, and the comment now says
that instead.

* fix: preserve history adoption through create and retries

* fix: replay committed history adoption from durable identity

* fix: validate history before claiming adopted sessions

* fix: extract AI vault resume domains

* fix: recognize typed history resume refusals

---------

Co-authored-by: Merge Sim <sim@local>
ivanvykopal pushed a commit to ivanvykopal/orca that referenced this pull request Sep 7, 2026
…tured gate (stablyai#19246)

Semantic conflict between two green PRs. stablyai#19176 added this replay test while
`agentSession.*` still admitted a `runtime` client on its negotiated capability
alone; stablyai#18700 then made `experimentalStructuredNativeChat` one rule for every
caller. Neither branch saw the other, and main runs no post-merge test gate, so
`agentSession.create` started refusing at the envelope level and the test's
`ok: true` expectation broke.

stablyai#18700's rule is the intended behaviour and `create` starts work, so it belongs
behind the gate. The fixture is what is stale: it builds a real
`OrcaRuntimeService` whose client settings are unset. Enable the setting the way
stablyai#18700 already did for the sibling pre-commit fixture. The assertions about
durable-identity replay are untouched and now actually run.
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