Skip to content

feat(native-chat): say in the chat why Orca stopped a reply, and offer Continue - #25675

Merged
brennanb2025 merged 55 commits into
mainfrom
brennanb2025/rsc-resume-in-chat
Oct 7, 2026
Merged

brennanb2025 merged 55 commits into
mainfrom
brennanb2025/rsc-resume-in-chat

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 24 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1858 $\color{#cf222e}{\Huge{\mathbf{−}}}$​20 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1838
Prod 56 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1437 $\color{#cf222e}{\Huge{\mathbf{−}}}$​246 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1191

ELI5

When Orca quits, installs an update, or crashes while an agent is in the middle of a reply, that reply gets cut off. Today the chat shows a generic red line ("Claude stopped while this response was in progress. You can continue in this conversation.") that doesn't say why it stopped or on which machine, and there is no button to pick up where it left off. With this PR the chat says what happened ("Orca on QA server was closed while this response was in progress.") and offers a Continue button that asks the agent to check its last step and carry on. Chats on this computer and chats on a paired Orca server work the same way.

What Changed

Before (what a user sees):

  • A reply cut off by an Orca quit, update or crash shows one red row with generic words. It never names the cause or the machine.
  • The only one-click way to continue is the local-only "Resume interrupted chats?" dialog, which appears only after a graceful quit or update of this computer. After a crash, or when a paired server restarted, the user has to type a message themselves.

After:

  • When the host knows Orca itself stopped, the row is one muted sentence (not red, because the agent did nothing wrong):
    • "Orca on {machine} restarted for an update while this response was in progress."
    • "Orca on {machine} was closed while this response was in progress."
    • "Orca on {machine} stopped unexpectedly while this response was in progress."
    • {machine} is the name Orca already shows for that host: the user's rename, the paired server's name, or "Local Mac" / "Local Windows" / "Local Linux". A paired server this client has no name for gets today's generic words, never a raw id.
  • Which sentence:
    • "restarted for an update": the desktop app quitting to install an update.
    • "was closed": any other graceful exit — quitting the app, the Settings / compatibility / GPU-fallback relaunches, a headless paired server stopped by its service manager (SIGTERM/SIGINT), and Windows logoff or shutdown. A headless server can't tell an update from a stop, so it always says "was closed".
    • "stopped unexpectedly": only when that Orca recorded that it started and never recorded an end (a crash or a kill).
  • A Continue button sits just above the message box while the chat's latest reply is one of these cuts. Its tooltip and accessible description: "Continue, and the agent first checks whether its last step finished."
    • It sends Orca's existing restart continuation (never the user's own prompt), and the chat shows the existing "Orca asked this agent to continue after a restart. Your own prompt was not re-sent." note.
    • It goes away once the chat moves on: Continue was used (here or from another client), the user sent their own message, or the restart dialog resumed the chat. A steer or a chat command such as /context after the cut does not remove it.
    • It is not shown while the launch is resuming this computer's chats automatically ("resume automatically" on), so it never flashes before the automatic continuation.
    • If the request is refused or fails, the existing composer line says "Couldn't continue this chat. Try again, or send a message." It clears once the chat moves on.
  • Today's generic row stays, unchanged, when the host can't tell what happened: the agent exited on its own while Orca ran, an idle eviction, an agent an older Orca build started, a terminal's agent, or a missing/unreadable record.
  • Against an older Orca server, the row stays today's generic one with no button, and the user continues by sending a message as today.
  • Older clients (desktop or phone) keep showing exactly today's single red row.

Mechanism:

  1. The cause is recorded on the host, in the chat's history, so every client reads it the same way it reads the rest of the chat, including after a reconnect. It is the host's existing stale-session: row about the cut turn, plus:

    • an optional orcaStop: { cause } field, and
    • presentation: 'orca-stop'.

    The stored row keeps today's words, failure fact and red tone, so older clients still draw one visible row. New clients see the presentation and render it muted, keep it on screen under a collapsed turn, and don't count it as the turn's failure.

    • Quit / update. After a quitting Orca proves a session's agent exited and the exit handler has settled the turn, the host writes the row if that root turn reads "cut off with nobody asking". It never writes one for a finished turn, a person's Stop, or an idle eviction. The row id is keyed by the stopped child, so a retry adds nothing.

    • Crash, or a quit that did not get that far. The existing reopen row gets the cause from two facts:

      • Which Orca held the agent: each recorded agent owner is stamped with the id of the Orca process that recorded it.
      • How that Orca ended: each Orca process writes a small record file when its chat store opens ("started"), and records its end ("quit" / "update") with one synchronous, durable write. Every graceful exit calls it: the quit teardown, the update watchdog, the headless server's stop, Windows session-end, and a process exit hook for any other clean exit.

      A death of an agent held by an earlier Orca reads "crash" only if that Orca has a start record and no end. If the record is missing, pruned (16 kept), unreadable, or the end write failed (the file is then removed), the chat keeps the generic words.

  2. Continue is one host-side operation: new RPC agentSession.continueInterrupted { sessionId, turnItemId }, advertised by the runtime capability agent-session.continue-interrupted.v1. It reuses the existing restart continuation end to end: same message, same Orca caller (it leaves a queue pause alone), same delivery verdicts and notes.

    • Inside the session lock it re-checks that the chat's latest root turn is still this cut, with no new request after it and no send in flight. A second click, another client, or a retry after a lost answer therefore sends nothing.
    • It answers once Orca has accepted the message. The agent's start finishes in the background, and a start that fails becomes a note in the chat. A slow agent start on a paired server therefore can't surface as a false "Couldn't continue".
    • A restart offer, if one exists, only picks the continuation's wording, and is retired by the send.
  3. Client. The button is routed to the chat's owning target (local or paired) and shown only when the host advertises the capability.

Why

  • In the chat's history, not on the restart offer: the offer file is local-only, exists only after a graceful quit, and is deleted as soon as the user sends anything. The explanation has to outlive it and reach paired clients.
  • Keep today's row and add optional fields: clients and servers update independently. A new row kind would be invisible to older clients, and a row they don't recognize as explaining the cut would make them draw a second row.
  • A start and an end record per Orca process: the existing death evidence can't tell "Orca crashed" from "the agent died". The alternative of guessing "crash" whenever no quit was recorded mislabels every exit path that skips the normal quit, such as a headless server stop or an in-app relaunch. "Crash" is now said only when Orca knows it started and never ended. Anything less certain stays generic.
  • One continuation path for Continue: a plain client send of the same text would lift queue pauses that Orca's own continuation preserves, lose the attribution note, and could send twice on an ambiguous answer.

Differences from the common pattern (labelled):

  • The host records the interruption in the chat's history and the client renders it as a row, with a short reason: matches the common pattern's mechanism (host-owned settlement leaving a durable interrupted row that says the host restarted). Intended.
  • Splitting the reason into update / closed / crash, and naming the machine: goes further than the common pattern, which says only that the host restarted or the connection was lost. Intended (product decision): with paired servers the user needs to know which machine and whether it was a crash.
  • The Continue button: the common pattern leaves continuing to the user's next message; its only resume button is for held queued messages. Intended (product decision): after a crash or a paired-server restart the user otherwise has no one-click way to continue. No toast is added; the chat itself shows what Continue did.
  • Per-process start/end record files outside the chat database: intended. They survive a stalled database during quit, and nothing already stored can tell an unfinished quit from a crash.
  • Wording vs. the approved mock: one neutral sentence without a "Stopped:" lead, "was closed" rather than "was restarted", muted rather than a warning icon, and the explanation in the button's tooltip. Agreed with the owner of fix(native-chat): a chat cut off by a crash, quit or restart reads Interrupted until its state changes #25670, which owns the generic row's wording. Intended.

Not in this PR (follow-ups / known limits):

  • Generic row wording and sidebar status belong to fix(native-chat): a chat cut off by a crash, quit or restart reads Interrupted until its state changes #25670. The sidebar and tab still label a cut chat "Failed", as on main. The two PRs compose: fix(native-chat): a chat cut off by a crash, quit or restart reads Interrupted until its state changes #25670 re-presents an orca-stop row with its neutral words but keeps the presentation and cause, so this PR's client still names the cause. Whichever lands second adds orca-stop to the never-fold list.
  • Mobile shows the host's stored text (today's generic sentence) and no Continue yet.
  • This computer's own restart still opens the "Resume interrupted chats?" dialog, as today, beside the in-chat Continue. Using either one settles the chat in both places (the status-bar entry clears after Continue).
  • Crash restart offers in the resume dialog, and "unconfirmed" turns, keep today's behavior.
  • Residual limits:
    • an exit before any chat store opened records nothing (nothing to explain);
    • two Orca processes forced onto one profile (single-instance lock bypassed) would read the still-running one as started-never-ended;
    • after a crash, the continuation can't mention lost background work, because a crash leaves no restart offer.

