Skip to content

feat: chat tabs on orchestration v2 - #152

Merged
adampeterhiggins merged 2 commits into
t3code/sync-upstream-2026-10-05from
t3code/stack-chat-tabs
Oct 5, 2026
Merged

adampeterhiggins merged 2 commits into
t3code/sync-upstream-2026-10-05from
t3code/stack-chat-tabs

Conversation

@adampeterhiggins

Copy link
Copy Markdown
Owner

The orchestration v2 merge (#149) kept the chat tab clients compiling but turned the server into a stub: memberships returned nothing and list, create, and handoff failed. Tabs could not be opened, summarized, forked, or settled as one sidebar row, and the web entry points for forking into a tab were removed from ChatView.

How it works on v2

  • Service and HTTP. A new ThreadTabs service (apps/server/src/threadTabs/ThreadTabs.ts) owns the tab logic, and threadTabs/http.ts is now a thin transport. It reads shells and snapshots through ThreadManagementService, so migrated v1 threads hydrate their transcript before they are summarized. Creating a tab dispatches the v2 thread.create (createdBy: "user", creationSource from the client, defaulting to web) on the source's branch and worktree. The membership row is written first and removed if the dispatch is rejected. The Linear and GitHub issue thread-link refresh calls are kept.
  • Native fork into a tab. New endpoint POST /api/thread-tabs/:threadId/fork dispatches upstream's thread.fork at a run and adds the fork to the open tab's group. In the web app, Fork from this response goes through it (forkResponseIntoTab), so the new tab continues the actual conversation instead of starting from a summary. When the server has no tabs (an upstream server, where no tab group loads), the button falls back to upstream's separate-thread fork.
  • Summary forks kept. "Fork before a user message" and "fork to another model or account" still create a tab and attach a thread-tab summary chip, because v2 has no native way to do either. Restored in ChatView: onForkFromMessage, onForkModel, canForkModel, and modelRequiresFork. The provider account picker's New tab option works again too, since it is driven by onForkModel.
  • Summary. summary.ts now reads the v2 timeline (visibleTurnItems, which includes history a fork inherited): dialogue, reasoning, commands and other tool calls, errors, checkpoint files, and the latest proposed plan. I kept the fork's extractive format rather than reusing upstream's ContextHandoffBudget rendering. That renderer is built for replay to a provider (per-item attribution headers and t3_thread_read hints), while the web attach picker parses the fork's line format for its hover preview.
  • Group settlement. settlement.ts follows OrchestratorV2.streamStoredEventsFrom, starting from the current end of the log, and resumes from the last event it saw if the stream fails. run.created and thread.unsettled wake a group's settled tabs. thread.settled settles the rest of the group. The settle is undone instead while a sibling has an active run, a pending runtime request, background work that holds completion, or a queued turn (threadHasQueuedTurnStart from ThreadSettlementService). For an automatic settle (server:auto-settle: command prefix), a sibling's open pull request also undoes it. It is wired in server.ts the same way as PullRequestWatchReactor.
  • Mobile. ThreadRouteScreen decided whether a tab was empty with selectedThreadDetail?.messages.length === 0, which is false until the detail loads. It now checks the shell (latestRun, latestUserMessageAt, runtime). Mobile tab creation sends creationSource: "mobile".

Not restored

  • Siblings adopting a checked-out branch together (sharedWorkspace.ts and the v1 CheckpointReactor hook). Orchestration v2 has no branch-drift following for any thread; RunFinalizationService only refreshes status when the recorded branch already matches. So there is no upstream behaviour to extend. Tabs get the source's branch and worktree when they are created or forked, so siblings stay consistent with each other, and tabs behave like any other v2 thread when someone switches branches by hand. If upstream brings drift following back, the sibling rule belongs there.
  • The afterMessageId summary path is still in the contract and on the server, but the web app now uses the native fork for responses.

Contract and client-runtime changes (stacked PRs build on these)

  • CreateThreadTabInput.creationSource?: OrchestrationV2CreationSource, optional and defaulting to web.
  • New ForkThreadTabInput { threadId, sourceThreadId, runId, title?, creationSource? } and the threadTabs.fork endpoint (POST /api/thread-tabs/:threadId/fork, returns ThreadTabGroup).
  • New forkThreadTabFromRun in @t3tools/client-runtime/thread-tabs.
  • Server: the ThreadTabs service (create, fork, handoff, group, memberships) is the place MCP or agent entry points should call.

Verification

  • npx tsc --noEmit in packages/contracts, packages/client-runtime, apps/server, apps/web, apps/mobile: clean.
  • npx vp test run src/threadTabs/ in apps/server: 4 files, 16 tests. These port the fork's summary and settlement tests to v2, add ThreadTabs service tests for create, fork, and rollback, and port the "summarizes any other thread for a draft, never itself" handoff test that lived in the deleted server.test.ts.
  • npx vp test run src/threadTabs.test.ts in packages/client-runtime. npx vp test run src/components/chat/ThreadTabs.test.tsx src/components/chat/threadAttachPickerSummary.test.ts in apps/web.
  • npx vp check --fix on the changed files.
  • Not exercised in a running client.

Done by Claude Opus 5.5 in Claude Code (via T3 Code).

🤖 Generated with Claude Code

adampeterhiggins and others added 2 commits October 5, 2026 12:49
The orchestration v2 merge left the chat tab server as a stub, so tabs could
not be listed, created, summarized, or settled as a group. Rebuild it on v2:

- ThreadTabs service (thin threadTabs HTTP group) reads v2 shells, creates
  tabs with thread.create on the source's branch and worktree, and adds a
  fork endpoint that joins upstream's native thread.fork to the tab group.
- Summaries read the v2 timeline (visible turn items), keeping the format the
  web preview parses.
- Group settlement follows the stored v2 event stream and mirrors settles and
  wakes across a group, blocking on active runs, pending requests,
  background work, and queued turns.
- Web restores fork-before-message, fork-to-model, and the account picker's
  New tab; Fork from this response opens a native fork as a new tab.
- Mobile judges an empty tab from the thread shell.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@adampeterhiggins
adampeterhiggins merged commit 1aee26d into t3code/sync-upstream-2026-10-05 Oct 5, 2026
adampeterhiggins added a commit that referenced this pull request Oct 5, 2026
Brings in upstream's orchestrator v2 (pingdotgg#2829) and everything
up to 51f7f77, with the fork's features ported onto it:

- conflict resolution and v2 ports of every fork feature (chat tabs, Devin
  and Custom ACP drivers, agent access MCP, launch flow, PR watch extras,
  usage-limit extras, transcript export, conversation import, tool-call
  hover previews, Cursor per-instance usage)
- the Agents sidebar panel restored next to Lineage, with in-panel agent
  inspection and the audited regressions fixed
- Cursor out-of-usage stops shown as usage limits, Cursor turn usage and
  delegated-task usage recorded
- Claude and Codex edits carry real unified diffs
- fork migration 55 repaired so fork databases get upstream's v2 schema

The fork's work was developed in #149 and the stacked PRs #152-#166 (history
kept on t3code/sync-upstream-2026-10-05-backup) and squashed into this merge.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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