Skip to content

fix(orchestrator): Close OpenCode tool items when a turn ends under them - #4759

Closed
mwolson wants to merge 223 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/opencode-interrupt-tool-items
Closed

fix(orchestrator): Close OpenCode tool items when a turn ends under them#4759
mwolson wants to merge 223 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/opencode-interrupt-tool-items

Conversation

@mwolson

@mwolson mwolson commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Interrupting an OpenCode turn left whatever tool was mid-flight projected as
running permanently. In the UI that is a spinner that never stops, on a run
the user deliberately stopped.

Stop itself was never broken. The run projects as interrupted and sequential
recoveries map to their own completed runs. Only the tool item leaked.

Problem and Fix

Problem and Why it Happened Fix
finalizeTurn flushed only text and reasoning parts and never tool parts, so a tool still running when session.abort landed kept its last observed running state. OpenCode reports no final state for an aborted tool, so nothing else ever closed it and the row spun forever. Sweep non-terminal tool parts in finalizeTurn and emit them with the turn's terminal status, via an optional terminal override threaded through emitToolPart.
A subagent caught the same way would have collapsed to failed, since that was the only non-success status reachable without a mapping. emitSubagent takes the same override. OrchestrationV2Subagent and OrchestrationV2TurnItem carry the same status literals, so an interrupted subagent stays interrupted.

Validation

Typecheck, lint, unit tests, and a live interrupt scenario against a real
opencode serve, all on this branch rather than on the branch where the fix was
originally written.

  • vpr typecheck across all 15 packages: 0 errors
  • vp check: 0 errors
  • vp test src/orchestration-v2/Adapters/OpenCodeAdapterV2.test.ts: 10 passed
  • Live, direct Stop plus two sequential recoveries on opencode/glm-5.2: pass

The live run captures the projection at several points. Counting
command_execution items by status:

  • Mid-turn: 2 completed, 2 pending
  • At the interrupt boundary: 2 completed, 2 running
  • After the interrupt: 2 completed, 2 interrupted
  • Final: 2 completed, 2 interrupted

The boundary capture shows the tools genuinely in flight; the next one shows
them closed. Before this change they stayed running in every subsequent
capture, which is the reported symptom.

Manual re-test scenarios, in the published guide at
https://nam7nt0rbtm6.postplan.dev:

Scenario 1 is the one this fix changes. Scenarios 2 and 3 passed before and
after, and are included because they are what would catch an over-broad fix that
closed the interrupted run's items by tearing down the provider thread.

Replay coverage

turn_interrupt_mid_tool gains an OpenCode provider, so the fix is driven
through the real adapter rather than asserted on a pure helper. The transcript
reproduces the ordering from a live run rather than an idealised one: the tool
goes running, the abort goes out, session.error and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
interrupted status has to come from the sweep.

Confirmed the fixture catches the regression: with the sweep commented out it
fails with expected 'running' to equal 'interrupted', and nothing else breaks.

Notes for review

The fix was found during an audit of OpenCode background-work and interrupt
coverage that also produced an OpenCode 2.0 driver. That driver is a separate,
much larger change and is deliberately not in this PR; this one is independently
shippable and fixes a bug users can hit today.

OpenCode 1.x has no background work that can outlive the root turn, so
hasPendingBackgroundWork and hasPendingBackgroundWorkForThread remaining
unimplemented on this adapter is correct rather than an oversight.

Current CI Note

The branch-local validation above passes. GitHub Check, Release Smoke, Mobile
Native Static Analysis, Cursor Bugbot, CodeRabbit, and both Macroscope code
checks pass. Test currently fails in five unchanged queued-turn replay
assertions from the newer t3code/codex-turn-mapping merge result; this PR does
not touch queued-turn behavior.

Note

Fix OpenCode tool items left in running state when a turn ends

  • Adds terminalToolStatus in OpenCodeAdapterV2.ts to map a terminal turn status (completed, interrupted, cancelled, failed) to matching node/item statuses for tools that never reported their own terminal state.
  • finalizeTurn now sweeps any tool parts still in pending or running state, calling emitToolPart with the turn's terminal status so they close with a matching status and a non-null completedAt.
  • emitSubagent and emitToolPart accept an optional terminal status parameter; when present, they use terminalToolStatus output instead of the observed toolStatus/toolCompletedAt.
  • Adds a turn_interrupt_mid_tool fixture with an OpenCode transcript and assertion function to cover the interrupted-mid-tool scenario end-to-end.
  • Behavioral Change: subagents and tool items swept at turn finalization now carry the precise terminal status of the turn instead of remaining running indefinitely.

Macroscope summarized a33de3b.


Note

Medium Risk
Changes turn-finalization and tool/subagent projection in the OpenCode adapter—a user-visible orchestration path—but scope is narrow and covered by new unit and replay tests.