Linked Issue

N/A (maintainer change)

Visual Proof

Live QA: hidden Electron on a separate Mac, isolated profiles, real Claude, a client and a paired server on the same box. Before: today's generic red row (main), not re-captured.

Case Screenshot
Local quit: muted row + Continue 05-s1-closed-row-continue.jpg
After Continue: one continuation, Orca's note, agent carries on from 141 06-s1-after-continue.jpg
Local crash (SIGKILL): "stopped unexpectedly" 12-s2-crash-row-continue.jpg
Settings relaunch: "was closed", not a crash 16-s3-closed-row-continue.jpg
Paired server stopped (SIGTERM): "Orca on QA server was closed" 22-s4-after-server-restart.jpg
Paired Continue: agent continues on the server 23-s4-after-continue.jpg
Paired server killed (SIGKILL): "stopped unexpectedly" 26-s5-crash-row.jpg
Local restart: the existing resume dialog still opens too 04-s1-resume-dialog.jpg

Testing

  • Live QA (macOS, real Claude, local + paired server):

    • passed: local quit, local crash, Settings relaunch, paired-server SIGTERM and SIGKILL, user Stop / quit after a finished reply / own message instead of Continue;
    • re-run on the final head: quit, relaunch and crash, plus "resume automatically" (no Continue flash in 47 samples at 100 ms; exactly one continuation);
    • blocked: the update-install sentence (the install path refuses while another Orca runs) and an older paired server. Both are covered by unit and cross-version tests.
  • Unit tests, each ablated (fails with its fix reverted):

    • the quit/update row and its scope;
    • crash only for started-never-ended;
    • every graceful exit path records its end;
    • missing / pruned / unreadable / failed-write records stay generic;
    • Continue sends once (double click, two clients, lost answer, the user's own message) and answers at acceptance;
    • steer and /context keep Continue;
    • no Continue flash during auto-resume;
    • the composer line clears;
    • the rendered tone for old, fix(native-chat): a chat cut off by a crash, quit or restart reads Interrupted until its state changes #25670 and new rows;
    • chat start with a runtime-stamped owner.
  • Targeted vitest across the touched areas: 101 files / 1,334 tests at the last full targeted run. The cross-version agentSession.* wire suite includes the new method. Localization verify scripts, RPC catalog check, changed-code quality gate, React Doctor and oxlint pass.

  • Typecheck: tc:node, tc:web and tc:cli pass. CI is fully green at e7ec734118b, including the cross-version wire suite with the new method.

  • I manually tested these changes locally

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

Review

Agent skill upstream boundary

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

Notes

  • Remote/SSH: the row reaches paired clients through the chat's own history. Continue routes to the chat's owning runtime target; SSH or unresolved owners have no runtime target, so it never falls back to local. Folder workspaces are unaffected.
  • Backwards compatibility: optional fields only; rollback reads the records; the new RPC is capability-gated and an older host answers method_not_found (asserted by the cross-version suite).
  • Windows: logoff/shutdown records the end through session-end; the record write reuses the existing durable write with its locked-file retry.

Checklist

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

…r Continue

When the Orca that runs a structured chat (this computer or a paired server) quits, updates or
crashes mid-reply, the chat's stopped row now names the cause and the machine, and a Continue
button sends the existing restart continuation for that cut turn, with or without a restart offer.

- Host: a quit/update writes one turn-scoped row for the turn its stop cut, in today's words, with
  an optional `orcaStop` cause on the providerExited fact; restart adjudication stamps how the
  previous runtime ended on the deaths it proves (crash, or the quit/update it began), so the
  crash row names it too. Older clients keep their single row.
- Host: agentSession.continueInterrupted, capability-gated, rechecks under the session lock that
  the chat still sits on that cut, so a second click or a retry sends nothing.
- Client: the row's copy names the cause and machine; Continue sits above the composer.
…the agent; neutral row, Continue explains itself

- The cause now rides on the host's row itself (`orcaStop` on the status row, beside today's
  words), the same row family and id scheme as the reopen's death row.
- Each recorded owner is stamped with the Orca runtime that holds it; a death proven later (at
  restart, or when recovery stops a survivor) names how that runtime ended: the quit or update it
  began, else a crash. Owners an older build recorded, a terminal's claim, an agent that died while
  its Orca ran, and unreadable quit records all keep the generic words.
- The quitting runtime's word is written first in teardown, before the recovery wait, through a
  bounded asynchronous writer apart from the chat database.
- Row copy: one neutral sentence naming the machine and cause; it drops "You can continue in this
  conversation." while Continue is offered, and Continue's tooltip says what it does.
…me-in-chat

# Conflicts:
#	src/main/native-chat/agent-session-wire/structured-agent-session-host-lifetime.ts
#	src/main/native-chat/agent-session-wire/structured-agent-session-host-stop-event.ts
#	src/main/runtime/structured-agent-session-runtime-teardown.ts
…passes

The cut turn's outcome takes the journal's outcome type, and the stand-in close reads the
provider sink through a checked lookup instead of an index that may be absent.
… and never ended

A chat said "Orca stopped unexpectedly" whenever its runtime left no quit record, and only the
desktop quit wrote one, so a headless server's restart or update, the Settings relaunch, and a
Windows logoff all read as crashes.

Each runtime now records its own start when its chat store opens, and every graceful exit records
its end through one synchronous entry point: the desktop quit's teardown, the headless server's
stop, the in-app relaunch, the GPU-fallback restarts, the update-install watchdog, and Windows
session end. A crash is a runtime that started and never ended; a runtime with no readable record
(never written, pruned, unreadable) names no cause, so the chat keeps its generic words. One file
per runtime, written durably and only by that runtime, so a damaged file never blocks a later
write and two processes never lose each other's record.
… agent has started

On a paired server, Continue waited for the agent to start before answering, and the client
gives a paired call 15 s. A slow start (account switch, login shell, a long resume) showed
"Couldn't continue this chat" while the agent was in fact continuing.

Continue now answers when Orca has accepted the message, as a send does. The agent's start and
answer settle afterwards, and a start that fails is the chat's own note, as before. The restart
dialog's batch still waits for the handover, which is where it counts a start as done. The
verdict helpers move to their own module to keep the continuation file within its size limit.
…e cut, still offers Continue

The cut detector stopped at the first user message after the cut turn, so a steer the turn had
taken, or a conversation command such as /context run after the cut, removed Continue while the
row still named the cause.

The rule for what is no request of its own (a conversation command, a row its turn produced, a
send handed into a running turn) moves out of the latest-request reader into one shared
predicate, which both that reader and the cut detector use. The host's "still wanted?" check
reads the same detector, so the client and host agree.
… turn it ends

When a chat was read before the restart proved its old agent dead, the read could only call the
turn unverifiable. The proof then revised the turn to interrupted, but the death row was scoped
by the turn still marked running, and none was, so it landed on the conversation instead of the
turn. That cut never offered Continue, and the row did not name it as the turn's explanation.

The row is now scoped to the newest root turn the settle actually ends, running or revised.
…out a resume already running

The row's "You can continue in this conversation." came and went with the button: it showed
while a paired host's answer was still on its way, vanished when the button appeared, and came
back the moment Continue was clicked. Continue also appeared on chats the restart prompt or the
launch's own resume was already carrying on.

The row now drops that sentence wherever the chat's host can continue a cut, counting a host
that has not answered yet as able (a host that writes cause rows has Continue), so its words
never change on screen. Continue is hidden while a resume is carrying that chat on.
A Continue the host refused before accepting anything wrote a red note into the chat and brought
the button back, so each retry added another identical note; a chat the host had no record of
was refused with no word at all.

Continue now reports every refusal the same way as a failed request: the existing composer line
"Couldn't continue this chat. Try again, or send a message.", which a retry replaces rather than
repeats. A refusal before acceptance writes no note. A failure after the message was accepted
(the agent could not start) is still the chat's own note, as for any send.
…tion and never folds

A client that re-words host rows it cannot name (the draft that makes these cuts read as
interruptions) treated the cause row as an older red row and replaced its words, so the cause
never showed there. The row was also folded away under its collapsed turn once shown muted.

The host's cause row now names the presentation 'orca-stop' beside today's words, failure fact and
red tone, so a client that predates both changes still prints exactly today's row, red and on
screen, and a client that re-words unnamed rows passes it through. This build shows it muted,
counts it as no failure (the reply it cut stays the turn's answer), and never folds it; the fold
field becomes `explainsTurn`, as the other change names it.
…r re-presented it

Pins the rendered tone on this build: the stored red row, and the same row after a reader
re-presents it in the neutral tone with its presentation and cause kept, both render muted and
never fold. The phone draws chat rows without tone styling, so it needs no change.
…continued

The composer line a failed or refused Continue set stayed on screen while the agent carried on:
after an answer lost in transit, or once another client or the restart prompt continued the
chat. Only the next Continue click or the user's own send cleared it, and a click also wiped an
unrelated composer error.

The line is now derived: shown only while the chat still sits on the cut that Continue failed
on, so it goes as soon as the journal shows the chat continued, from anywhere. A Continue click
clears only its own line, and a retry answered "already continued" leaves none.
…esume the chat

With "resume automatically" on, Continue showed on a quit or update cut while the launch was still
waiting for its settings and reading the restart offer, then vanished when the launch's own
resume began; a click in between sent a competing continuation.

The launch's one decision (nothing offered, ask, or resume) is now published, and the chats it
resumes are named the moment it decides, with no gap. Until it decides, and while the setting
has not loaded or is on, Continue stays hidden on this machine's chats; a paired server's chats
are not the launch's to resume and keep it.
… write and any clean exit

Three ways the runtime record could still read a graceful stop as a crash:

- A chat host reinstalled during the quit (a request landing after teardown began) recorded the
  runtime's start again and erased the end it had just written. A second start of the same
  runtime now keeps that end.
- When the end could not be written (a full disk), the start alone stayed and read as a crash.
  The runtime now removes its record, so its chats name no cause.
- Each `app.exit(0)` had to remember to record the end. A process 'exit' with code 0 now records
  a quit when nothing else did: Electron emits it on every quit and exit once its loop runs
  (`app.exit` -> Browser::Shutdown -> the app's 'quit' -> process 'exit'), and Node on every
  `process.exit`. The relaunch and GPU-fallback calls it covers are dropped; the quit teardown,
  the headless server's stop, the update watchdog and Windows session end keep theirs, which run
  earlier or say more.
…me-in-chat

# Conflicts:
#	src/main/runtime/agent-session-record-store-file.ts
…o its own failure shows

Since the "Couldn't continue" line became derived, an older composer error (such as "Remove
attachments before using a chat-session command.") outranked it: a failed Continue showed the
old error instead, and a Continue that went through left the old error on screen.

A Continue click is the user's newer action, so it clears the composer's error again, as before;
the line then shows the Continue's own failure, if any. That failure still goes away by itself
once the chat is continued, and nothing but the user's own Continue click clears an unrelated
composer error.
…ntime stamped on it

A chat start checks that the process it just started is the one the record names, by a deep
comparison of the stored owner with the adapter's process. The store stamps that owner with the
Orca runtime holding it, so the check passed only because the store happened to return the record
from before the stamp; returning the published record would have refused every chat start with
agent_session_ownership_unknown.

The start now compares the process identity without the runtime stamp, which says who holds the
process rather than which process it is.
…me-in-chat

# Conflicts:
#	src/shared/agent-session-journal-schemas.ts
@brennanb2025

brennanb2025 commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Review status — ready for review

Head: 5bc40fcc255 (main merged at 80d45095d2f). Three conflicts from main's #24863 (SSH hosts run a managed Orca server), all kept from both sides: the server capability list keeps the Continue capability next to main's new server capabilities; the server's stop path still records "Orca quit" before anything waits; the damaged-history test keeps main's wait for the restart-offer lock and this PR's order-free file listing. On the merged tree tc:node and tc:web pass, 23 test files (296 tests) pass and lint is clean. CI pending. (Previous head 232128bea5f was fully green: 27 pass, 12 skipped, 0 fail.)

The problem. When Orca on this computer or on a paired Orca server quits, installs an update, or crashes in the middle of a structured-chat reply, the chat showed one generic red row ("{agent} stopped while this response was in progress…"). It never said that Orca stopped, why, or on which machine. The only one-click way to continue was the local-only resume dialog, which never appears after a crash or for a paired server.

What changes for the user.

  • The cut reply now carries one muted row: "Orca on {machine} restarted for an update / was closed / stopped unexpectedly while this response was in progress."
  • A Continue button above the message box asks the agent to check its last step and carry on. It sends exactly once, and works for local and paired chats.
  • Older servers keep today's generic row with no button. Older clients and the phone keep today's single row.

Fixed during review (all on this branch, each with a test that fails when the fix is reverted):

  • Graceful exits shown as crashes. A missing "quit" record was read as a crash, so a headless paired server stopped by its service manager, an in-app relaunch, or a Windows logoff would have said "stopped unexpectedly". Each Orca process now records its start and its end, and "stopped unexpectedly" needs a start with no end. Every graceful exit records its end, including a catch-all for any clean process exit. A missing, unreadable or pruned record keeps the generic words.
  • Continue answered too late on paired servers. Continue waited until the agent had started, so a slow start on a paired server would have shown "Couldn't continue" while the agent was in fact continuing. It now answers once Orca accepts the message, and a later failure becomes a note in the chat.
  • Continue missing after a steer. A reply the user steered, or a command such as /context run after the cut, lost the Continue button. Continue also never appeared when a death was proven only after an earlier settle.
  • Flicker. The row text no longer changes on load or click. Continue no longer flashes while "resume automatically" is resuming the chat.
  • Composer error line. The "Couldn't continue" line clears once the chat moves on, and a refusal no longer stacks red notes.
  • Compatibility with fix(native-chat): a chat cut off by a crash, quit or restart reads Interrupted until its state changes #25670. Rows carry their own presentation, so a client running fix(native-chat): a chat cut off by a crash, quit or restart reads Interrupted until its state changes #25670 doesn't re-word them. They stay red in storage, so clients without either PR keep today's visible row.
  • Hardening. Chat start no longer depends on the store returning the record before the runtime stamp is added. The record files reuse the repo's durable-write and bounded-read helpers.
  • Housekeeping. Merged main three times and bumped the structured-method count test.

Deferred (none block release):

Verified.

  • Live QA (hidden Electron on a separate Mac, isolated profiles, real Claude, a client paired to a server):

    • correct sentence, machine name and muted row for local quit, local crash, Settings relaunch, paired-server SIGTERM and paired-server SIGKILL;
    • Continue sends exactly one continuation, including on a double-click and for a paired server;
    • user Stop, quit after a finished reply, and sending your own message behave as before;
    • a re-run on the final code confirmed quit, relaunch, crash and "resume automatically" (no Continue flash, one continuation).

    Screenshots are in the description.

  • Automated: unit tests for every fix above, each ablated; targeted suites across the touched areas; the cross-version wire suite with the new method; localization and RPC-catalog checks; quality gates.

  • Review: an architecture challenge plus three review rounds, and a final readiness pass (remote/compat/data loss, crash/perf/security, functional/organization). No P0, P1 or open P2 remains.

Not verified.

  • The "restarted for an update" sentence live: the real install path refuses while another Orca runs. Unit-tested.
  • An older paired server live: no older build was available. The cross-version suite covers it.
  • Windows and Linux live runs, including Windows logoff and orcad under systemd. These were reviewed in code only.
  • The phone app.
  • The cross-version wire suite on the final head locally: the machine was overloaded, so CI runs it.
  • Typecheck: tc:node and tc:web pass at the head above.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 6, 2026 00:16
@coderabbitai

coderabbitai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4dda5d6a-1fc7-47b3-bf32-b5e8192a4b1c
📥 Commits

Reviewing files that changed from the base of the PR and between f13ed9d and 624fe53.

⛔ Files ignored due to path filters (1)
  • src/shared/rpc-contract/rpc-params-catalog.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (13)
  • src/main/native-chat/agent-session-wire/structured-agent-session-crash-turn-end.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-shutdown-cut-row.test.ts
  • src/renderer/src/components/native-chat/NativeChatNoticeRow.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
  • src/renderer/src/components/native-chat/native-chat-transcript-slots.ts
  • src/renderer/src/components/native-chat/use-structured-chat-live-session.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/shared/agent-session-record.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/ja.json

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


📝 Walkthrough

Walkthrough

The changes add runtime-end records and attribute runtime endings to agent-session death evidence. They add turn-scoped Orca stop rows and a host RPC for continuing interrupted turns. Native chat now displays cause-specific stop notices and offers continuation when the host supports it and the cut remains eligible. The changes also update launch-resume tracking, transcript folding, and localized messages.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 624fe

No concrete issue in the reviewed changes currently calls for a merge hold.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 4c910

Continue can restart interrupted work on a local or paired computer. The implementation checks that the interrupted turn is still current and limits duplicate requests. No introduced security vulnerability was established, but paired-computer authorization and complete shutdown recovery were not fully verified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — Each Continue request addresses one session and interrupted turn on the selected runtime and can initiate further work in that conversation. Whether an authenticated paired client may address every session on that host or only a restricted subset remains unverified.

Trust Boundaries and Controls

  • observed — Client-supplied identifiers are checked against the host journal’s current cut before continuation acceptance. The continuation body is constructed server-side. Capability negotiation controls compatibility, while the inspected turn and journal checks do not by themselves prove paired-client authorization.

Resilience and Maintainability Implications

  • inferred — An absent in-memory session is not sufficient evidence of a teardown defect: send admission prepares and opens the durable conversation before the latest-cut callback runs. Complete disposed-host and reconnect recovery remains a coverage gap rather than an established failure.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.95% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 73 functions across 52 files. (6 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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 summarizes the main change: explain why Orca stopped a reply and offer a Continue action.
Description check ✅ Passed The description covers the required sections with detailed user-facing changes, rationale, visual proof, testing, compatibility notes, and checklists. The maintainer-specific Linked Issue exception is…
Full details: Docstring Coverage

Explanation

Docstring coverage is 47.95% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 73 functions across 52 files. (6 skipped: 6 unsupported.)

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

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.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

  • Host records why Orca stopped, in the chat's history: the cut turn's existing stale-session: status row gains an optional orcaStop: { cause } and presentation: 'orca-stop' (structured-agent-session-orca-stop-row.ts, dead-generation-settlement.ts), so every client reads the cause the same way it reads the chat. Older clients keep the stored red generic row — the journal schema is non-strict and strips the unknown key.
  • Crash distinguished from quit/update by per-process start/end records: new agent-session-runtimes/<runtime>.json files (agent-session-runtime-end-record.ts) are written on store open and on every graceful exit, and attributeAgentSessionRuntime stamps deathEvidence.runtimeEnd when a proven death is held by an earlier runtime. crash is concluded only from a recorded start with no end.
  • New agentSession.continueInterrupted RPC, capability-gated behind agent-session.continue-interrupted.v1, reusing the restart continuation and re-checking the cut under the session lock (structured-agent-session-interrupted-continuation.ts).
  • Renderer Continue + muted cause row: useNativeChatInterruptedContinuation, NativeChatInterruptedContinue.tsx, the orca-stop context and words, the machine label hook, and launch-resume gating that suppresses Continue while an opted-in launch may still resume local chats.
  • Shared helpers and refactors: latestNativeChatOrcaStopCut, agent-session-orca-stop.ts, isStructuredAgentSessionNonRequestRow, reportsCompaction → explainsTurn, and the fold/projection changes that keep an orca-stop row on screen and out of the turn's failure count.

Verified the send-once and crash-attribution mechanisms with independent review passes; both are sound for the single-live-runtime model, and the guards are pinned by tests (double click, two clients, lost-answer retry, user-stop/idle-eviction/older-host staying generic).

ℹ️ Two-runtime attribution limit (documented, non-blocking)

The rule "a runtime record with no end reads as crash" conflates still running with crashed. It is only reachable when two live Orca processes share one state directory (single-instance lock bypassed): the second process snapshots runtimeEnds once at load, sees the still-running first runtime as crash, and could stamp that onto a death it attributes to it. This is already called out in the PR body's residual limits, so it is not new — noting it only to confirm it is the one residual the independent passes found and that it is accepted.

Technical details
# Two live runtimes on one state directory read as a crash

## Affected sites
- `src/main/runtime/agent-session-runtime-end-record.ts:139-140` — a record lacking `end` maps to `'crash'`
- `src/main/runtime/agent-session-runtime-attribution.ts:36-54` — consumes the map with no liveness check on `heldBy` (only `heldBy === incarnation` is guarded)
- `src/main/runtime/agent-session-store-transactions.ts:90-93` — `runtimeEnds` is snapshotted once and never refreshed

## Required outcome
None required for merge. Behavior is the documented residual limit ("two Orca processes forced
onto one profile ... would read the still-running one as started-never-ended"). If that limit is
ever lifted, a liveness check on the held-by runtime is the missing guard.

## Open questions for the human
- Is bypassing the single-instance lock considered unsupported enough that no further guard is wanted?

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (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 found.

Reviewed changes

  • Delta since the prior review (2264121 → 4c91058): a merge of origin/main into the branch. The PR's own changed files are unchanged — the diff TOC line ranges and per-file region hashes are identical to the previously reviewed revision — so no PR-authored code moved and the earlier review stands.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

…me-in-chat

# Conflicts:
#	src/renderer/src/i18n/locales/en.json
#	src/renderer/src/i18n/locales/es.json
#	src/renderer/src/i18n/locales/fr.json
#	src/renderer/src/i18n/locales/ja.json
#	src/renderer/src/i18n/locales/ko.json
#	src/renderer/src/i18n/locales/zh.json
…me-in-chat

# Conflicts:
#	src/shared/protocol-version.ts
…me-in-chat

# Conflicts:
#	src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
…me-in-chat

# Conflicts:
#	src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
#	src/renderer/src/i18n/locales/en.json
#	src/renderer/src/i18n/locales/es.json
#	src/renderer/src/i18n/locales/fr.json
#	src/renderer/src/i18n/locales/ja.json
#	src/renderer/src/i18n/locales/ko.json
#	src/renderer/src/i18n/locales/zh.json
…me-in-chat

# Conflicts:
#	src/main/runtime/agent-session-record-store-file.ts
#	src/renderer/src/components/native-chat/native-chat-transcript-slots.ts
… live

Main's #26038 dropped stateDirectory from JournalHostDatabase; this PR's
runtime end records are read from and written beside it.
…me-in-chat

# Conflicts:
#	src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
#	src/renderer/src/i18n/locales/en.json
#	src/renderer/src/i18n/locales/es.json
#	src/renderer/src/i18n/locales/fr.json
#	src/renderer/src/i18n/locales/ja.json
#	src/renderer/src/i18n/locales/ko.json
#	src/renderer/src/i18n/locales/zh.json
…me-in-chat

# Conflicts:
#	src/main/native-chat/agent-session-wire/structured-agent-session-journal-corruption.test.ts
#	src/main/orcad/orcad-lifecycle.ts
#	src/shared/protocol-version.ts
@brennanb2025
brennanb2025 merged commit 995ef11 into main Oct 7, 2026
80 of 81 checks passed
@brennanb2025
brennanb2025 deleted the brennanb2025/rsc-resume-in-chat branch October 7, 2026 21:52
brennanb2025 added a commit that referenced this pull request Oct 7, 2026
…nto brennanb2025/rsc-resume-reconnect

#25652: each workspace is a bordered box drawn in the content area, a
nested child's box sits inside its parent's, and rows have no dividers.
Under a machine row everything a machine holds moves in one level while
every checkbox stays in the outside column. The machine row is the header
over its boxes, never boxed: the project's band in the content area, set
off from the previous machine's boxes by twice the list's gap.

Main via the base: the in-chat "why Orca stopped" row and Continue
(#25675) and the desktop send layer (#25959).
- Host: main's restart-resume contract module now also declares this
  branch's listed dismissal; the branch's own copy of that type is gone.
  continueInterrupted and the listed dismissal both stay.
- Client: one options argument carries both the pairing fence and the
  compatibility skip; restartContinue keeps its 195 s.
- Continue is hidden while a resume carries the chat on its own machine
  (resumes are per machine here), and the launch's resume decision is
  marked where main marks it: when this computer's resume names its
  chats, and when the launch read ends.
brennanb2025 added a commit that referenced this pull request Oct 8, 2026
Main's #25675 now writes the quit's own row about a cut turn, and these restart tests read it twice:
as stored and as the transcript shows it. The host stores it red so an older client keeps it on
screen, and this branch's reader shows it muted, so the two views differ only in that tone. The
stored-row assertions now expect the red row; the reader assertions keep the muted notice.
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