Overview
Fixes OpenCode runs where Stop left mid-flight tools projected as running forever (stuck UI spinners) even though the run correctly showed interrupted.

finalizeTurn now sweeps tool parts still pending or running when the turn ends (e.g. after session.abort) and re-emits them via emitToolPart / emitSubagent with an optional terminal override. New terminalToolStatus maps the turn’s terminal outcome (interrupted, cancelled, failed, completed) onto matching item/node statuses; subagents use the same literals instead of collapsing non-success to failed.

Adds unit tests for terminalToolStatus and an OpenCode turn_interrupt_mid_tool replay fixture (including late provider cleanup events on an already-finalized turn).

Reviewed by Cursor Bugbot for commit a33de3b. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0dfeb91b-29bc-4f08-8d40-b74131e3df60

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Jul 28, 2026
@mwolson
mwolson marked this pull request as ready for review July 28, 2026 17:32
@macroscopeapp

macroscopeapp Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved b010622

Bug fix that properly closes tool items when a turn ends mid-flight, preventing stuck spinners in the UI. Changes are self-contained to the OpenCode adapter with clear intent and comprehensive test coverage.

You can customize Macroscope's approvability policy. Learn more.

mwolson added a commit to mwolson/t3code that referenced this pull request Jul 28, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). and removed size:M 30-99 changed lines (additions + deletions). labels Jul 28, 2026
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jul 28, 2026
mwolson added a commit to mwolson/t3code that referenced this pull request Jul 28, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
mwolson added a commit to mwolson/t3code that referenced this pull request Jul 30, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
mwolson added a commit to mwolson/t3code that referenced this pull request Jul 30, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
@mwolson
mwolson force-pushed the fix/opencode-interrupt-tool-items branch from 9f2a8a2 to 0e50a43 Compare July 30, 2026 01:37
mwolson added a commit to mwolson/t3code that referenced this pull request Jul 30, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
@mwolson
mwolson force-pushed the fix/opencode-interrupt-tool-items branch from 0e50a43 to d9e8afa Compare July 30, 2026 01:46
@macroscopeapp
macroscopeapp Bot dismissed their stale review July 30, 2026 01:46

Dismissing prior approval to re-evaluate d9e8afa

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jul 30, 2026
mwolson added a commit to mwolson/t3code that referenced this pull request Jul 30, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
@mwolson
mwolson force-pushed the fix/opencode-interrupt-tool-items branch from d9e8afa to 1e994fd Compare July 30, 2026 02:06
@macroscopeapp
macroscopeapp Bot dismissed their stale review July 30, 2026 02:06

Dismissing prior approval to re-evaluate 1e994fd

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jul 30, 2026
mwolson added a commit to mwolson/t3code that referenced this pull request Jul 30, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
mwolson added a commit to mwolson/t3code that referenced this pull request Jul 30, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
mwolson added a commit to mwolson/t3code that referenced this pull request Jul 30, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
mwolson added a commit to mwolson/t3code that referenced this pull request Jul 30, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
mwolson added a commit to mwolson/t3code that referenced this pull request Jul 30, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
mwolson added a commit to mwolson/t3code that referenced this pull request Jul 30, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
@mwolson
mwolson force-pushed the fix/opencode-interrupt-tool-items branch from 1e994fd to b010622 Compare July 30, 2026 16:14
@macroscopeapp
macroscopeapp Bot dismissed their stale review July 30, 2026 16:15

Dismissing prior approval to re-evaluate b010622

juliusmarminge and others added 11 commits August 10, 2026 18:05
…eysetIndex

Main owns migration numbering: 037_ProjectionTurnsKeysetIndex landed on
main (pingdotgg#5493), so the v2 migrations shift from 037-045 to 038-046.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Native subagent observability (pingdotgg#5219), wired per its spec's v2 merge plan:
- getWorkflowScript RPC re-homed onto the v2 WS surface (contracts, rpc
  group, ws handler, auth scope, client atom).
- AgentsPanel fed by the spec's mapper swap: projectedSubagentsToRuntime
  maps orchestration-v2 subagent entities into the panel model;
  deriveAgentPanelModel's v2Projection leg is now live and the v1 fold
  never runs. Agents surface wired into ChatView + RightPanelTabs.
Other ports and reconciliations:
- Shell reconnect-loop fix (pingdotgg#5561) ported into the v2 shell sync
  (same-session resubscribes resume from the in-memory cursor), with the
  cursor-resume regression test adapted to v2 fixtures.
- Mobile end-follow latch (pingdotgg#5566) ported onto the v2 ThreadFeed.
- Claude ede_diagnostic interrupt classification (pingdotgg#5557) ported into
  ClaudeAdapterV2 (aborted_tools/aborted_streaming => interrupted; CLI
  telemetry never becomes the failure banner). pingdotgg#5559 needs no v2 port
  (unknown system subtypes are already ignored).
- Plan sidebar removed from the v2 ChatView/ChatComposer per main's
  plans-fold-into-chat rework (pingdotgg#5558); rightPanelStore stays at main's
  surface set.
- SettingsPanels rebuilt as main's refactored version plus the branch's
  composer-context setting; sidebar snooze respects the time format
  (pingdotgg#4438 follow-through).
- v1-only leftovers deleted: zombie v1 adapters/ingestion/tests the v2
  rewrite removes, the v1-bound transfer-budget CI harness (pingdotgg#5350, needs
  a v2 rebuild), and main's v1 client pagination machinery (pingdotgg#5493 client
  side; the 037 keyset migration is kept — server-side v2 windowing is a
  follow-up).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rebase kept the LegendList 3.3.3 upgrade and patch from pingdotgg#5449 and the
mobile end-follow latch from pingdotgg#5566, but the v2 MessagesTimeline/ChatView
still carried the branch's blunt any-gesture-breaks-follow listeners.
Port main's pingdotgg#5566 web mechanics onto the v2 follow architecture:

- resolveTimelineIsAtEnd measures the 40px follow re-arm band from real
  geometry (contentLength/scroll/scrollLength minus the composer inset),
  keeping the isNearEnd fallback for older state shapes.
- Follow now breaks only on gestures that can actually leave the live
  edge: upward wheel with overflowing content, touch drags that exited
  the end band, scrollbar drags vs content clicks, and keyboard
  navigation (PageUp/Home/ArrowUp) — previously keyboard scrolling never
  broke follow and the next stream chunk yanked the view back down.
- Listener attach retries across frames so a thread switch cannot mount
  the list without its opt-out listeners.

Deliberately not ported: pingdotgg#5449's shouldRestorePosition disclosure
anchoring and follow-gated maintainScrollAtEnd — the v2 timeline keeps
maintainScrollAtEnd={false} with its own follow scrolls and anchor
system; flipping that core is a separate change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gdotgg#5449)

Complete the pingdotgg#5449 architecture on the v2 timeline, following the
LegendList author's direction to lean on the list's native mechanisms
instead of app-side scroll layers:

- maintainScrollAtEnd is enabled and owned by LegendList, gated off only
  while the user reads history (liveFollowEnabled), while a sent turn
  anchors near the top (anchoredEndSpace), or during the two-frame settle
  of a fold toggle.
- maintainVisibleContentPosition compensates size changes natively
  ({data, size, shouldRestorePosition}); fold toggles anchor compensation
  to the toggled row via a disclosure anchor key, so the trigger stays
  under the pointer instead of the viewport chasing the end.
- ChatView's hand-rolled streaming follow (double-rAF scrollToEnd on
  every data change) is gone; the app now only owns streaming
  adjustments during anchored-end-space mode, mirroring main.
- timelineLiveFollowEnabled state mirrors the follow refs so the
  render-visible gate switches native follow off when a gesture breaks
  follow and back on when the viewport returns to the end band.

Timeline tests updated to assert the native-ownership invariants.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Keep success feedback visible in the Git action control for 10 seconds
- Move the running elapsed timer into the panel menu slot
…s with v2

Post-rebase reconciliation sweep:
- Sidebar: main's folded Sidebar.tsx/Sidebar.logic.ts adapted to v2 shells
  (latestRun/runtime naming, waiting status instead of monitoring), with
  subagent-thread filtering and main's pinned-reorder helpers re-exported
- Pinned drag reorder (pingdotgg#5581) ported into v2: thread.pin orderKey +
  thread.pin.reorder command, thread.pin-reordered event, Orchestrator fold,
  ProjectionStore/Maintenance, client-runtime commands and shell mapping
- Project favicon (pingdotgg#4849-era) and defaultThreadEnvMode flowed through v2
  contracts (OrchestrationProjectShell, application event payloads)
- ChatView: main's pingdotgg#5592 header props, pull-request right-panel surfaces,
  liveAgentCount badge (pingdotgg#5745) wired into the v2 panel layout
- enableAssistantStreaming -> enableLegacyTokenStreaming rename applied to
  v2 RunExecutionService and replay testkit
- Removed v1 zombies resurrected by the rebase (provider service/reaper/
  ingestion + v1 layer tests, server.test.ts, integration harness)
- routeTree: main's tree + branch's /settings/scheduled-tasks route
- Misc marker-sweep syntax repairs (rpc.ts, entities.ts, localApi.test.ts,
  rightPanelStore.test.ts, GitManager.test.ts, mobile model menu helpers)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… 038-040

Main released ProjectionThreadsPinOrderKey (038),
ProjectionProjectsDefaultThreadEnvMode (039) and
ProjectionProjectFaviconPath (040), so the branch-private v2 stack shifts
up by three. Registry ids were already 41-49; this renames the files and
identifiers to match and updates the ledger expectations and through-id
boundaries in the migration tests (released boundary 37 -> 40).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- a6c9b41 (agents open pasted images): ClaudeAdapterV2 now grants the
  attachments dir alongside cwd via additionalDirectories and appends
  '[Attached ... is saved at: path]' lines to the turn text so tools can
  dereference pasted images (pixels alone are not tool-readable).
- 5bb8c03 (settle leaves monitors running): thread.settle now joins
  archive/delete in the provider-session detach set, so PR monitors, dev
  servers and subagent fleets stop when the user parks the thread. The
  settle guard already rejects active runs, and serialized dispatch closes
  the re-engage race the v1 fix handled with onlyIfSettled.
- e70cdb4 (Claude resume handshakes) and 2c7267a (reaper vs live
  background subagents) are already covered structurally in v2: results
  are turn-scoped with explicit zero-turn handshake drops, and idle
  release is pinned while background work is pending.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e panel-visibility merge

The keep-both merge nested main's plan-surface migration test inside a
branch popover test and dropped the threadPanelVisibilityByThreadKey key
from the migration results. Restore main's test body and include the
branch's (empty) visibility map in the expected persisted shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n text

Follow-up to the pingdotgg#5757 port: start and steer turns now append the
'[Attached ... is saved at: path]' line, so the adapter fixtures assert it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch from 22bd872 to a27c1cc Compare August 10, 2026 17:05
The round-3 reconciliation took main's ChatHeader wholesale and wired its
full prop set, resurrecting the scripts/open-in/git-actions cluster the
branch had deliberately relocated into the thread panel. Restore the
79-line slim header (project favicon + name + thread title) and its
minimal ChatView call. pingdotgg#5592's header actions stay a documented v2
follow-up, as decided in round 2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 10, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
juliusmarminge and others added 3 commits August 11, 2026 13:58
- Add ready, merge, and conflict-resolution actions to the PR row
- Share pull request action and handoff logic with the detail panel
- Fix thread details scrolling and row alignment
`finalizeTurn` flushed only `text` and `reasoning` parts, so a tool still
mid-flight when the turn ended kept whatever state it was last observed in.
OpenCode's `session.abort` stops the run without reporting a final state for the
running tool, so nothing ever closed the item: after a Stop, the interrupted
run's `command_execution` stayed `running` permanently and the row spun forever
in the UI.

Sweeps non-terminal tool parts during finalize and emits them with the turn's
terminal status, threaded through `emitToolPart` and `emitSubagent` as an
optional override so the existing projection logic is reused rather than
duplicated. `OrchestrationV2TurnItemStatus` and `OrchestrationV2Subagent`
declare the same status literals, so an interrupted subagent stays interrupted
rather than collapsing to failed.

Found by the new `opencode-interrupt-direct-stop` live pack, which reproduced it
as `command_execution:running` surviving on an `interrupted` run. The same pack
now reports `command_execution:interrupted` with zero running commands.
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
@mwolson
mwolson force-pushed the fix/opencode-interrupt-tool-items branch from 2230488 to a33de3b Compare August 11, 2026 13:24
@juliusmarminge
juliusmarminge force-pushed the t3code/codex-turn-mapping branch 2 times, most recently from a186d64 to 5b1a115 Compare August 12, 2026 23:19
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 13, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
@mwolson

mwolson commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Absorbed into #4786 cleanup-drain. That PR now carries the OpenCode 1.x interrupt-tool commits plus the cleanup drain on current t3code/codex-turn-mapping. Review the OpenCode 1.x work there.

@mwolson mwolson closed this Aug 13, 2026
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 14, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 14, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 14, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 15, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 19, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 19, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 19, 2026
The existing tests covered only the pure status mapping, so they would still
pass with the sweep deleted, applied to already-terminal tools, or failing to
forward the override. Codex review of pingdotgg#4759 called that out.

Adds an OpenCode provider to the `turn_interrupt_mid_tool` replay fixture, which
drives the real adapter and asserts the projection. The transcript reproduces
the ordering from a live run rather than an idealised one: the tool goes
running, the abort goes out, then `session.error` and idle arrive, and only
afterwards does OpenCode's cleanup emit its own tool part update. That late
update lands on an already-finalized turn and is ignored, so the item's
`interrupted` status has to come from the sweep.

Verified the fixture catches the regression: with the sweep commented out it
fails with `expected 'running' to equal 'interrupted'`, and nothing else in the
suite breaks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants