feat(orchestrator): introduce new orchestrator - #2829

Open
juliusmarminge wants to merge 330 commits into
mainfrom
t3code/codex-turn-mapping
Open

feat(orchestrator): introduce new orchestrator#2829
juliusmarminge wants to merge 330 commits into
mainfrom
t3code/codex-turn-mapping

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 27, 2026

Copy link
Copy Markdown
Member

Summary

  • wire orchestration V2 provider adapter registry/factory flow for Codex and Claude provider instances
  • add Claude replay/query primitives, native fork/rollback fixtures, subagent fixture coverage, and provider replay harness updates
  • update debugger model/provider picker and improve user-facing orchestration errors

Validation

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test -- src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts -t claudeAgent
  • bun run test -- src/orchestration-v2/testkit/ClaudeReplayFixtures.integration.test.ts
  • bun run test -- src/orchestration-v2/testkit/ThreadFork.integration.test.ts -t Claude

Notes

  • Draft PR for review of current branch state. Codex all-provider replay still needs schema alignment with latest app-server behavior before it can be treated as a full-suite signal.

Closes

Verified against the branch with code/commit evidence.

High confidence

Closes#4952
Closes#4873
Closes#4775
Closes#4795
Closes#4710
Closes#4668
Closes#4619
Closes#4584
Closes#4561
Closes#4713
Closes#4198
Closes#4452
Closes#3797
Closes#4232
Closes#3666
Closes#3580
Closes#2785
Closes#2789
Closes#3138
Closes#1404
Closes#231
Closes#216

Medium confidence (under review)

Closes#4568
Closes#4766
Closes#4495
Closes#4456
Closes#4399
Closes#3744
Closes#2921
Closes#3624
Closes#3149
Closes#2336
Closes#538
Closes#2173
Closes#2065

Note

Introduce new Orchestration V2 runtime with provider adapters, persistence, and client support

  • Adds a complete Orchestration V2 server runtime: event store, projection store, event sink, effect outbox/worker, ID allocator, command policy, thread management/launch/lifecycle/fork/settlement services, checkpoint capture/rollback, provider session manager, runtime recovery, and context handoff services across orchestration-v2/
  • Adds V2 provider adapters for Codex, Claude, Cursor, Grok, OpenCode, and ACP Registry, plus a provider adapter registry and driver framework with replay testkits for each adapter
  • Adds persistence migrations 044–052 for V2 event/projection tables, effect outbox, provider-session bindings, thread-launch workflows, application event sourcing, scheduled tasks, and legacy V1 import state
  • Adds MCP orchestration and worktree toolkits, a project service with enrichment, scheduled-task service, HTTP API layer, and a legacy V1 thread importer that reconciles old threads into V2 events
  • Updates web and mobile clients to consume V2 thread projections, shell snapshots, runtime status, queued-run management, thread relationships, history paging, and scheduled-task controls
  • Risk: legacy session/latestTurn fields are replaced by runtime/latestRun across web and mobile thread state; out-of-tree consumers of the old shell snapshot schema or V1 RPC methods will break. Cache schema version bumped to 3, discarding V1 cache envelopes on decode failure. Orchestration protocol version 2 is now required for client connections.

Macroscope summarized 6081201.


Note

High Risk
Mobile thread persistence, archive/stop guards, and approval/user-input handling now depend on V2 runtime semantics and a bumped cache schema, alongside removal of PR transfer regression reporting.

Overview
Removes the automated “thread transfer impact” PR comment pipeline by deleting the thread-transfer-report workflow and its trusted publisher scripts/tests, while CI still may emit transfer budget artifacts from the main workflow.

Mobile is wired to orchestration V2 end-to-end: SQLite shell/thread caches now use the shared ORCHESTRATION_CACHE_SCHEMA_VERSION and V2 snapshot shapes; the connection runtime loads bounded thread history plus a history controller instead of the old unbounded loader. Thread list/detail flows switch from legacy session/turn fields to runtime summaries (thread.runtime, threadRuntimeIsActive, dedicated archive eligibility), runtime request IDs for approvals and user input (including read-only UI when responseCapability is not live), and checkpoint/review data derived from thread projections rather than embedded checkpoint lists.

Thread UX additions and polish include visit watermark dispatch on open, a queue control and relationships banner on the feed, progressive history controls, a thread activity inspector with rollback, shared brand mark assets, extra adaptive theme tokens, and copy tweaks (“coding runtime”). CI installs build-essential so ACP process-tree pthread fixtures compile instead of soft-skipping. Smaller follow-ons: desktop environment user-data dir test expectations, Tailscale Effect typing, default persistComposerContextStrip, README appearance doc link, and marketing Cursor harness label.

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

@coderabbitai

coderabbitaiBot commented May 27, 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: e3efadfb-5f39-4c7c-970d-8aade3f16f07

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
  • Commit unit tests in branch t3code/codex-turn-mapping

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels May 27, 2026
Comment threadapps/server/src/orchestration-v2/EventStore.ts Outdated
Comment on lines +116 to +120
return decodeTranscript({
...metadata,
entries,
});
});

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.

🟢 Lowtestkit/ReplayTranscriptNdjson.ts:116

The call to decodeTranscript at line 116 invokes Schema.decodeUnknownSync, which throws on validation failure. Since this isn't wrapped in Effect.try, any validation error becomes an uncaught exception (defect) instead of a typed ProviderReplayNdjsonParseError. This breaks the function's declared error contract. Consider wrapping the call in Effect.try to catch the exception and convert it to the declared error type.

- return decodeTranscript({- ...metadata,- entries,- });+ return yield* Effect.try({+ try: () =>+ decodeTranscript({+ ...metadata,+ entries,+ }),+ catch: (cause) =>+ new ProviderReplayNdjsonLineParseError({+ lineNumber: lines.length,+ line: "<transcript validation>",+ cause,+ }),+ });
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts around lines 116-120:
The call to `decodeTranscript` at line 116 invokes `Schema.decodeUnknownSync`, which throws on validation failure. Since this isn't wrapped in `Effect.try`, any validation error becomes an uncaught exception (defect) instead of a typed `ProviderReplayNdjsonParseError`. This breaks the function's declared error contract. Consider wrapping the call in `Effect.try` to catch the exception and convert it to the declared error type.
Evidence trail:
apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts lines 50-53: `decodeTranscript = Schema.decodeUnknownSync(ProviderReplayTranscript)` — throws on failure.
Line 116-118: `return decodeTranscript({...metadata, entries})` — called directly inside Effect.gen without Effect.try wrapper.
Lines 55-68 (`parseReplayRecord`): same pattern but correctly wrapped in `Effect.try`.
Line 80: function declares error type `ProviderReplayNdjsonParseError`.
packages/contracts/src/orchestrationV2.ts lines 1561-1568: `ProviderReplayTranscript` schema with `TrimmedNonEmptyString` fields that can fail validation.

Comment threadapps/server/src/orchestration-v2/ProviderAdapterRegistry.ts Outdated
Comment threadpackages/client-runtime/src/wsRpcClient.ts Outdated
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/EventSink.ts
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 1, 2026
…der adapters (t3-29f.6)
Assessment of upstream PR pingdotgg#2829 (pingdotgg/t3code) from juliusmarminge:
WIP wire orchestration v2 provider adapters with Codex and Claude adapters,
event sourcing, provider session management, and replay testkit.
Relevance to target issues:
- pingdotgg#2838 (session resume): HIGH — ProviderSessionManager persists session
IDs and separates startSession/resumeSession operations
- pingdotgg#2778 (subagent hang): MEDIUM — ProviderEventIngestor provides
infrastructure to forward permission events, but UI plumbing not yet wired
- pingdotgg#2886 (thread stuck working): HIGH — event-sourced projections replace
mutable state flags, eliminating sticky "working" states
The PR is a draft (34 commits, not merged). No OpenCode ACP adapter
exists yet in v2 — OpenCode would need its own adapter wired into the
ProviderAdapterRegistry. Recommend watching for merge and adding an
OpenCode adapter post-merge.
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
The upstream PR pingdotgg#2829 added orchestrationV2 methods to the WsRpcClient
interface. The test mock in service.threadSubscriptions.test.ts was
missing the orchestrationV2 property, causing a typecheck failure:
'Property orchestrationV2 is missing in type...'
Added orchestrationV2 mock with dispatchCommand, getThreadProjection,
subscribeShell, and subscribeThread as vi.fn() stubs.
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
The upstream PR pingdotgg#2829 targets a newer Effect version than our fork's
pinned effect@4.0.0-beta.73. Fixes:
- Replace Random.nextUUIDv4 with Crypto.randomUUIDv4 (beta.73 API)
- Fix deterministic Service tag keys to match fork convention
(include file path segments; e.g. Adapters/ClaudeAdapterV2/...)
- Replace Schema.decodeSync with Schema.decodeUnknownEffect inside
Effect.gen generators (tsgo schemaSyncInEffect rule)
- Replace inline Schema.encodeUnknownSync with module-level wrappers
to avoid schemaSyncInEffect rule inside generators
@github-actions

github-actionsBot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🚀 Expo continuous deployment is ready!

  • Project → t3-code
  • Platforms → android, ios
  • Scheme → t3code-preview
🤖 Android🍎 iOS
Fingerprintfe5a51f2e189da69dfc4c2cd458e6cfb5fdff2eaae3bd597809dfd7771d0898f735d172973d4c1c8
Build DetailsBuild Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
App version: 0.1.0
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Build Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
App version: 0.1.0
Git commit: eea0dcae4150df8341606520c074dc651ae7c00a
Update DetailsUpdate Permalink
DetailsBranch: pr-2829
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Update Permalink
DetailsBranch: pr-2829
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Update QR

Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
Comment threadapps/server/src/ws.ts
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
@juliusmarmingejuliusmarminge changed the title WIP: wire orchestration v2 provider adaptersfeat(orchestrator): introduce new orchestratorJun 14, 2026
Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
Comment on lines +109 to +128
Effect.gen(function* () {
const threadId = payloadInput.threadId ?? input.threadId;
const eventId = yield* idAllocator.allocate.event({
threadId,
providerSessionId: input.providerSessionId,
});
const occurredAt = yield* DateTime.now;
return yield* Schema.decodeUnknownEffect(OrchestrationV2DomainEvent)(
compactUndefined({
id: eventId,
type: payloadInput.type,
threadId,
runId: payloadInput.runId ?? input.runId,
nodeId: payloadInput.nodeId ?? input.nodeId,
provider: input.event.provider,
rawEventId: input.rawEventId,
occurredAt,
payload: payloadInput.payload,
}),
);

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.

🟡 Mediumorchestration-v2/ProviderEventIngestor.ts:109

In makeDomainEvent, the ?? operator on lines 121 and 123 treats explicit null as equivalent to undefined, causing payloadInput.runId ?? input.runId to fall back to input.runId when payloadInput.runId is explicitly null. Since the type is readonly runId?: RunId | null, this means explicit null values from the caller (e.g., input.event.node.runId being null on line 172) are incorrectly overwritten instead of preserved. Consider using === undefined checks like lines 161-162 and 210-211, or use payloadInput.runId === undefined ? input.runId : payloadInput.runId.

 const threadId = payloadInput.threadId ?? input.threadId;
- const runId = payloadInput.runId ?? input.runId;- const nodeId = payloadInput.nodeId ?? input.nodeId;+ const runId = payloadInput.runId === undefined ? input.runId : payloadInput.runId;+ const nodeId = payloadInput.nodeId === undefined ? input.nodeId : payloadInput.nodeId;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/ProviderEventIngestor.ts around lines 109-128:
In `makeDomainEvent`, the `??` operator on lines 121 and 123 treats explicit `null` as equivalent to `undefined`, causing `payloadInput.runId ?? input.runId` to fall back to `input.runId` when `payloadInput.runId` is explicitly `null`. Since the type is `readonly runId?: RunId | null`, this means explicit `null` values from the caller (e.g., `input.event.node.runId` being `null` on line 172) are incorrectly overwritten instead of preserved. Consider using `=== undefined` checks like lines 161-162 and 210-211, or use `payloadInput.runId === undefined ? input.runId : payloadInput.runId`.
Evidence trail:
apps/server/src/orchestration-v2/ProviderEventIngestor.ts lines 105-106 (payloadInput type with `RunId | null`), line 121 (`runId: payloadInput.runId ?? input.runId`), line 122 (`nodeId: payloadInput.nodeId ?? input.nodeId`), lines 161-162 and 210-211 (codebase uses `=== undefined` pattern elsewhere). packages/contracts/src/orchestrationV2.ts line 358 (`runId: Schema.NullOr(RunId)` on ExecutionNode - confirms null is a valid value), line 857 (`runId: Schema.optional(RunId)` on EventBase - domain event uses optional/undefined, not null). apps/server/src/orchestration-v2/ProviderEventIngestor.ts lines 80-81 (compactUndefined only strips undefined, not null).

Comment threadapps/server/src/orchestration-v2/Orchestrator.ts
Comment threadapps/web/src/routes/debug.orchestration-v2.tsx Outdated
Comment threadapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 79031a1 to 4e68dcbCompareJune 14, 2026 23:55
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 4e68dcb to c7539b9CompareJune 17, 2026 07:30
Comment threadapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/ProviderSessionManager.ts Outdated
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts Outdated
Comment threadapps/server/src/mcp/OrchestratorMcpService.ts Outdated
Comment threadapps/server/src/ws.ts Outdated
function nativeThreadId(provider: ProviderKind, thread: OrchestrationV2ProviderThread): string {
const id = thread.nativeThreadRef?.nativeId;
if (id === null || id === undefined || id.trim().length === 0) {
throw new ProviderAdapterProtocolError({

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.

🟡 MediumAdapters/AcpAdapterV2.ts:271

When nativeThreadId is called inside Effect.gen generators (e.g., lines 899, 1813), the thrown ProviderAdapterProtocolError becomes an untyped defect instead of a typed failure. This bypasses Effect.mapError and other typed error handlers, causing the error to propagate as an unexpected defect. Consider converting nativeThreadId to return Effect<string, ProviderAdapterProtocolError> and yielding it at each call site, or inlining the validation with yield* new ProviderAdapterProtocolError(...) so the failure is properly typed.

Also found in 1 other location(s)

apps/server/src/orchestration-v2/ThreadManagementService.ts:278

The statement return yield* managementError(...) cannot work correctly because managementError() returns a ThreadManagementError instance, not an Effect. The yield* operator in Effect.gen expects an Effect value. This should be return yield* Effect.fail(managementError(...)). The correct pattern is demonstrated elsewhere in this file (lines 241-246) where Effect.fail(managementError(...)) is properly used. This same bug pattern repeats at lines 291, 333, 357, 374, 390, 406, and 427.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts around line 271:
When `nativeThreadId` is called inside `Effect.gen` generators (e.g., lines 899, 1813), the thrown `ProviderAdapterProtocolError` becomes an untyped defect instead of a typed failure. This bypasses `Effect.mapError` and other typed error handlers, causing the error to propagate as an unexpected defect. Consider converting `nativeThreadId` to return `Effect<string, ProviderAdapterProtocolError>` and yielding it at each call site, or inlining the validation with `yield* new ProviderAdapterProtocolError(...)` so the failure is properly typed.
Evidence trail:
1. nativeThreadId function with throw: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts lines 268-277
2. Call site inside Effect.gen: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 899
3. Call site inside Effect.gen: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 1813
4. Correct yield* pattern for comparison: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 1808
5. ProviderAdapterProtocolError class definition: apps/server/src/orchestration-v2/ProviderAdapter.ts lines 316-327
6. Effect.gen implementation delegating to fromIteratorUnsafe: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 1104-1125
7. fromIteratorUnsafe calling iter.next() without try/catch: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 1285-1307
8. FiberImpl.runLoop catch block converting thrown errors to exitDie: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 646-650
9. die = exitDie producing Effect<never> (untyped): https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts line 947
Also found in 1 other location(s):
- apps/server/src/orchestration-v2/ThreadManagementService.ts:278 -- The statement `return yield* managementError(...)` cannot work correctly because `managementError()` returns a `ThreadManagementError` instance, not an `Effect`. The `yield*` operator in `Effect.gen` expects an Effect value. This should be `return yield* Effect.fail(managementError(...))`. The correct pattern is demonstrated elsewhere in this file (lines 241-246) where `Effect.fail(managementError(...))` is properly used. This same bug pattern repeats at lines 291, 333, 357, 374, 390, 406, and 427.

juliusmarmingeand others added 23 commits September 1, 2026 16:33
R12 follow-up
Insert persistent feedback blocks by their timestamp within the canonical timeline while preserving projected row order. Keep real optimistic sends appended and suppress duplicate local messages already committed by the server.
Implemented by GPT-5.6 Sol via Codex.
R20 follow-up
Give inspector reasoning markdown its projected source thread and retain the explicit environment fallback for proposed plans without a thread reference. Workspace links and images now resolve through their owning environment after removal of the active-environment fallback.
Implemented by GPT-5.6 Sol via Codex.
Read the inclusive cursor, a full history page, and a look-behind row so older history does not terminate after one page.
Finding: P01 pagination termination
Model: GPT-5.6 Sol via Codex
Cancel pending SDK requests before aborting the native session. Preserve per-admission cancellation state and treat stopped initial prompts as interruption instead of provider failure.
Finding: R14 prompt cancellation
Model: GPT-5.6 Sol via Codex
Keep the original cursor owner through ancestor traversal and preserve the history budget across empty intermediate forks. Verify exact paged history against the complete nested projection.
Finding: P01 nested lineage
Model: GPT-5.6 Sol via Codex
Retain pending admission after transient status failures and use one generation-owned retry worker. Ignore stale timers and duplicate evidence so older prompts cannot finish newer steering.
Finding: R14 status reconciliation
Model: GPT-5.6 Sol via Codex
Keep hidden local and inherited rows from consuming history pages. Preserve stop-request dependencies, source-run cutoffs, and imported history while loading related metadata from the selected cohort and using indexed watermark lookups.
Finding: P01 bounded history visibility
Model: GPT-5.6 Sol via Codex
Adapt grouped tool summaries and the floating working timer to V2 run, attempt, and queue state. Bring over the composer, keyboard, and disclosure transitions while retaining the V2 activity inspector and queue controls.
Keep OV2 web composer and grouping behavior intact; share only the existing command label parser with mobile.
Restores main features dropped by the policy replay: #8569 theme wiring,
settings search rework, #8803 workspace-mutation refresh (v2-adapted),
video + image previews (web and mobile, v2-adapted), #8862 Expo glass,
and the round's docs. Timeline thinking rows (#8984) stay on the v2
work-live system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v2 equivalents of main's #8984 and #8922: a "Working for ..." header
anchors the active run, the trailing live tool row survives between
actions in past tense instead of vanishing, and a shimmering Thinking
row marks reasoning gaps. During workspace preparation the header shows
"Setting up worktree..." (driven by the local dispatch flag or the v2
run's preparing status, so remote viewers see it too), the composer
footer span is gone, and draft promotion waits until the run starts or
startup fails instead of navigating mid-preparation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adopts the round's main features into the v2 architecture: the #9023
media rework (streamed videos, media-file assets, protocol-relative
links), #9098 shared live-activity row folded into the v2 working and
thinking rows, the #9084/#9078 Claude model catalog for v2 consumers,
a native #9005 OpenCode child-session abort in the v2 adapter, #9013's
landed LegendList patch, and per-environment sidebar provider entries.
For #8600 the server-side pieces land, but auto-settle evaluation stays
client-side (reading the new server-owned settings) until the v2
orchestrator grows its own settlement reactor; main's v1-only reactor
and coalescer additions are dropped with the rest of the v1 path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

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.

composerVideos and composerOtherFiles are computed but never rendered — the attachment list below still maps composerFiles (line 4034) into the generic FileIcon row, so ComposerVideoThumbnail (line 176), the new PlayIcon import (line 305), and attachVideoThumbnail / isPreviewableComposerVideo have no live consumer here. The result is a split treatment for the same attachment: a staged video is a plain file row in the composer, while the sent message renders it as a playable tile (MessagesTimeline.tsxuserVideos). The three unused locals will also trip unused-symbol checks.

Smallest fix: render composerVideos as thumbnail tiles (ComposerVideoThumbnail + buildExpandedImagePreview, matching the timeline tile) and feed the existing file-row list from composerOtherFiles; if the composer treatment is not landing in this PR, drop the two locals, ComposerVideoThumbnail, and the PlayIcon import instead so the file does not carry a half-wired path.

Posted via Macroscope — UI Consistency

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.

composerVideos and composerOtherFiles are computed but never rendered — the attachment list below still maps composerFiles (line 4034) into the generic FileIcon row, so ComposerVideoThumbnail (line 176), the new PlayIcon import (line 305), and attachVideoThumbnail / isPreviewableComposerVideo have no live consumer here. The result is a split treatment for the same attachment: a staged video is a plain file row in the composer, while the sent message renders it as a playable tile (MessagesTimeline.tsxuserVideos). The three unused locals will also trip unused-symbol checks.

Smallest fix: render composerVideos as thumbnail tiles (ComposerVideoThumbnail + buildExpandedImagePreview, matching the timeline tile) and feed the existing file-row list from composerOtherFiles; if the composer treatment is not landing in this PR, drop the two locals, ComposerVideoThumbnail, and the PlayIcon import instead so the file does not carry a half-wired path.

Posted via Macroscope — UI Consistency

!hasContent &&
(props.selectedThread.session?.status === "running" ||
props.selectedThread.session?.status === "starting");
const showStopAction = props.canStopThread;

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.

Stop hides send while composing

High Severity

showStopAction now follows canStopThread alone and no longer requires an empty composer. While a run is interruptible, typed text or attachments still replace Send/Queue with Stop, so follow-ups cannot be sent or queued until the run is interrupted.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

selectedThreadFeed,
selectedThreadKey,
]);
}, [props.onSendMessage, selectedThreadKey]);

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.

Send always re-anchors the feed

Medium Severity

handleSendMessage now always sets anchorMessageId to the just-sent id. The previous first-message-only policy in resolveThreadFeedSubmissionAnchor is no longer applied, so follow-ups and second outbox sends retarget the live-follow scroll.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

// Dictation keeps that focus while the composer switches to its compact pill.
const composerBottomInset = (
Platform.OS === "android" ? isKeyboardVisible : composerExpanded || composerFocused
)

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.

iOS composer inset ignores focus

Medium Severity

iOS composerBottomInset now keys only on composerExpanded, and the editor no longer updates composerFocused. Focusing the collapsed pill still applies home-indicator padding above the keyboard, leaving a gap between the composer and the IME.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

…ator
Ports #8600's server-owned settlement to orchestration v2 instead of
keeping client-side evaluation. A ThreadSettlementService sweep runs at
startup, on auto-settle settings changes, and once per minute: it
evaluates inactivity and merged or closed pull requests over v2 thread
shells and dispatches the new guarded thread.auto-settle command, which
rejects threads that changed after the sweep's snapshot or carry any
explicit override, then reuses the orchestrator's settle lifecycle.
With the server deciding, the clients drop their effectiveSettled
evaluation and partition on the persisted settledOverride like main.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@macroscopeapp

Copy link
Copy Markdown
Contributor

apps/server/src/cli/app.ts:232-239

(Posted at PR level: an inline comment could not be anchored on this file in the current diff.)

The Effect.tryPromisecatch in runAppCommand collapses six semantically distinct failures into one error whose distinction survives only as an English sentence inside cause: Schema.Defect() — response timeout, oversized response, non-JSON response, schema-invalid response, request-id mismatch, and "connection closed" all become DesktopAppUnreachableError, whose message asserts the desktop app could not be reached and tells the user to start it. In the four response-shaped cases the app did answer, so that caller-visible message is wrong, and cli/app.test.ts asserts on the prose (cause: { message: "The desktop app response is invalid." }), which makes it behavior rather than diagnostics.

The conventions ask that failures be distinguished by stable structural attributes with the message derived only from them, and that a translation boundary wrap only genuinely lower-level failures rather than flatten known ones. Consider having sendDesktopAppActivationRequest reject with tagged errors (or return a tagged result) so the CLI can keep DesktopAppUnreachableError for the connect-level ENOENT/ECONNREFUSED case its message describes, and add e.g. DesktopAppResponseInvalidError { requestId, workspaceRoot, byteLength } and DesktopAppResponseTimeoutError { requestId, timeoutMs } for the rest, each keeping the real socket error as cause where one exists.

Posted via Macroscope — Effect Service Conventions

Posted via Macroscope — Effect Service Conventions

@macroscopeapp

Copy link
Copy Markdown
Contributor

apps/server/src/cli/app.ts:232-239

(Posted at PR level: an inline comment could not be anchored on this file in the current diff.)

The Effect.tryPromisecatch in runAppCommand collapses six semantically distinct failures into one error whose distinction survives only as an English sentence inside cause: Schema.Defect() — response timeout, oversized response, non-JSON response, schema-invalid response, request-id mismatch, and "connection closed" all become DesktopAppUnreachableError, whose message asserts the desktop app could not be reached and tells the user to start it. In the four response-shaped cases the app did answer, so that caller-visible message is wrong, and cli/app.test.ts asserts on the prose (cause: { message: "The desktop app response is invalid." }), which makes it behavior rather than diagnostics.

The conventions ask that failures be distinguished by stable structural attributes with the message derived only from them, and that a translation boundary wrap only genuinely lower-level failures rather than flatten known ones. Consider having sendDesktopAppActivationRequest reject with tagged errors (or return a tagged result) so the CLI can keep DesktopAppUnreachableError for the connect-level ENOENT/ECONNREFUSED case its message describes, and add e.g. DesktopAppResponseInvalidError { requestId, workspaceRoot, byteLength } and DesktopAppResponseTimeoutError { requestId, timeoutMs } for the rest, each keeping the real socket error as cause where one exists.

Posted via Macroscope — Effect Service Conventions

Posted via Macroscope — Effect Service Conventions

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

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

@@ -2085,12 +2062,9 @@ export default function Sidebar() {
// memo exactly at the next wake boundary.
void snoozeWakeTick;
const preciseNow = new Date().toISOString();
const visible = threads.filter(

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.

Web sidebar pinned threads partition completely broken

High Severity

The partition loop declares const pinned: EnvironmentThreadShell[] = [] at line 2068 but never pushes anything into it. The old code had an else if (thread.pinnedAt != null) { pinned.push(thread); } branch between the snoozed and settled checks, but it was removed in this refactor. All pinned threads now fall into active instead. The mobile equivalent in threadListV2.ts correctly retains else if (thread.pinnedAt != null) { pinned.push(thread); } at line 421. This means pinnedThreads is always empty, the pinned section never renders, DnD reordering is inert, and reorderablePinnedKeys is an empty set.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

thread.session?.status === "running" &&
thread.session.activeTurnId != null
) {
if (action === "archive" && threadRuntimeIsActive(thread.runtime)) {

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.

Archive guard stricter than old threadCanArchive behavior

Medium Severity

The comment says "Archive keeps its original, narrower guard" but threadRuntimeIsActive is actually wider (more restrictive) than the replaced threadCanArchive. threadCanArchive allowed archiving queued threads without an activeRunId and all waiting threads; threadRuntimeIsActive blocks both. The old threadCanArchive is still imported at line 21 but now unused, confirming a mistaken substitution rather than an intentional tightening.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(orchestrator): introduce new orchestrator - #2829

Open
juliusmarminge wants to merge 330 commits into
mainfrom
t3code/codex-turn-mapping
Open

feat(orchestrator): introduce new orchestrator#2829
juliusmarminge wants to merge 330 commits into
mainfrom
t3code/codex-turn-mapping

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 27, 2026

Copy link
Copy Markdown
Member

Summary

  • wire orchestration V2 provider adapter registry/factory flow for Codex and Claude provider instances
  • add Claude replay/query primitives, native fork/rollback fixtures, subagent fixture coverage, and provider replay harness updates
  • update debugger model/provider picker and improve user-facing orchestration errors

Validation

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test -- src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts -t claudeAgent
  • bun run test -- src/orchestration-v2/testkit/ClaudeReplayFixtures.integration.test.ts
  • bun run test -- src/orchestration-v2/testkit/ThreadFork.integration.test.ts -t Claude

Notes

  • Draft PR for review of current branch state. Codex all-provider replay still needs schema alignment with latest app-server behavior before it can be treated as a full-suite signal.

Closes

Verified against the branch with code/commit evidence.

High confidence

Closes#4952
Closes#4873
Closes#4775
Closes#4795
Closes#4710
Closes#4668
Closes#4619
Closes#4584
Closes#4561
Closes#4713
Closes#4198
Closes#4452
Closes#3797
Closes#4232
Closes#3666
Closes#3580
Closes#2785
Closes#2789
Closes#3138
Closes#1404
Closes#231
Closes#216

Medium confidence (under review)

Closes#4568
Closes#4766
Closes#4495
Closes#4456
Closes#4399
Closes#3744
Closes#2921
Closes#3624
Closes#3149
Closes#2336
Closes#538
Closes#2173
Closes#2065

Note

Introduce new Orchestration V2 runtime with provider adapters, persistence, and client support

  • Adds a complete Orchestration V2 server runtime: event store, projection store, event sink, effect outbox/worker, ID allocator, command policy, thread management/launch/lifecycle/fork/settlement services, checkpoint capture/rollback, provider session manager, runtime recovery, and context handoff services across orchestration-v2/
  • Adds V2 provider adapters for Codex, Claude, Cursor, Grok, OpenCode, and ACP Registry, plus a provider adapter registry and driver framework with replay testkits for each adapter
  • Adds persistence migrations 044–052 for V2 event/projection tables, effect outbox, provider-session bindings, thread-launch workflows, application event sourcing, scheduled tasks, and legacy V1 import state
  • Adds MCP orchestration and worktree toolkits, a project service with enrichment, scheduled-task service, HTTP API layer, and a legacy V1 thread importer that reconciles old threads into V2 events
  • Updates web and mobile clients to consume V2 thread projections, shell snapshots, runtime status, queued-run management, thread relationships, history paging, and scheduled-task controls
  • Risk: legacy session/latestTurn fields are replaced by runtime/latestRun across web and mobile thread state; out-of-tree consumers of the old shell snapshot schema or V1 RPC methods will break. Cache schema version bumped to 3, discarding V1 cache envelopes on decode failure. Orchestration protocol version 2 is now required for client connections.

Macroscope summarized 6081201.


Note

High Risk
Mobile thread persistence, archive/stop guards, and approval/user-input handling now depend on V2 runtime semantics and a bumped cache schema, alongside removal of PR transfer regression reporting.

Overview
Removes the automated “thread transfer impact” PR comment pipeline by deleting the thread-transfer-report workflow and its trusted publisher scripts/tests, while CI still may emit transfer budget artifacts from the main workflow.

Mobile is wired to orchestration V2 end-to-end: SQLite shell/thread caches now use the shared ORCHESTRATION_CACHE_SCHEMA_VERSION and V2 snapshot shapes; the connection runtime loads bounded thread history plus a history controller instead of the old unbounded loader. Thread list/detail flows switch from legacy session/turn fields to runtime summaries (thread.runtime, threadRuntimeIsActive, dedicated archive eligibility), runtime request IDs for approvals and user input (including read-only UI when responseCapability is not live), and checkpoint/review data derived from thread projections rather than embedded checkpoint lists.

Thread UX additions and polish include visit watermark dispatch on open, a queue control and relationships banner on the feed, progressive history controls, a thread activity inspector with rollback, shared brand mark assets, extra adaptive theme tokens, and copy tweaks (“coding runtime”). CI installs build-essential so ACP process-tree pthread fixtures compile instead of soft-skipping. Smaller follow-ons: desktop environment user-data dir test expectations, Tailscale Effect typing, default persistComposerContextStrip, README appearance doc link, and marketing Cursor harness label.

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

@coderabbitai

coderabbitaiBot commented May 27, 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: e3efadfb-5f39-4c7c-970d-8aade3f16f07

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
  • Commit unit tests in branch t3code/codex-turn-mapping

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels May 27, 2026
Comment threadapps/server/src/orchestration-v2/EventStore.ts Outdated
Comment on lines +116 to +120
return decodeTranscript({
...metadata,
entries,
});
});

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.

🟢 Lowtestkit/ReplayTranscriptNdjson.ts:116

The call to decodeTranscript at line 116 invokes Schema.decodeUnknownSync, which throws on validation failure. Since this isn't wrapped in Effect.try, any validation error becomes an uncaught exception (defect) instead of a typed ProviderReplayNdjsonParseError. This breaks the function's declared error contract. Consider wrapping the call in Effect.try to catch the exception and convert it to the declared error type.

- return decodeTranscript({- ...metadata,- entries,- });+ return yield* Effect.try({+ try: () =>+ decodeTranscript({+ ...metadata,+ entries,+ }),+ catch: (cause) =>+ new ProviderReplayNdjsonLineParseError({+ lineNumber: lines.length,+ line: "<transcript validation>",+ cause,+ }),+ });
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts around lines 116-120:
The call to `decodeTranscript` at line 116 invokes `Schema.decodeUnknownSync`, which throws on validation failure. Since this isn't wrapped in `Effect.try`, any validation error becomes an uncaught exception (defect) instead of a typed `ProviderReplayNdjsonParseError`. This breaks the function's declared error contract. Consider wrapping the call in `Effect.try` to catch the exception and convert it to the declared error type.
Evidence trail:
apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts lines 50-53: `decodeTranscript = Schema.decodeUnknownSync(ProviderReplayTranscript)` — throws on failure.
Line 116-118: `return decodeTranscript({...metadata, entries})` — called directly inside Effect.gen without Effect.try wrapper.
Lines 55-68 (`parseReplayRecord`): same pattern but correctly wrapped in `Effect.try`.
Line 80: function declares error type `ProviderReplayNdjsonParseError`.
packages/contracts/src/orchestrationV2.ts lines 1561-1568: `ProviderReplayTranscript` schema with `TrimmedNonEmptyString` fields that can fail validation.

Comment threadapps/server/src/orchestration-v2/ProviderAdapterRegistry.ts Outdated
Comment threadpackages/client-runtime/src/wsRpcClient.ts Outdated
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/EventSink.ts
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 1, 2026
…der adapters (t3-29f.6)
Assessment of upstream PR pingdotgg#2829 (pingdotgg/t3code) from juliusmarminge:
WIP wire orchestration v2 provider adapters with Codex and Claude adapters,
event sourcing, provider session management, and replay testkit.
Relevance to target issues:
- pingdotgg#2838 (session resume): HIGH — ProviderSessionManager persists session
IDs and separates startSession/resumeSession operations
- pingdotgg#2778 (subagent hang): MEDIUM — ProviderEventIngestor provides
infrastructure to forward permission events, but UI plumbing not yet wired
- pingdotgg#2886 (thread stuck working): HIGH — event-sourced projections replace
mutable state flags, eliminating sticky "working" states
The PR is a draft (34 commits, not merged). No OpenCode ACP adapter
exists yet in v2 — OpenCode would need its own adapter wired into the
ProviderAdapterRegistry. Recommend watching for merge and adding an
OpenCode adapter post-merge.
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
The upstream PR pingdotgg#2829 added orchestrationV2 methods to the WsRpcClient
interface. The test mock in service.threadSubscriptions.test.ts was
missing the orchestrationV2 property, causing a typecheck failure:
'Property orchestrationV2 is missing in type...'
Added orchestrationV2 mock with dispatchCommand, getThreadProjection,
subscribeShell, and subscribeThread as vi.fn() stubs.
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
The upstream PR pingdotgg#2829 targets a newer Effect version than our fork's
pinned effect@4.0.0-beta.73. Fixes:
- Replace Random.nextUUIDv4 with Crypto.randomUUIDv4 (beta.73 API)
- Fix deterministic Service tag keys to match fork convention
(include file path segments; e.g. Adapters/ClaudeAdapterV2/...)
- Replace Schema.decodeSync with Schema.decodeUnknownEffect inside
Effect.gen generators (tsgo schemaSyncInEffect rule)
- Replace inline Schema.encodeUnknownSync with module-level wrappers
to avoid schemaSyncInEffect rule inside generators
@github-actions

github-actionsBot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🚀 Expo continuous deployment is ready!

  • Project → t3-code
  • Platforms → android, ios
  • Scheme → t3code-preview
🤖 Android🍎 iOS
Fingerprintfe5a51f2e189da69dfc4c2cd458e6cfb5fdff2eaae3bd597809dfd7771d0898f735d172973d4c1c8
Build DetailsBuild Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
App version: 0.1.0
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Build Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
App version: 0.1.0
Git commit: eea0dcae4150df8341606520c074dc651ae7c00a
Update DetailsUpdate Permalink
DetailsBranch: pr-2829
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Update Permalink
DetailsBranch: pr-2829
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Update QR

Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
Comment threadapps/server/src/ws.ts
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
@juliusmarmingejuliusmarminge changed the title WIP: wire orchestration v2 provider adaptersfeat(orchestrator): introduce new orchestratorJun 14, 2026
Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
Comment on lines +109 to +128
Effect.gen(function* () {
const threadId = payloadInput.threadId ?? input.threadId;
const eventId = yield* idAllocator.allocate.event({
threadId,
providerSessionId: input.providerSessionId,
});
const occurredAt = yield* DateTime.now;
return yield* Schema.decodeUnknownEffect(OrchestrationV2DomainEvent)(
compactUndefined({
id: eventId,
type: payloadInput.type,
threadId,
runId: payloadInput.runId ?? input.runId,
nodeId: payloadInput.nodeId ?? input.nodeId,
provider: input.event.provider,
rawEventId: input.rawEventId,
occurredAt,
payload: payloadInput.payload,
}),
);

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.

🟡 Mediumorchestration-v2/ProviderEventIngestor.ts:109

In makeDomainEvent, the ?? operator on lines 121 and 123 treats explicit null as equivalent to undefined, causing payloadInput.runId ?? input.runId to fall back to input.runId when payloadInput.runId is explicitly null. Since the type is readonly runId?: RunId | null, this means explicit null values from the caller (e.g., input.event.node.runId being null on line 172) are incorrectly overwritten instead of preserved. Consider using === undefined checks like lines 161-162 and 210-211, or use payloadInput.runId === undefined ? input.runId : payloadInput.runId.

 const threadId = payloadInput.threadId ?? input.threadId;
- const runId = payloadInput.runId ?? input.runId;- const nodeId = payloadInput.nodeId ?? input.nodeId;+ const runId = payloadInput.runId === undefined ? input.runId : payloadInput.runId;+ const nodeId = payloadInput.nodeId === undefined ? input.nodeId : payloadInput.nodeId;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/ProviderEventIngestor.ts around lines 109-128:
In `makeDomainEvent`, the `??` operator on lines 121 and 123 treats explicit `null` as equivalent to `undefined`, causing `payloadInput.runId ?? input.runId` to fall back to `input.runId` when `payloadInput.runId` is explicitly `null`. Since the type is `readonly runId?: RunId | null`, this means explicit `null` values from the caller (e.g., `input.event.node.runId` being `null` on line 172) are incorrectly overwritten instead of preserved. Consider using `=== undefined` checks like lines 161-162 and 210-211, or use `payloadInput.runId === undefined ? input.runId : payloadInput.runId`.
Evidence trail:
apps/server/src/orchestration-v2/ProviderEventIngestor.ts lines 105-106 (payloadInput type with `RunId | null`), line 121 (`runId: payloadInput.runId ?? input.runId`), line 122 (`nodeId: payloadInput.nodeId ?? input.nodeId`), lines 161-162 and 210-211 (codebase uses `=== undefined` pattern elsewhere). packages/contracts/src/orchestrationV2.ts line 358 (`runId: Schema.NullOr(RunId)` on ExecutionNode - confirms null is a valid value), line 857 (`runId: Schema.optional(RunId)` on EventBase - domain event uses optional/undefined, not null). apps/server/src/orchestration-v2/ProviderEventIngestor.ts lines 80-81 (compactUndefined only strips undefined, not null).

Comment threadapps/server/src/orchestration-v2/Orchestrator.ts
Comment threadapps/web/src/routes/debug.orchestration-v2.tsx Outdated
Comment threadapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 79031a1 to 4e68dcbCompareJune 14, 2026 23:55
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 4e68dcb to c7539b9CompareJune 17, 2026 07:30
Comment threadapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/ProviderSessionManager.ts Outdated
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts Outdated
Comment threadapps/server/src/mcp/OrchestratorMcpService.ts Outdated
Comment threadapps/server/src/ws.ts Outdated
function nativeThreadId(provider: ProviderKind, thread: OrchestrationV2ProviderThread): string {
const id = thread.nativeThreadRef?.nativeId;
if (id === null || id === undefined || id.trim().length === 0) {
throw new ProviderAdapterProtocolError({

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.

🟡 MediumAdapters/AcpAdapterV2.ts:271

When nativeThreadId is called inside Effect.gen generators (e.g., lines 899, 1813), the thrown ProviderAdapterProtocolError becomes an untyped defect instead of a typed failure. This bypasses Effect.mapError and other typed error handlers, causing the error to propagate as an unexpected defect. Consider converting nativeThreadId to return Effect<string, ProviderAdapterProtocolError> and yielding it at each call site, or inlining the validation with yield* new ProviderAdapterProtocolError(...) so the failure is properly typed.

Also found in 1 other location(s)

apps/server/src/orchestration-v2/ThreadManagementService.ts:278

The statement return yield* managementError(...) cannot work correctly because managementError() returns a ThreadManagementError instance, not an Effect. The yield* operator in Effect.gen expects an Effect value. This should be return yield* Effect.fail(managementError(...)). The correct pattern is demonstrated elsewhere in this file (lines 241-246) where Effect.fail(managementError(...)) is properly used. This same bug pattern repeats at lines 291, 333, 357, 374, 390, 406, and 427.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts around line 271:
When `nativeThreadId` is called inside `Effect.gen` generators (e.g., lines 899, 1813), the thrown `ProviderAdapterProtocolError` becomes an untyped defect instead of a typed failure. This bypasses `Effect.mapError` and other typed error handlers, causing the error to propagate as an unexpected defect. Consider converting `nativeThreadId` to return `Effect<string, ProviderAdapterProtocolError>` and yielding it at each call site, or inlining the validation with `yield* new ProviderAdapterProtocolError(...)` so the failure is properly typed.
Evidence trail:
1. nativeThreadId function with throw: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts lines 268-277
2. Call site inside Effect.gen: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 899
3. Call site inside Effect.gen: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 1813
4. Correct yield* pattern for comparison: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 1808
5. ProviderAdapterProtocolError class definition: apps/server/src/orchestration-v2/ProviderAdapter.ts lines 316-327
6. Effect.gen implementation delegating to fromIteratorUnsafe: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 1104-1125
7. fromIteratorUnsafe calling iter.next() without try/catch: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 1285-1307
8. FiberImpl.runLoop catch block converting thrown errors to exitDie: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 646-650
9. die = exitDie producing Effect<never> (untyped): https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts line 947
Also found in 1 other location(s):
- apps/server/src/orchestration-v2/ThreadManagementService.ts:278 -- The statement `return yield* managementError(...)` cannot work correctly because `managementError()` returns a `ThreadManagementError` instance, not an `Effect`. The `yield*` operator in `Effect.gen` expects an Effect value. This should be `return yield* Effect.fail(managementError(...))`. The correct pattern is demonstrated elsewhere in this file (lines 241-246) where `Effect.fail(managementError(...))` is properly used. This same bug pattern repeats at lines 291, 333, 357, 374, 390, 406, and 427.

juliusmarmingeand others added 23 commits September 1, 2026 16:33
R12 follow-up
Insert persistent feedback blocks by their timestamp within the canonical timeline while preserving projected row order. Keep real optimistic sends appended and suppress duplicate local messages already committed by the server.
Implemented by GPT-5.6 Sol via Codex.
R20 follow-up
Give inspector reasoning markdown its projected source thread and retain the explicit environment fallback for proposed plans without a thread reference. Workspace links and images now resolve through their owning environment after removal of the active-environment fallback.
Implemented by GPT-5.6 Sol via Codex.
Read the inclusive cursor, a full history page, and a look-behind row so older history does not terminate after one page.
Finding: P01 pagination termination
Model: GPT-5.6 Sol via Codex
Cancel pending SDK requests before aborting the native session. Preserve per-admission cancellation state and treat stopped initial prompts as interruption instead of provider failure.
Finding: R14 prompt cancellation
Model: GPT-5.6 Sol via Codex
Keep the original cursor owner through ancestor traversal and preserve the history budget across empty intermediate forks. Verify exact paged history against the complete nested projection.
Finding: P01 nested lineage
Model: GPT-5.6 Sol via Codex
Retain pending admission after transient status failures and use one generation-owned retry worker. Ignore stale timers and duplicate evidence so older prompts cannot finish newer steering.
Finding: R14 status reconciliation
Model: GPT-5.6 Sol via Codex
Keep hidden local and inherited rows from consuming history pages. Preserve stop-request dependencies, source-run cutoffs, and imported history while loading related metadata from the selected cohort and using indexed watermark lookups.
Finding: P01 bounded history visibility
Model: GPT-5.6 Sol via Codex
Adapt grouped tool summaries and the floating working timer to V2 run, attempt, and queue state. Bring over the composer, keyboard, and disclosure transitions while retaining the V2 activity inspector and queue controls.
Keep OV2 web composer and grouping behavior intact; share only the existing command label parser with mobile.
Restores main features dropped by the policy replay: #8569 theme wiring,
settings search rework, #8803 workspace-mutation refresh (v2-adapted),
video + image previews (web and mobile, v2-adapted), #8862 Expo glass,
and the round's docs. Timeline thinking rows (#8984) stay on the v2
work-live system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v2 equivalents of main's #8984 and #8922: a "Working for ..." header
anchors the active run, the trailing live tool row survives between
actions in past tense instead of vanishing, and a shimmering Thinking
row marks reasoning gaps. During workspace preparation the header shows
"Setting up worktree..." (driven by the local dispatch flag or the v2
run's preparing status, so remote viewers see it too), the composer
footer span is gone, and draft promotion waits until the run starts or
startup fails instead of navigating mid-preparation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adopts the round's main features into the v2 architecture: the #9023
media rework (streamed videos, media-file assets, protocol-relative
links), #9098 shared live-activity row folded into the v2 working and
thinking rows, the #9084/#9078 Claude model catalog for v2 consumers,
a native #9005 OpenCode child-session abort in the v2 adapter, #9013's
landed LegendList patch, and per-environment sidebar provider entries.
For #8600 the server-side pieces land, but auto-settle evaluation stays
client-side (reading the new server-owned settings) until the v2
orchestrator grows its own settlement reactor; main's v1-only reactor
and coalescer additions are dropped with the rest of the v1 path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

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.

composerVideos and composerOtherFiles are computed but never rendered — the attachment list below still maps composerFiles (line 4034) into the generic FileIcon row, so ComposerVideoThumbnail (line 176), the new PlayIcon import (line 305), and attachVideoThumbnail / isPreviewableComposerVideo have no live consumer here. The result is a split treatment for the same attachment: a staged video is a plain file row in the composer, while the sent message renders it as a playable tile (MessagesTimeline.tsxuserVideos). The three unused locals will also trip unused-symbol checks.

Smallest fix: render composerVideos as thumbnail tiles (ComposerVideoThumbnail + buildExpandedImagePreview, matching the timeline tile) and feed the existing file-row list from composerOtherFiles; if the composer treatment is not landing in this PR, drop the two locals, ComposerVideoThumbnail, and the PlayIcon import instead so the file does not carry a half-wired path.

Posted via Macroscope — UI Consistency

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.

composerVideos and composerOtherFiles are computed but never rendered — the attachment list below still maps composerFiles (line 4034) into the generic FileIcon row, so ComposerVideoThumbnail (line 176), the new PlayIcon import (line 305), and attachVideoThumbnail / isPreviewableComposerVideo have no live consumer here. The result is a split treatment for the same attachment: a staged video is a plain file row in the composer, while the sent message renders it as a playable tile (MessagesTimeline.tsxuserVideos). The three unused locals will also trip unused-symbol checks.

Smallest fix: render composerVideos as thumbnail tiles (ComposerVideoThumbnail + buildExpandedImagePreview, matching the timeline tile) and feed the existing file-row list from composerOtherFiles; if the composer treatment is not landing in this PR, drop the two locals, ComposerVideoThumbnail, and the PlayIcon import instead so the file does not carry a half-wired path.

Posted via Macroscope — UI Consistency

!hasContent &&
(props.selectedThread.session?.status === "running" ||
props.selectedThread.session?.status === "starting");
const showStopAction = props.canStopThread;

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.

Stop hides send while composing

High Severity

showStopAction now follows canStopThread alone and no longer requires an empty composer. While a run is interruptible, typed text or attachments still replace Send/Queue with Stop, so follow-ups cannot be sent or queued until the run is interrupted.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

selectedThreadFeed,
selectedThreadKey,
]);
}, [props.onSendMessage, selectedThreadKey]);

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.

Send always re-anchors the feed

Medium Severity

handleSendMessage now always sets anchorMessageId to the just-sent id. The previous first-message-only policy in resolveThreadFeedSubmissionAnchor is no longer applied, so follow-ups and second outbox sends retarget the live-follow scroll.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

// Dictation keeps that focus while the composer switches to its compact pill.
const composerBottomInset = (
Platform.OS === "android" ? isKeyboardVisible : composerExpanded || composerFocused
)

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.

iOS composer inset ignores focus

Medium Severity

iOS composerBottomInset now keys only on composerExpanded, and the editor no longer updates composerFocused. Focusing the collapsed pill still applies home-indicator padding above the keyboard, leaving a gap between the composer and the IME.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

…ator
Ports #8600's server-owned settlement to orchestration v2 instead of
keeping client-side evaluation. A ThreadSettlementService sweep runs at
startup, on auto-settle settings changes, and once per minute: it
evaluates inactivity and merged or closed pull requests over v2 thread
shells and dispatches the new guarded thread.auto-settle command, which
rejects threads that changed after the sweep's snapshot or carry any
explicit override, then reuses the orchestrator's settle lifecycle.
With the server deciding, the clients drop their effectiveSettled
evaluation and partition on the persisted settledOverride like main.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@macroscopeapp

Copy link
Copy Markdown
Contributor

apps/server/src/cli/app.ts:232-239

(Posted at PR level: an inline comment could not be anchored on this file in the current diff.)

The Effect.tryPromisecatch in runAppCommand collapses six semantically distinct failures into one error whose distinction survives only as an English sentence inside cause: Schema.Defect() — response timeout, oversized response, non-JSON response, schema-invalid response, request-id mismatch, and "connection closed" all become DesktopAppUnreachableError, whose message asserts the desktop app could not be reached and tells the user to start it. In the four response-shaped cases the app did answer, so that caller-visible message is wrong, and cli/app.test.ts asserts on the prose (cause: { message: "The desktop app response is invalid." }), which makes it behavior rather than diagnostics.

The conventions ask that failures be distinguished by stable structural attributes with the message derived only from them, and that a translation boundary wrap only genuinely lower-level failures rather than flatten known ones. Consider having sendDesktopAppActivationRequest reject with tagged errors (or return a tagged result) so the CLI can keep DesktopAppUnreachableError for the connect-level ENOENT/ECONNREFUSED case its message describes, and add e.g. DesktopAppResponseInvalidError { requestId, workspaceRoot, byteLength } and DesktopAppResponseTimeoutError { requestId, timeoutMs } for the rest, each keeping the real socket error as cause where one exists.

Posted via Macroscope — Effect Service Conventions

Posted via Macroscope — Effect Service Conventions

@macroscopeapp

Copy link
Copy Markdown
Contributor

apps/server/src/cli/app.ts:232-239

(Posted at PR level: an inline comment could not be anchored on this file in the current diff.)

The Effect.tryPromisecatch in runAppCommand collapses six semantically distinct failures into one error whose distinction survives only as an English sentence inside cause: Schema.Defect() — response timeout, oversized response, non-JSON response, schema-invalid response, request-id mismatch, and "connection closed" all become DesktopAppUnreachableError, whose message asserts the desktop app could not be reached and tells the user to start it. In the four response-shaped cases the app did answer, so that caller-visible message is wrong, and cli/app.test.ts asserts on the prose (cause: { message: "The desktop app response is invalid." }), which makes it behavior rather than diagnostics.

The conventions ask that failures be distinguished by stable structural attributes with the message derived only from them, and that a translation boundary wrap only genuinely lower-level failures rather than flatten known ones. Consider having sendDesktopAppActivationRequest reject with tagged errors (or return a tagged result) so the CLI can keep DesktopAppUnreachableError for the connect-level ENOENT/ECONNREFUSED case its message describes, and add e.g. DesktopAppResponseInvalidError { requestId, workspaceRoot, byteLength } and DesktopAppResponseTimeoutError { requestId, timeoutMs } for the rest, each keeping the real socket error as cause where one exists.

Posted via Macroscope — Effect Service Conventions

Posted via Macroscope — Effect Service Conventions

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

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

@@ -2085,12 +2062,9 @@ export default function Sidebar() {
// memo exactly at the next wake boundary.
void snoozeWakeTick;
const preciseNow = new Date().toISOString();
const visible = threads.filter(

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.

Web sidebar pinned threads partition completely broken

High Severity

The partition loop declares const pinned: EnvironmentThreadShell[] = [] at line 2068 but never pushes anything into it. The old code had an else if (thread.pinnedAt != null) { pinned.push(thread); } branch between the snoozed and settled checks, but it was removed in this refactor. All pinned threads now fall into active instead. The mobile equivalent in threadListV2.ts correctly retains else if (thread.pinnedAt != null) { pinned.push(thread); } at line 421. This means pinnedThreads is always empty, the pinned section never renders, DnD reordering is inert, and reorderablePinnedKeys is an empty set.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

thread.session?.status === "running" &&
thread.session.activeTurnId != null
) {
if (action === "archive" && threadRuntimeIsActive(thread.runtime)) {

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.

Archive guard stricter than old threadCanArchive behavior

Medium Severity

The comment says "Archive keeps its original, narrower guard" but threadRuntimeIsActive is actually wider (more restrictive) than the replaced threadCanArchive. threadCanArchive allowed archiving queued threads without an activeRunId and all waiting threads; threadRuntimeIsActive blocks both. The old threadCanArchive is still imported at line 21 but now unused, confirming a mistaken substitution rather than an intentional tightening.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(orchestrator): introduce new orchestrator - #2829

Open
juliusmarminge wants to merge 330 commits into
mainfrom
t3code/codex-turn-mapping
Open

feat(orchestrator): introduce new orchestrator#2829
juliusmarminge wants to merge 330 commits into
mainfrom
t3code/codex-turn-mapping

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 27, 2026

Copy link
Copy Markdown
Member

Summary

  • wire orchestration V2 provider adapter registry/factory flow for Codex and Claude provider instances
  • add Claude replay/query primitives, native fork/rollback fixtures, subagent fixture coverage, and provider replay harness updates
  • update debugger model/provider picker and improve user-facing orchestration errors

Validation

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test -- src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts -t claudeAgent
  • bun run test -- src/orchestration-v2/testkit/ClaudeReplayFixtures.integration.test.ts
  • bun run test -- src/orchestration-v2/testkit/ThreadFork.integration.test.ts -t Claude

Notes

  • Draft PR for review of current branch state. Codex all-provider replay still needs schema alignment with latest app-server behavior before it can be treated as a full-suite signal.

Closes

Verified against the branch with code/commit evidence.

High confidence

Closes#4952
Closes#4873
Closes#4775
Closes#4795
Closes#4710
Closes#4668
Closes#4619
Closes#4584
Closes#4561
Closes#4713
Closes#4198
Closes#4452
Closes#3797
Closes#4232
Closes#3666
Closes#3580
Closes#2785
Closes#2789
Closes#3138
Closes#1404
Closes#231
Closes#216

Medium confidence (under review)

Closes#4568
Closes#4766
Closes#4495
Closes#4456
Closes#4399
Closes#3744
Closes#2921
Closes#3624
Closes#3149
Closes#2336
Closes#538
Closes#2173
Closes#2065

Note

Introduce new Orchestration V2 runtime with provider adapters, persistence, and client support

  • Adds a complete Orchestration V2 server runtime: event store, projection store, event sink, effect outbox/worker, ID allocator, command policy, thread management/launch/lifecycle/fork/settlement services, checkpoint capture/rollback, provider session manager, runtime recovery, and context handoff services across orchestration-v2/
  • Adds V2 provider adapters for Codex, Claude, Cursor, Grok, OpenCode, and ACP Registry, plus a provider adapter registry and driver framework with replay testkits for each adapter
  • Adds persistence migrations 044–052 for V2 event/projection tables, effect outbox, provider-session bindings, thread-launch workflows, application event sourcing, scheduled tasks, and legacy V1 import state
  • Adds MCP orchestration and worktree toolkits, a project service with enrichment, scheduled-task service, HTTP API layer, and a legacy V1 thread importer that reconciles old threads into V2 events
  • Updates web and mobile clients to consume V2 thread projections, shell snapshots, runtime status, queued-run management, thread relationships, history paging, and scheduled-task controls
  • Risk: legacy session/latestTurn fields are replaced by runtime/latestRun across web and mobile thread state; out-of-tree consumers of the old shell snapshot schema or V1 RPC methods will break. Cache schema version bumped to 3, discarding V1 cache envelopes on decode failure. Orchestration protocol version 2 is now required for client connections.

Macroscope summarized 6081201.


Note

High Risk
Mobile thread persistence, archive/stop guards, and approval/user-input handling now depend on V2 runtime semantics and a bumped cache schema, alongside removal of PR transfer regression reporting.

Overview
Removes the automated “thread transfer impact” PR comment pipeline by deleting the thread-transfer-report workflow and its trusted publisher scripts/tests, while CI still may emit transfer budget artifacts from the main workflow.

Mobile is wired to orchestration V2 end-to-end: SQLite shell/thread caches now use the shared ORCHESTRATION_CACHE_SCHEMA_VERSION and V2 snapshot shapes; the connection runtime loads bounded thread history plus a history controller instead of the old unbounded loader. Thread list/detail flows switch from legacy session/turn fields to runtime summaries (thread.runtime, threadRuntimeIsActive, dedicated archive eligibility), runtime request IDs for approvals and user input (including read-only UI when responseCapability is not live), and checkpoint/review data derived from thread projections rather than embedded checkpoint lists.

Thread UX additions and polish include visit watermark dispatch on open, a queue control and relationships banner on the feed, progressive history controls, a thread activity inspector with rollback, shared brand mark assets, extra adaptive theme tokens, and copy tweaks (“coding runtime”). CI installs build-essential so ACP process-tree pthread fixtures compile instead of soft-skipping. Smaller follow-ons: desktop environment user-data dir test expectations, Tailscale Effect typing, default persistComposerContextStrip, README appearance doc link, and marketing Cursor harness label.

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

@coderabbitai

coderabbitaiBot commented May 27, 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: e3efadfb-5f39-4c7c-970d-8aade3f16f07

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
  • Commit unit tests in branch t3code/codex-turn-mapping

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels May 27, 2026
Comment threadapps/server/src/orchestration-v2/EventStore.ts Outdated
Comment on lines +116 to +120
return decodeTranscript({
...metadata,
entries,
});
});

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.

🟢 Lowtestkit/ReplayTranscriptNdjson.ts:116

The call to decodeTranscript at line 116 invokes Schema.decodeUnknownSync, which throws on validation failure. Since this isn't wrapped in Effect.try, any validation error becomes an uncaught exception (defect) instead of a typed ProviderReplayNdjsonParseError. This breaks the function's declared error contract. Consider wrapping the call in Effect.try to catch the exception and convert it to the declared error type.

- return decodeTranscript({- ...metadata,- entries,- });+ return yield* Effect.try({+ try: () =>+ decodeTranscript({+ ...metadata,+ entries,+ }),+ catch: (cause) =>+ new ProviderReplayNdjsonLineParseError({+ lineNumber: lines.length,+ line: "<transcript validation>",+ cause,+ }),+ });
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts around lines 116-120:
The call to `decodeTranscript` at line 116 invokes `Schema.decodeUnknownSync`, which throws on validation failure. Since this isn't wrapped in `Effect.try`, any validation error becomes an uncaught exception (defect) instead of a typed `ProviderReplayNdjsonParseError`. This breaks the function's declared error contract. Consider wrapping the call in `Effect.try` to catch the exception and convert it to the declared error type.
Evidence trail:
apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts lines 50-53: `decodeTranscript = Schema.decodeUnknownSync(ProviderReplayTranscript)` — throws on failure.
Line 116-118: `return decodeTranscript({...metadata, entries})` — called directly inside Effect.gen without Effect.try wrapper.
Lines 55-68 (`parseReplayRecord`): same pattern but correctly wrapped in `Effect.try`.
Line 80: function declares error type `ProviderReplayNdjsonParseError`.
packages/contracts/src/orchestrationV2.ts lines 1561-1568: `ProviderReplayTranscript` schema with `TrimmedNonEmptyString` fields that can fail validation.

Comment threadapps/server/src/orchestration-v2/ProviderAdapterRegistry.ts Outdated
Comment threadpackages/client-runtime/src/wsRpcClient.ts Outdated
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/EventSink.ts
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 1, 2026
…der adapters (t3-29f.6)
Assessment of upstream PR pingdotgg#2829 (pingdotgg/t3code) from juliusmarminge:
WIP wire orchestration v2 provider adapters with Codex and Claude adapters,
event sourcing, provider session management, and replay testkit.
Relevance to target issues:
- pingdotgg#2838 (session resume): HIGH — ProviderSessionManager persists session
IDs and separates startSession/resumeSession operations
- pingdotgg#2778 (subagent hang): MEDIUM — ProviderEventIngestor provides
infrastructure to forward permission events, but UI plumbing not yet wired
- pingdotgg#2886 (thread stuck working): HIGH — event-sourced projections replace
mutable state flags, eliminating sticky "working" states
The PR is a draft (34 commits, not merged). No OpenCode ACP adapter
exists yet in v2 — OpenCode would need its own adapter wired into the
ProviderAdapterRegistry. Recommend watching for merge and adding an
OpenCode adapter post-merge.
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
The upstream PR pingdotgg#2829 added orchestrationV2 methods to the WsRpcClient
interface. The test mock in service.threadSubscriptions.test.ts was
missing the orchestrationV2 property, causing a typecheck failure:
'Property orchestrationV2 is missing in type...'
Added orchestrationV2 mock with dispatchCommand, getThreadProjection,
subscribeShell, and subscribeThread as vi.fn() stubs.
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
The upstream PR pingdotgg#2829 targets a newer Effect version than our fork's
pinned effect@4.0.0-beta.73. Fixes:
- Replace Random.nextUUIDv4 with Crypto.randomUUIDv4 (beta.73 API)
- Fix deterministic Service tag keys to match fork convention
(include file path segments; e.g. Adapters/ClaudeAdapterV2/...)
- Replace Schema.decodeSync with Schema.decodeUnknownEffect inside
Effect.gen generators (tsgo schemaSyncInEffect rule)
- Replace inline Schema.encodeUnknownSync with module-level wrappers
to avoid schemaSyncInEffect rule inside generators
@github-actions

github-actionsBot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🚀 Expo continuous deployment is ready!

  • Project → t3-code
  • Platforms → android, ios
  • Scheme → t3code-preview
🤖 Android🍎 iOS
Fingerprintfe5a51f2e189da69dfc4c2cd458e6cfb5fdff2eaae3bd597809dfd7771d0898f735d172973d4c1c8
Build DetailsBuild Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
App version: 0.1.0
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Build Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
App version: 0.1.0
Git commit: eea0dcae4150df8341606520c074dc651ae7c00a
Update DetailsUpdate Permalink
DetailsBranch: pr-2829
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Update Permalink
DetailsBranch: pr-2829
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Update QR

Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
Comment threadapps/server/src/ws.ts
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
@juliusmarmingejuliusmarminge changed the title WIP: wire orchestration v2 provider adaptersfeat(orchestrator): introduce new orchestratorJun 14, 2026
Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
Comment on lines +109 to +128
Effect.gen(function* () {
const threadId = payloadInput.threadId ?? input.threadId;
const eventId = yield* idAllocator.allocate.event({
threadId,
providerSessionId: input.providerSessionId,
});
const occurredAt = yield* DateTime.now;
return yield* Schema.decodeUnknownEffect(OrchestrationV2DomainEvent)(
compactUndefined({
id: eventId,
type: payloadInput.type,
threadId,
runId: payloadInput.runId ?? input.runId,
nodeId: payloadInput.nodeId ?? input.nodeId,
provider: input.event.provider,
rawEventId: input.rawEventId,
occurredAt,
payload: payloadInput.payload,
}),
);

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.

🟡 Mediumorchestration-v2/ProviderEventIngestor.ts:109

In makeDomainEvent, the ?? operator on lines 121 and 123 treats explicit null as equivalent to undefined, causing payloadInput.runId ?? input.runId to fall back to input.runId when payloadInput.runId is explicitly null. Since the type is readonly runId?: RunId | null, this means explicit null values from the caller (e.g., input.event.node.runId being null on line 172) are incorrectly overwritten instead of preserved. Consider using === undefined checks like lines 161-162 and 210-211, or use payloadInput.runId === undefined ? input.runId : payloadInput.runId.

 const threadId = payloadInput.threadId ?? input.threadId;
- const runId = payloadInput.runId ?? input.runId;- const nodeId = payloadInput.nodeId ?? input.nodeId;+ const runId = payloadInput.runId === undefined ? input.runId : payloadInput.runId;+ const nodeId = payloadInput.nodeId === undefined ? input.nodeId : payloadInput.nodeId;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/ProviderEventIngestor.ts around lines 109-128:
In `makeDomainEvent`, the `??` operator on lines 121 and 123 treats explicit `null` as equivalent to `undefined`, causing `payloadInput.runId ?? input.runId` to fall back to `input.runId` when `payloadInput.runId` is explicitly `null`. Since the type is `readonly runId?: RunId | null`, this means explicit `null` values from the caller (e.g., `input.event.node.runId` being `null` on line 172) are incorrectly overwritten instead of preserved. Consider using `=== undefined` checks like lines 161-162 and 210-211, or use `payloadInput.runId === undefined ? input.runId : payloadInput.runId`.
Evidence trail:
apps/server/src/orchestration-v2/ProviderEventIngestor.ts lines 105-106 (payloadInput type with `RunId | null`), line 121 (`runId: payloadInput.runId ?? input.runId`), line 122 (`nodeId: payloadInput.nodeId ?? input.nodeId`), lines 161-162 and 210-211 (codebase uses `=== undefined` pattern elsewhere). packages/contracts/src/orchestrationV2.ts line 358 (`runId: Schema.NullOr(RunId)` on ExecutionNode - confirms null is a valid value), line 857 (`runId: Schema.optional(RunId)` on EventBase - domain event uses optional/undefined, not null). apps/server/src/orchestration-v2/ProviderEventIngestor.ts lines 80-81 (compactUndefined only strips undefined, not null).

Comment threadapps/server/src/orchestration-v2/Orchestrator.ts
Comment threadapps/web/src/routes/debug.orchestration-v2.tsx Outdated
Comment threadapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 79031a1 to 4e68dcbCompareJune 14, 2026 23:55
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 4e68dcb to c7539b9CompareJune 17, 2026 07:30
Comment threadapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/ProviderSessionManager.ts Outdated
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts Outdated
Comment threadapps/server/src/mcp/OrchestratorMcpService.ts Outdated
Comment threadapps/server/src/ws.ts Outdated
function nativeThreadId(provider: ProviderKind, thread: OrchestrationV2ProviderThread): string {
const id = thread.nativeThreadRef?.nativeId;
if (id === null || id === undefined || id.trim().length === 0) {
throw new ProviderAdapterProtocolError({

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.

🟡 MediumAdapters/AcpAdapterV2.ts:271

When nativeThreadId is called inside Effect.gen generators (e.g., lines 899, 1813), the thrown ProviderAdapterProtocolError becomes an untyped defect instead of a typed failure. This bypasses Effect.mapError and other typed error handlers, causing the error to propagate as an unexpected defect. Consider converting nativeThreadId to return Effect<string, ProviderAdapterProtocolError> and yielding it at each call site, or inlining the validation with yield* new ProviderAdapterProtocolError(...) so the failure is properly typed.

Also found in 1 other location(s)

apps/server/src/orchestration-v2/ThreadManagementService.ts:278

The statement return yield* managementError(...) cannot work correctly because managementError() returns a ThreadManagementError instance, not an Effect. The yield* operator in Effect.gen expects an Effect value. This should be return yield* Effect.fail(managementError(...)). The correct pattern is demonstrated elsewhere in this file (lines 241-246) where Effect.fail(managementError(...)) is properly used. This same bug pattern repeats at lines 291, 333, 357, 374, 390, 406, and 427.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts around line 271:
When `nativeThreadId` is called inside `Effect.gen` generators (e.g., lines 899, 1813), the thrown `ProviderAdapterProtocolError` becomes an untyped defect instead of a typed failure. This bypasses `Effect.mapError` and other typed error handlers, causing the error to propagate as an unexpected defect. Consider converting `nativeThreadId` to return `Effect<string, ProviderAdapterProtocolError>` and yielding it at each call site, or inlining the validation with `yield* new ProviderAdapterProtocolError(...)` so the failure is properly typed.
Evidence trail:
1. nativeThreadId function with throw: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts lines 268-277
2. Call site inside Effect.gen: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 899
3. Call site inside Effect.gen: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 1813
4. Correct yield* pattern for comparison: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 1808
5. ProviderAdapterProtocolError class definition: apps/server/src/orchestration-v2/ProviderAdapter.ts lines 316-327
6. Effect.gen implementation delegating to fromIteratorUnsafe: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 1104-1125
7. fromIteratorUnsafe calling iter.next() without try/catch: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 1285-1307
8. FiberImpl.runLoop catch block converting thrown errors to exitDie: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 646-650
9. die = exitDie producing Effect<never> (untyped): https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts line 947
Also found in 1 other location(s):
- apps/server/src/orchestration-v2/ThreadManagementService.ts:278 -- The statement `return yield* managementError(...)` cannot work correctly because `managementError()` returns a `ThreadManagementError` instance, not an `Effect`. The `yield*` operator in `Effect.gen` expects an Effect value. This should be `return yield* Effect.fail(managementError(...))`. The correct pattern is demonstrated elsewhere in this file (lines 241-246) where `Effect.fail(managementError(...))` is properly used. This same bug pattern repeats at lines 291, 333, 357, 374, 390, 406, and 427.

juliusmarmingeand others added 23 commits September 1, 2026 16:33
R12 follow-up
Insert persistent feedback blocks by their timestamp within the canonical timeline while preserving projected row order. Keep real optimistic sends appended and suppress duplicate local messages already committed by the server.
Implemented by GPT-5.6 Sol via Codex.
R20 follow-up
Give inspector reasoning markdown its projected source thread and retain the explicit environment fallback for proposed plans without a thread reference. Workspace links and images now resolve through their owning environment after removal of the active-environment fallback.
Implemented by GPT-5.6 Sol via Codex.
Read the inclusive cursor, a full history page, and a look-behind row so older history does not terminate after one page.
Finding: P01 pagination termination
Model: GPT-5.6 Sol via Codex
Cancel pending SDK requests before aborting the native session. Preserve per-admission cancellation state and treat stopped initial prompts as interruption instead of provider failure.
Finding: R14 prompt cancellation
Model: GPT-5.6 Sol via Codex
Keep the original cursor owner through ancestor traversal and preserve the history budget across empty intermediate forks. Verify exact paged history against the complete nested projection.
Finding: P01 nested lineage
Model: GPT-5.6 Sol via Codex
Retain pending admission after transient status failures and use one generation-owned retry worker. Ignore stale timers and duplicate evidence so older prompts cannot finish newer steering.
Finding: R14 status reconciliation
Model: GPT-5.6 Sol via Codex
Keep hidden local and inherited rows from consuming history pages. Preserve stop-request dependencies, source-run cutoffs, and imported history while loading related metadata from the selected cohort and using indexed watermark lookups.
Finding: P01 bounded history visibility
Model: GPT-5.6 Sol via Codex
Adapt grouped tool summaries and the floating working timer to V2 run, attempt, and queue state. Bring over the composer, keyboard, and disclosure transitions while retaining the V2 activity inspector and queue controls.
Keep OV2 web composer and grouping behavior intact; share only the existing command label parser with mobile.
Restores main features dropped by the policy replay: #8569 theme wiring,
settings search rework, #8803 workspace-mutation refresh (v2-adapted),
video + image previews (web and mobile, v2-adapted), #8862 Expo glass,
and the round's docs. Timeline thinking rows (#8984) stay on the v2
work-live system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v2 equivalents of main's #8984 and #8922: a "Working for ..." header
anchors the active run, the trailing live tool row survives between
actions in past tense instead of vanishing, and a shimmering Thinking
row marks reasoning gaps. During workspace preparation the header shows
"Setting up worktree..." (driven by the local dispatch flag or the v2
run's preparing status, so remote viewers see it too), the composer
footer span is gone, and draft promotion waits until the run starts or
startup fails instead of navigating mid-preparation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adopts the round's main features into the v2 architecture: the #9023
media rework (streamed videos, media-file assets, protocol-relative
links), #9098 shared live-activity row folded into the v2 working and
thinking rows, the #9084/#9078 Claude model catalog for v2 consumers,
a native #9005 OpenCode child-session abort in the v2 adapter, #9013's
landed LegendList patch, and per-environment sidebar provider entries.
For #8600 the server-side pieces land, but auto-settle evaluation stays
client-side (reading the new server-owned settings) until the v2
orchestrator grows its own settlement reactor; main's v1-only reactor
and coalescer additions are dropped with the rest of the v1 path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

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.

composerVideos and composerOtherFiles are computed but never rendered — the attachment list below still maps composerFiles (line 4034) into the generic FileIcon row, so ComposerVideoThumbnail (line 176), the new PlayIcon import (line 305), and attachVideoThumbnail / isPreviewableComposerVideo have no live consumer here. The result is a split treatment for the same attachment: a staged video is a plain file row in the composer, while the sent message renders it as a playable tile (MessagesTimeline.tsxuserVideos). The three unused locals will also trip unused-symbol checks.

Smallest fix: render composerVideos as thumbnail tiles (ComposerVideoThumbnail + buildExpandedImagePreview, matching the timeline tile) and feed the existing file-row list from composerOtherFiles; if the composer treatment is not landing in this PR, drop the two locals, ComposerVideoThumbnail, and the PlayIcon import instead so the file does not carry a half-wired path.

Posted via Macroscope — UI Consistency

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.

composerVideos and composerOtherFiles are computed but never rendered — the attachment list below still maps composerFiles (line 4034) into the generic FileIcon row, so ComposerVideoThumbnail (line 176), the new PlayIcon import (line 305), and attachVideoThumbnail / isPreviewableComposerVideo have no live consumer here. The result is a split treatment for the same attachment: a staged video is a plain file row in the composer, while the sent message renders it as a playable tile (MessagesTimeline.tsxuserVideos). The three unused locals will also trip unused-symbol checks.

Smallest fix: render composerVideos as thumbnail tiles (ComposerVideoThumbnail + buildExpandedImagePreview, matching the timeline tile) and feed the existing file-row list from composerOtherFiles; if the composer treatment is not landing in this PR, drop the two locals, ComposerVideoThumbnail, and the PlayIcon import instead so the file does not carry a half-wired path.

Posted via Macroscope — UI Consistency

!hasContent &&
(props.selectedThread.session?.status === "running" ||
props.selectedThread.session?.status === "starting");
const showStopAction = props.canStopThread;

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.

Stop hides send while composing

High Severity

showStopAction now follows canStopThread alone and no longer requires an empty composer. While a run is interruptible, typed text or attachments still replace Send/Queue with Stop, so follow-ups cannot be sent or queued until the run is interrupted.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

selectedThreadFeed,
selectedThreadKey,
]);
}, [props.onSendMessage, selectedThreadKey]);

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.

Send always re-anchors the feed

Medium Severity

handleSendMessage now always sets anchorMessageId to the just-sent id. The previous first-message-only policy in resolveThreadFeedSubmissionAnchor is no longer applied, so follow-ups and second outbox sends retarget the live-follow scroll.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

// Dictation keeps that focus while the composer switches to its compact pill.
const composerBottomInset = (
Platform.OS === "android" ? isKeyboardVisible : composerExpanded || composerFocused
)

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.

iOS composer inset ignores focus

Medium Severity

iOS composerBottomInset now keys only on composerExpanded, and the editor no longer updates composerFocused. Focusing the collapsed pill still applies home-indicator padding above the keyboard, leaving a gap between the composer and the IME.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

…ator
Ports #8600's server-owned settlement to orchestration v2 instead of
keeping client-side evaluation. A ThreadSettlementService sweep runs at
startup, on auto-settle settings changes, and once per minute: it
evaluates inactivity and merged or closed pull requests over v2 thread
shells and dispatches the new guarded thread.auto-settle command, which
rejects threads that changed after the sweep's snapshot or carry any
explicit override, then reuses the orchestrator's settle lifecycle.
With the server deciding, the clients drop their effectiveSettled
evaluation and partition on the persisted settledOverride like main.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@macroscopeapp

Copy link
Copy Markdown
Contributor

apps/server/src/cli/app.ts:232-239

(Posted at PR level: an inline comment could not be anchored on this file in the current diff.)

The Effect.tryPromisecatch in runAppCommand collapses six semantically distinct failures into one error whose distinction survives only as an English sentence inside cause: Schema.Defect() — response timeout, oversized response, non-JSON response, schema-invalid response, request-id mismatch, and "connection closed" all become DesktopAppUnreachableError, whose message asserts the desktop app could not be reached and tells the user to start it. In the four response-shaped cases the app did answer, so that caller-visible message is wrong, and cli/app.test.ts asserts on the prose (cause: { message: "The desktop app response is invalid." }), which makes it behavior rather than diagnostics.

The conventions ask that failures be distinguished by stable structural attributes with the message derived only from them, and that a translation boundary wrap only genuinely lower-level failures rather than flatten known ones. Consider having sendDesktopAppActivationRequest reject with tagged errors (or return a tagged result) so the CLI can keep DesktopAppUnreachableError for the connect-level ENOENT/ECONNREFUSED case its message describes, and add e.g. DesktopAppResponseInvalidError { requestId, workspaceRoot, byteLength } and DesktopAppResponseTimeoutError { requestId, timeoutMs } for the rest, each keeping the real socket error as cause where one exists.

Posted via Macroscope — Effect Service Conventions

Posted via Macroscope — Effect Service Conventions

@macroscopeapp

Copy link
Copy Markdown
Contributor

apps/server/src/cli/app.ts:232-239

(Posted at PR level: an inline comment could not be anchored on this file in the current diff.)

The Effect.tryPromisecatch in runAppCommand collapses six semantically distinct failures into one error whose distinction survives only as an English sentence inside cause: Schema.Defect() — response timeout, oversized response, non-JSON response, schema-invalid response, request-id mismatch, and "connection closed" all become DesktopAppUnreachableError, whose message asserts the desktop app could not be reached and tells the user to start it. In the four response-shaped cases the app did answer, so that caller-visible message is wrong, and cli/app.test.ts asserts on the prose (cause: { message: "The desktop app response is invalid." }), which makes it behavior rather than diagnostics.

The conventions ask that failures be distinguished by stable structural attributes with the message derived only from them, and that a translation boundary wrap only genuinely lower-level failures rather than flatten known ones. Consider having sendDesktopAppActivationRequest reject with tagged errors (or return a tagged result) so the CLI can keep DesktopAppUnreachableError for the connect-level ENOENT/ECONNREFUSED case its message describes, and add e.g. DesktopAppResponseInvalidError { requestId, workspaceRoot, byteLength } and DesktopAppResponseTimeoutError { requestId, timeoutMs } for the rest, each keeping the real socket error as cause where one exists.

Posted via Macroscope — Effect Service Conventions

Posted via Macroscope — Effect Service Conventions

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

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

@@ -2085,12 +2062,9 @@ export default function Sidebar() {
// memo exactly at the next wake boundary.
void snoozeWakeTick;
const preciseNow = new Date().toISOString();
const visible = threads.filter(

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.

Web sidebar pinned threads partition completely broken

High Severity

The partition loop declares const pinned: EnvironmentThreadShell[] = [] at line 2068 but never pushes anything into it. The old code had an else if (thread.pinnedAt != null) { pinned.push(thread); } branch between the snoozed and settled checks, but it was removed in this refactor. All pinned threads now fall into active instead. The mobile equivalent in threadListV2.ts correctly retains else if (thread.pinnedAt != null) { pinned.push(thread); } at line 421. This means pinnedThreads is always empty, the pinned section never renders, DnD reordering is inert, and reorderablePinnedKeys is an empty set.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

thread.session?.status === "running" &&
thread.session.activeTurnId != null
) {
if (action === "archive" && threadRuntimeIsActive(thread.runtime)) {

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.

Archive guard stricter than old threadCanArchive behavior

Medium Severity

The comment says "Archive keeps its original, narrower guard" but threadRuntimeIsActive is actually wider (more restrictive) than the replaced threadCanArchive. threadCanArchive allowed archiving queued threads without an activeRunId and all waiting threads; threadRuntimeIsActive blocks both. The old threadCanArchive is still imported at line 21 but now unused, confirming a mistaken substitution rather than an intentional tightening.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(orchestrator): introduce new orchestrator - #2829

Open
juliusmarminge wants to merge 330 commits into
mainfrom
t3code/codex-turn-mapping
Open

feat(orchestrator): introduce new orchestrator#2829
juliusmarminge wants to merge 330 commits into
mainfrom
t3code/codex-turn-mapping

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 27, 2026

Copy link
Copy Markdown
Member

Summary

  • wire orchestration V2 provider adapter registry/factory flow for Codex and Claude provider instances
  • add Claude replay/query primitives, native fork/rollback fixtures, subagent fixture coverage, and provider replay harness updates
  • update debugger model/provider picker and improve user-facing orchestration errors

Validation

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test -- src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts -t claudeAgent
  • bun run test -- src/orchestration-v2/testkit/ClaudeReplayFixtures.integration.test.ts
  • bun run test -- src/orchestration-v2/testkit/ThreadFork.integration.test.ts -t Claude

Notes

  • Draft PR for review of current branch state. Codex all-provider replay still needs schema alignment with latest app-server behavior before it can be treated as a full-suite signal.

Closes

Verified against the branch with code/commit evidence.

High confidence

Closes#4952
Closes#4873
Closes#4775
Closes#4795
Closes#4710
Closes#4668
Closes#4619
Closes#4584
Closes#4561
Closes#4713
Closes#4198
Closes#4452
Closes#3797
Closes#4232
Closes#3666
Closes#3580
Closes#2785
Closes#2789
Closes#3138
Closes#1404
Closes#231
Closes#216

Medium confidence (under review)

Closes#4568
Closes#4766
Closes#4495
Closes#4456
Closes#4399
Closes#3744
Closes#2921
Closes#3624
Closes#3149
Closes#2336
Closes#538
Closes#2173
Closes#2065

Note

Introduce new Orchestration V2 runtime with provider adapters, persistence, and client support

  • Adds a complete Orchestration V2 server runtime: event store, projection store, event sink, effect outbox/worker, ID allocator, command policy, thread management/launch/lifecycle/fork/settlement services, checkpoint capture/rollback, provider session manager, runtime recovery, and context handoff services across orchestration-v2/
  • Adds V2 provider adapters for Codex, Claude, Cursor, Grok, OpenCode, and ACP Registry, plus a provider adapter registry and driver framework with replay testkits for each adapter
  • Adds persistence migrations 044–052 for V2 event/projection tables, effect outbox, provider-session bindings, thread-launch workflows, application event sourcing, scheduled tasks, and legacy V1 import state
  • Adds MCP orchestration and worktree toolkits, a project service with enrichment, scheduled-task service, HTTP API layer, and a legacy V1 thread importer that reconciles old threads into V2 events
  • Updates web and mobile clients to consume V2 thread projections, shell snapshots, runtime status, queued-run management, thread relationships, history paging, and scheduled-task controls
  • Risk: legacy session/latestTurn fields are replaced by runtime/latestRun across web and mobile thread state; out-of-tree consumers of the old shell snapshot schema or V1 RPC methods will break. Cache schema version bumped to 3, discarding V1 cache envelopes on decode failure. Orchestration protocol version 2 is now required for client connections.

Macroscope summarized 6081201.


Note

High Risk
Mobile thread persistence, archive/stop guards, and approval/user-input handling now depend on V2 runtime semantics and a bumped cache schema, alongside removal of PR transfer regression reporting.

Overview
Removes the automated “thread transfer impact” PR comment pipeline by deleting the thread-transfer-report workflow and its trusted publisher scripts/tests, while CI still may emit transfer budget artifacts from the main workflow.

Mobile is wired to orchestration V2 end-to-end: SQLite shell/thread caches now use the shared ORCHESTRATION_CACHE_SCHEMA_VERSION and V2 snapshot shapes; the connection runtime loads bounded thread history plus a history controller instead of the old unbounded loader. Thread list/detail flows switch from legacy session/turn fields to runtime summaries (thread.runtime, threadRuntimeIsActive, dedicated archive eligibility), runtime request IDs for approvals and user input (including read-only UI when responseCapability is not live), and checkpoint/review data derived from thread projections rather than embedded checkpoint lists.

Thread UX additions and polish include visit watermark dispatch on open, a queue control and relationships banner on the feed, progressive history controls, a thread activity inspector with rollback, shared brand mark assets, extra adaptive theme tokens, and copy tweaks (“coding runtime”). CI installs build-essential so ACP process-tree pthread fixtures compile instead of soft-skipping. Smaller follow-ons: desktop environment user-data dir test expectations, Tailscale Effect typing, default persistComposerContextStrip, README appearance doc link, and marketing Cursor harness label.

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

@coderabbitai

coderabbitaiBot commented May 27, 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: e3efadfb-5f39-4c7c-970d-8aade3f16f07

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
  • Commit unit tests in branch t3code/codex-turn-mapping

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels May 27, 2026
Comment threadapps/server/src/orchestration-v2/EventStore.ts Outdated
Comment on lines +116 to +120
return decodeTranscript({
...metadata,
entries,
});
});

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.

🟢 Lowtestkit/ReplayTranscriptNdjson.ts:116

The call to decodeTranscript at line 116 invokes Schema.decodeUnknownSync, which throws on validation failure. Since this isn't wrapped in Effect.try, any validation error becomes an uncaught exception (defect) instead of a typed ProviderReplayNdjsonParseError. This breaks the function's declared error contract. Consider wrapping the call in Effect.try to catch the exception and convert it to the declared error type.

- return decodeTranscript({- ...metadata,- entries,- });+ return yield* Effect.try({+ try: () =>+ decodeTranscript({+ ...metadata,+ entries,+ }),+ catch: (cause) =>+ new ProviderReplayNdjsonLineParseError({+ lineNumber: lines.length,+ line: "<transcript validation>",+ cause,+ }),+ });
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts around lines 116-120:
The call to `decodeTranscript` at line 116 invokes `Schema.decodeUnknownSync`, which throws on validation failure. Since this isn't wrapped in `Effect.try`, any validation error becomes an uncaught exception (defect) instead of a typed `ProviderReplayNdjsonParseError`. This breaks the function's declared error contract. Consider wrapping the call in `Effect.try` to catch the exception and convert it to the declared error type.
Evidence trail:
apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts lines 50-53: `decodeTranscript = Schema.decodeUnknownSync(ProviderReplayTranscript)` — throws on failure.
Line 116-118: `return decodeTranscript({...metadata, entries})` — called directly inside Effect.gen without Effect.try wrapper.
Lines 55-68 (`parseReplayRecord`): same pattern but correctly wrapped in `Effect.try`.
Line 80: function declares error type `ProviderReplayNdjsonParseError`.
packages/contracts/src/orchestrationV2.ts lines 1561-1568: `ProviderReplayTranscript` schema with `TrimmedNonEmptyString` fields that can fail validation.

Comment threadapps/server/src/orchestration-v2/ProviderAdapterRegistry.ts Outdated
Comment threadpackages/client-runtime/src/wsRpcClient.ts Outdated
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/EventSink.ts
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 1, 2026
…der adapters (t3-29f.6)
Assessment of upstream PR pingdotgg#2829 (pingdotgg/t3code) from juliusmarminge:
WIP wire orchestration v2 provider adapters with Codex and Claude adapters,
event sourcing, provider session management, and replay testkit.
Relevance to target issues:
- pingdotgg#2838 (session resume): HIGH — ProviderSessionManager persists session
IDs and separates startSession/resumeSession operations
- pingdotgg#2778 (subagent hang): MEDIUM — ProviderEventIngestor provides
infrastructure to forward permission events, but UI plumbing not yet wired
- pingdotgg#2886 (thread stuck working): HIGH — event-sourced projections replace
mutable state flags, eliminating sticky "working" states
The PR is a draft (34 commits, not merged). No OpenCode ACP adapter
exists yet in v2 — OpenCode would need its own adapter wired into the
ProviderAdapterRegistry. Recommend watching for merge and adding an
OpenCode adapter post-merge.
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
The upstream PR pingdotgg#2829 added orchestrationV2 methods to the WsRpcClient
interface. The test mock in service.threadSubscriptions.test.ts was
missing the orchestrationV2 property, causing a typecheck failure:
'Property orchestrationV2 is missing in type...'
Added orchestrationV2 mock with dispatchCommand, getThreadProjection,
subscribeShell, and subscribeThread as vi.fn() stubs.
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
The upstream PR pingdotgg#2829 targets a newer Effect version than our fork's
pinned effect@4.0.0-beta.73. Fixes:
- Replace Random.nextUUIDv4 with Crypto.randomUUIDv4 (beta.73 API)
- Fix deterministic Service tag keys to match fork convention
(include file path segments; e.g. Adapters/ClaudeAdapterV2/...)
- Replace Schema.decodeSync with Schema.decodeUnknownEffect inside
Effect.gen generators (tsgo schemaSyncInEffect rule)
- Replace inline Schema.encodeUnknownSync with module-level wrappers
to avoid schemaSyncInEffect rule inside generators
@github-actions

github-actionsBot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🚀 Expo continuous deployment is ready!

  • Project → t3-code
  • Platforms → android, ios
  • Scheme → t3code-preview
🤖 Android🍎 iOS
Fingerprintfe5a51f2e189da69dfc4c2cd458e6cfb5fdff2eaae3bd597809dfd7771d0898f735d172973d4c1c8
Build DetailsBuild Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
App version: 0.1.0
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Build Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
App version: 0.1.0
Git commit: eea0dcae4150df8341606520c074dc651ae7c00a
Update DetailsUpdate Permalink
DetailsBranch: pr-2829
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Update Permalink
DetailsBranch: pr-2829
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Update QR

Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
Comment threadapps/server/src/ws.ts
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
@juliusmarmingejuliusmarminge changed the title WIP: wire orchestration v2 provider adaptersfeat(orchestrator): introduce new orchestratorJun 14, 2026
Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
Comment on lines +109 to +128
Effect.gen(function* () {
const threadId = payloadInput.threadId ?? input.threadId;
const eventId = yield* idAllocator.allocate.event({
threadId,
providerSessionId: input.providerSessionId,
});
const occurredAt = yield* DateTime.now;
return yield* Schema.decodeUnknownEffect(OrchestrationV2DomainEvent)(
compactUndefined({
id: eventId,
type: payloadInput.type,
threadId,
runId: payloadInput.runId ?? input.runId,
nodeId: payloadInput.nodeId ?? input.nodeId,
provider: input.event.provider,
rawEventId: input.rawEventId,
occurredAt,
payload: payloadInput.payload,
}),
);

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.

🟡 Mediumorchestration-v2/ProviderEventIngestor.ts:109

In makeDomainEvent, the ?? operator on lines 121 and 123 treats explicit null as equivalent to undefined, causing payloadInput.runId ?? input.runId to fall back to input.runId when payloadInput.runId is explicitly null. Since the type is readonly runId?: RunId | null, this means explicit null values from the caller (e.g., input.event.node.runId being null on line 172) are incorrectly overwritten instead of preserved. Consider using === undefined checks like lines 161-162 and 210-211, or use payloadInput.runId === undefined ? input.runId : payloadInput.runId.

 const threadId = payloadInput.threadId ?? input.threadId;
- const runId = payloadInput.runId ?? input.runId;- const nodeId = payloadInput.nodeId ?? input.nodeId;+ const runId = payloadInput.runId === undefined ? input.runId : payloadInput.runId;+ const nodeId = payloadInput.nodeId === undefined ? input.nodeId : payloadInput.nodeId;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/ProviderEventIngestor.ts around lines 109-128:
In `makeDomainEvent`, the `??` operator on lines 121 and 123 treats explicit `null` as equivalent to `undefined`, causing `payloadInput.runId ?? input.runId` to fall back to `input.runId` when `payloadInput.runId` is explicitly `null`. Since the type is `readonly runId?: RunId | null`, this means explicit `null` values from the caller (e.g., `input.event.node.runId` being `null` on line 172) are incorrectly overwritten instead of preserved. Consider using `=== undefined` checks like lines 161-162 and 210-211, or use `payloadInput.runId === undefined ? input.runId : payloadInput.runId`.
Evidence trail:
apps/server/src/orchestration-v2/ProviderEventIngestor.ts lines 105-106 (payloadInput type with `RunId | null`), line 121 (`runId: payloadInput.runId ?? input.runId`), line 122 (`nodeId: payloadInput.nodeId ?? input.nodeId`), lines 161-162 and 210-211 (codebase uses `=== undefined` pattern elsewhere). packages/contracts/src/orchestrationV2.ts line 358 (`runId: Schema.NullOr(RunId)` on ExecutionNode - confirms null is a valid value), line 857 (`runId: Schema.optional(RunId)` on EventBase - domain event uses optional/undefined, not null). apps/server/src/orchestration-v2/ProviderEventIngestor.ts lines 80-81 (compactUndefined only strips undefined, not null).

Comment threadapps/server/src/orchestration-v2/Orchestrator.ts
Comment threadapps/web/src/routes/debug.orchestration-v2.tsx Outdated
Comment threadapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 79031a1 to 4e68dcbCompareJune 14, 2026 23:55
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 4e68dcb to c7539b9CompareJune 17, 2026 07:30
Comment threadapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/ProviderSessionManager.ts Outdated
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts Outdated
Comment threadapps/server/src/mcp/OrchestratorMcpService.ts Outdated
Comment threadapps/server/src/ws.ts Outdated
function nativeThreadId(provider: ProviderKind, thread: OrchestrationV2ProviderThread): string {
const id = thread.nativeThreadRef?.nativeId;
if (id === null || id === undefined || id.trim().length === 0) {
throw new ProviderAdapterProtocolError({

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.

🟡 MediumAdapters/AcpAdapterV2.ts:271

When nativeThreadId is called inside Effect.gen generators (e.g., lines 899, 1813), the thrown ProviderAdapterProtocolError becomes an untyped defect instead of a typed failure. This bypasses Effect.mapError and other typed error handlers, causing the error to propagate as an unexpected defect. Consider converting nativeThreadId to return Effect<string, ProviderAdapterProtocolError> and yielding it at each call site, or inlining the validation with yield* new ProviderAdapterProtocolError(...) so the failure is properly typed.

Also found in 1 other location(s)

apps/server/src/orchestration-v2/ThreadManagementService.ts:278

The statement return yield* managementError(...) cannot work correctly because managementError() returns a ThreadManagementError instance, not an Effect. The yield* operator in Effect.gen expects an Effect value. This should be return yield* Effect.fail(managementError(...)). The correct pattern is demonstrated elsewhere in this file (lines 241-246) where Effect.fail(managementError(...)) is properly used. This same bug pattern repeats at lines 291, 333, 357, 374, 390, 406, and 427.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts around line 271:
When `nativeThreadId` is called inside `Effect.gen` generators (e.g., lines 899, 1813), the thrown `ProviderAdapterProtocolError` becomes an untyped defect instead of a typed failure. This bypasses `Effect.mapError` and other typed error handlers, causing the error to propagate as an unexpected defect. Consider converting `nativeThreadId` to return `Effect<string, ProviderAdapterProtocolError>` and yielding it at each call site, or inlining the validation with `yield* new ProviderAdapterProtocolError(...)` so the failure is properly typed.
Evidence trail:
1. nativeThreadId function with throw: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts lines 268-277
2. Call site inside Effect.gen: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 899
3. Call site inside Effect.gen: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 1813
4. Correct yield* pattern for comparison: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 1808
5. ProviderAdapterProtocolError class definition: apps/server/src/orchestration-v2/ProviderAdapter.ts lines 316-327
6. Effect.gen implementation delegating to fromIteratorUnsafe: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 1104-1125
7. fromIteratorUnsafe calling iter.next() without try/catch: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 1285-1307
8. FiberImpl.runLoop catch block converting thrown errors to exitDie: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 646-650
9. die = exitDie producing Effect<never> (untyped): https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts line 947
Also found in 1 other location(s):
- apps/server/src/orchestration-v2/ThreadManagementService.ts:278 -- The statement `return yield* managementError(...)` cannot work correctly because `managementError()` returns a `ThreadManagementError` instance, not an `Effect`. The `yield*` operator in `Effect.gen` expects an Effect value. This should be `return yield* Effect.fail(managementError(...))`. The correct pattern is demonstrated elsewhere in this file (lines 241-246) where `Effect.fail(managementError(...))` is properly used. This same bug pattern repeats at lines 291, 333, 357, 374, 390, 406, and 427.

juliusmarmingeand others added 23 commits September 1, 2026 16:33
R12 follow-up
Insert persistent feedback blocks by their timestamp within the canonical timeline while preserving projected row order. Keep real optimistic sends appended and suppress duplicate local messages already committed by the server.
Implemented by GPT-5.6 Sol via Codex.
R20 follow-up
Give inspector reasoning markdown its projected source thread and retain the explicit environment fallback for proposed plans without a thread reference. Workspace links and images now resolve through their owning environment after removal of the active-environment fallback.
Implemented by GPT-5.6 Sol via Codex.
Read the inclusive cursor, a full history page, and a look-behind row so older history does not terminate after one page.
Finding: P01 pagination termination
Model: GPT-5.6 Sol via Codex
Cancel pending SDK requests before aborting the native session. Preserve per-admission cancellation state and treat stopped initial prompts as interruption instead of provider failure.
Finding: R14 prompt cancellation
Model: GPT-5.6 Sol via Codex
Keep the original cursor owner through ancestor traversal and preserve the history budget across empty intermediate forks. Verify exact paged history against the complete nested projection.
Finding: P01 nested lineage
Model: GPT-5.6 Sol via Codex
Retain pending admission after transient status failures and use one generation-owned retry worker. Ignore stale timers and duplicate evidence so older prompts cannot finish newer steering.
Finding: R14 status reconciliation
Model: GPT-5.6 Sol via Codex
Keep hidden local and inherited rows from consuming history pages. Preserve stop-request dependencies, source-run cutoffs, and imported history while loading related metadata from the selected cohort and using indexed watermark lookups.
Finding: P01 bounded history visibility
Model: GPT-5.6 Sol via Codex
Adapt grouped tool summaries and the floating working timer to V2 run, attempt, and queue state. Bring over the composer, keyboard, and disclosure transitions while retaining the V2 activity inspector and queue controls.
Keep OV2 web composer and grouping behavior intact; share only the existing command label parser with mobile.
Restores main features dropped by the policy replay: #8569 theme wiring,
settings search rework, #8803 workspace-mutation refresh (v2-adapted),
video + image previews (web and mobile, v2-adapted), #8862 Expo glass,
and the round's docs. Timeline thinking rows (#8984) stay on the v2
work-live system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v2 equivalents of main's #8984 and #8922: a "Working for ..." header
anchors the active run, the trailing live tool row survives between
actions in past tense instead of vanishing, and a shimmering Thinking
row marks reasoning gaps. During workspace preparation the header shows
"Setting up worktree..." (driven by the local dispatch flag or the v2
run's preparing status, so remote viewers see it too), the composer
footer span is gone, and draft promotion waits until the run starts or
startup fails instead of navigating mid-preparation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adopts the round's main features into the v2 architecture: the #9023
media rework (streamed videos, media-file assets, protocol-relative
links), #9098 shared live-activity row folded into the v2 working and
thinking rows, the #9084/#9078 Claude model catalog for v2 consumers,
a native #9005 OpenCode child-session abort in the v2 adapter, #9013's
landed LegendList patch, and per-environment sidebar provider entries.
For #8600 the server-side pieces land, but auto-settle evaluation stays
client-side (reading the new server-owned settings) until the v2
orchestrator grows its own settlement reactor; main's v1-only reactor
and coalescer additions are dropped with the rest of the v1 path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

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.

composerVideos and composerOtherFiles are computed but never rendered — the attachment list below still maps composerFiles (line 4034) into the generic FileIcon row, so ComposerVideoThumbnail (line 176), the new PlayIcon import (line 305), and attachVideoThumbnail / isPreviewableComposerVideo have no live consumer here. The result is a split treatment for the same attachment: a staged video is a plain file row in the composer, while the sent message renders it as a playable tile (MessagesTimeline.tsxuserVideos). The three unused locals will also trip unused-symbol checks.

Smallest fix: render composerVideos as thumbnail tiles (ComposerVideoThumbnail + buildExpandedImagePreview, matching the timeline tile) and feed the existing file-row list from composerOtherFiles; if the composer treatment is not landing in this PR, drop the two locals, ComposerVideoThumbnail, and the PlayIcon import instead so the file does not carry a half-wired path.

Posted via Macroscope — UI Consistency

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.

composerVideos and composerOtherFiles are computed but never rendered — the attachment list below still maps composerFiles (line 4034) into the generic FileIcon row, so ComposerVideoThumbnail (line 176), the new PlayIcon import (line 305), and attachVideoThumbnail / isPreviewableComposerVideo have no live consumer here. The result is a split treatment for the same attachment: a staged video is a plain file row in the composer, while the sent message renders it as a playable tile (MessagesTimeline.tsxuserVideos). The three unused locals will also trip unused-symbol checks.

Smallest fix: render composerVideos as thumbnail tiles (ComposerVideoThumbnail + buildExpandedImagePreview, matching the timeline tile) and feed the existing file-row list from composerOtherFiles; if the composer treatment is not landing in this PR, drop the two locals, ComposerVideoThumbnail, and the PlayIcon import instead so the file does not carry a half-wired path.

Posted via Macroscope — UI Consistency

!hasContent &&
(props.selectedThread.session?.status === "running" ||
props.selectedThread.session?.status === "starting");
const showStopAction = props.canStopThread;

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.

Stop hides send while composing

High Severity

showStopAction now follows canStopThread alone and no longer requires an empty composer. While a run is interruptible, typed text or attachments still replace Send/Queue with Stop, so follow-ups cannot be sent or queued until the run is interrupted.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

selectedThreadFeed,
selectedThreadKey,
]);
}, [props.onSendMessage, selectedThreadKey]);

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.

Send always re-anchors the feed

Medium Severity

handleSendMessage now always sets anchorMessageId to the just-sent id. The previous first-message-only policy in resolveThreadFeedSubmissionAnchor is no longer applied, so follow-ups and second outbox sends retarget the live-follow scroll.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

// Dictation keeps that focus while the composer switches to its compact pill.
const composerBottomInset = (
Platform.OS === "android" ? isKeyboardVisible : composerExpanded || composerFocused
)

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.

iOS composer inset ignores focus

Medium Severity

iOS composerBottomInset now keys only on composerExpanded, and the editor no longer updates composerFocused. Focusing the collapsed pill still applies home-indicator padding above the keyboard, leaving a gap between the composer and the IME.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

…ator
Ports #8600's server-owned settlement to orchestration v2 instead of
keeping client-side evaluation. A ThreadSettlementService sweep runs at
startup, on auto-settle settings changes, and once per minute: it
evaluates inactivity and merged or closed pull requests over v2 thread
shells and dispatches the new guarded thread.auto-settle command, which
rejects threads that changed after the sweep's snapshot or carry any
explicit override, then reuses the orchestrator's settle lifecycle.
With the server deciding, the clients drop their effectiveSettled
evaluation and partition on the persisted settledOverride like main.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@macroscopeapp

Copy link
Copy Markdown
Contributor

apps/server/src/cli/app.ts:232-239

(Posted at PR level: an inline comment could not be anchored on this file in the current diff.)

The Effect.tryPromisecatch in runAppCommand collapses six semantically distinct failures into one error whose distinction survives only as an English sentence inside cause: Schema.Defect() — response timeout, oversized response, non-JSON response, schema-invalid response, request-id mismatch, and "connection closed" all become DesktopAppUnreachableError, whose message asserts the desktop app could not be reached and tells the user to start it. In the four response-shaped cases the app did answer, so that caller-visible message is wrong, and cli/app.test.ts asserts on the prose (cause: { message: "The desktop app response is invalid." }), which makes it behavior rather than diagnostics.

The conventions ask that failures be distinguished by stable structural attributes with the message derived only from them, and that a translation boundary wrap only genuinely lower-level failures rather than flatten known ones. Consider having sendDesktopAppActivationRequest reject with tagged errors (or return a tagged result) so the CLI can keep DesktopAppUnreachableError for the connect-level ENOENT/ECONNREFUSED case its message describes, and add e.g. DesktopAppResponseInvalidError { requestId, workspaceRoot, byteLength } and DesktopAppResponseTimeoutError { requestId, timeoutMs } for the rest, each keeping the real socket error as cause where one exists.

Posted via Macroscope — Effect Service Conventions

Posted via Macroscope — Effect Service Conventions

@macroscopeapp

Copy link
Copy Markdown
Contributor

apps/server/src/cli/app.ts:232-239

(Posted at PR level: an inline comment could not be anchored on this file in the current diff.)

The Effect.tryPromisecatch in runAppCommand collapses six semantically distinct failures into one error whose distinction survives only as an English sentence inside cause: Schema.Defect() — response timeout, oversized response, non-JSON response, schema-invalid response, request-id mismatch, and "connection closed" all become DesktopAppUnreachableError, whose message asserts the desktop app could not be reached and tells the user to start it. In the four response-shaped cases the app did answer, so that caller-visible message is wrong, and cli/app.test.ts asserts on the prose (cause: { message: "The desktop app response is invalid." }), which makes it behavior rather than diagnostics.

The conventions ask that failures be distinguished by stable structural attributes with the message derived only from them, and that a translation boundary wrap only genuinely lower-level failures rather than flatten known ones. Consider having sendDesktopAppActivationRequest reject with tagged errors (or return a tagged result) so the CLI can keep DesktopAppUnreachableError for the connect-level ENOENT/ECONNREFUSED case its message describes, and add e.g. DesktopAppResponseInvalidError { requestId, workspaceRoot, byteLength } and DesktopAppResponseTimeoutError { requestId, timeoutMs } for the rest, each keeping the real socket error as cause where one exists.

Posted via Macroscope — Effect Service Conventions

Posted via Macroscope — Effect Service Conventions

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

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

@@ -2085,12 +2062,9 @@ export default function Sidebar() {
// memo exactly at the next wake boundary.
void snoozeWakeTick;
const preciseNow = new Date().toISOString();
const visible = threads.filter(

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.

Web sidebar pinned threads partition completely broken

High Severity

The partition loop declares const pinned: EnvironmentThreadShell[] = [] at line 2068 but never pushes anything into it. The old code had an else if (thread.pinnedAt != null) { pinned.push(thread); } branch between the snoozed and settled checks, but it was removed in this refactor. All pinned threads now fall into active instead. The mobile equivalent in threadListV2.ts correctly retains else if (thread.pinnedAt != null) { pinned.push(thread); } at line 421. This means pinnedThreads is always empty, the pinned section never renders, DnD reordering is inert, and reorderablePinnedKeys is an empty set.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

thread.session?.status === "running" &&
thread.session.activeTurnId != null
) {
if (action === "archive" && threadRuntimeIsActive(thread.runtime)) {

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.

Archive guard stricter than old threadCanArchive behavior

Medium Severity

The comment says "Archive keeps its original, narrower guard" but threadRuntimeIsActive is actually wider (more restrictive) than the replaced threadCanArchive. threadCanArchive allowed archiving queued threads without an activeRunId and all waiting threads; threadRuntimeIsActive blocks both. The old threadCanArchive is still imported at line 21 but now unused, confirming a mistaken substitution rather than an intentional tightening.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(orchestrator): introduce new orchestrator - #2829

Open
juliusmarminge wants to merge 330 commits into
mainfrom
t3code/codex-turn-mapping
Open

feat(orchestrator): introduce new orchestrator#2829
juliusmarminge wants to merge 330 commits into
mainfrom
t3code/codex-turn-mapping

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 27, 2026

Copy link
Copy Markdown
Member

Summary

  • wire orchestration V2 provider adapter registry/factory flow for Codex and Claude provider instances
  • add Claude replay/query primitives, native fork/rollback fixtures, subagent fixture coverage, and provider replay harness updates
  • update debugger model/provider picker and improve user-facing orchestration errors

Validation

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test -- src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts -t claudeAgent
  • bun run test -- src/orchestration-v2/testkit/ClaudeReplayFixtures.integration.test.ts
  • bun run test -- src/orchestration-v2/testkit/ThreadFork.integration.test.ts -t Claude

Notes

  • Draft PR for review of current branch state. Codex all-provider replay still needs schema alignment with latest app-server behavior before it can be treated as a full-suite signal.

Closes

Verified against the branch with code/commit evidence.

High confidence

Closes#4952
Closes#4873
Closes#4775
Closes#4795
Closes#4710
Closes#4668
Closes#4619
Closes#4584
Closes#4561
Closes#4713
Closes#4198
Closes#4452
Closes#3797
Closes#4232
Closes#3666
Closes#3580
Closes#2785
Closes#2789
Closes#3138
Closes#1404
Closes#231
Closes#216

Medium confidence (under review)

Closes#4568
Closes#4766
Closes#4495
Closes#4456
Closes#4399
Closes#3744
Closes#2921
Closes#3624
Closes#3149
Closes#2336
Closes#538
Closes#2173
Closes#2065

Note

Introduce new Orchestration V2 runtime with provider adapters, persistence, and client support

  • Adds a complete Orchestration V2 server runtime: event store, projection store, event sink, effect outbox/worker, ID allocator, command policy, thread management/launch/lifecycle/fork/settlement services, checkpoint capture/rollback, provider session manager, runtime recovery, and context handoff services across orchestration-v2/
  • Adds V2 provider adapters for Codex, Claude, Cursor, Grok, OpenCode, and ACP Registry, plus a provider adapter registry and driver framework with replay testkits for each adapter
  • Adds persistence migrations 044–052 for V2 event/projection tables, effect outbox, provider-session bindings, thread-launch workflows, application event sourcing, scheduled tasks, and legacy V1 import state
  • Adds MCP orchestration and worktree toolkits, a project service with enrichment, scheduled-task service, HTTP API layer, and a legacy V1 thread importer that reconciles old threads into V2 events
  • Updates web and mobile clients to consume V2 thread projections, shell snapshots, runtime status, queued-run management, thread relationships, history paging, and scheduled-task controls
  • Risk: legacy session/latestTurn fields are replaced by runtime/latestRun across web and mobile thread state; out-of-tree consumers of the old shell snapshot schema or V1 RPC methods will break. Cache schema version bumped to 3, discarding V1 cache envelopes on decode failure. Orchestration protocol version 2 is now required for client connections.

Macroscope summarized 6081201.


Note

High Risk
Mobile thread persistence, archive/stop guards, and approval/user-input handling now depend on V2 runtime semantics and a bumped cache schema, alongside removal of PR transfer regression reporting.

Overview
Removes the automated “thread transfer impact” PR comment pipeline by deleting the thread-transfer-report workflow and its trusted publisher scripts/tests, while CI still may emit transfer budget artifacts from the main workflow.

Mobile is wired to orchestration V2 end-to-end: SQLite shell/thread caches now use the shared ORCHESTRATION_CACHE_SCHEMA_VERSION and V2 snapshot shapes; the connection runtime loads bounded thread history plus a history controller instead of the old unbounded loader. Thread list/detail flows switch from legacy session/turn fields to runtime summaries (thread.runtime, threadRuntimeIsActive, dedicated archive eligibility), runtime request IDs for approvals and user input (including read-only UI when responseCapability is not live), and checkpoint/review data derived from thread projections rather than embedded checkpoint lists.

Thread UX additions and polish include visit watermark dispatch on open, a queue control and relationships banner on the feed, progressive history controls, a thread activity inspector with rollback, shared brand mark assets, extra adaptive theme tokens, and copy tweaks (“coding runtime”). CI installs build-essential so ACP process-tree pthread fixtures compile instead of soft-skipping. Smaller follow-ons: desktop environment user-data dir test expectations, Tailscale Effect typing, default persistComposerContextStrip, README appearance doc link, and marketing Cursor harness label.

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

@coderabbitai

coderabbitaiBot commented May 27, 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: e3efadfb-5f39-4c7c-970d-8aade3f16f07

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
  • Commit unit tests in branch t3code/codex-turn-mapping

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels May 27, 2026
Comment threadapps/server/src/orchestration-v2/EventStore.ts Outdated
Comment on lines +116 to +120
return decodeTranscript({
...metadata,
entries,
});
});

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.

🟢 Lowtestkit/ReplayTranscriptNdjson.ts:116

The call to decodeTranscript at line 116 invokes Schema.decodeUnknownSync, which throws on validation failure. Since this isn't wrapped in Effect.try, any validation error becomes an uncaught exception (defect) instead of a typed ProviderReplayNdjsonParseError. This breaks the function's declared error contract. Consider wrapping the call in Effect.try to catch the exception and convert it to the declared error type.

- return decodeTranscript({- ...metadata,- entries,- });+ return yield* Effect.try({+ try: () =>+ decodeTranscript({+ ...metadata,+ entries,+ }),+ catch: (cause) =>+ new ProviderReplayNdjsonLineParseError({+ lineNumber: lines.length,+ line: "<transcript validation>",+ cause,+ }),+ });
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts around lines 116-120:
The call to `decodeTranscript` at line 116 invokes `Schema.decodeUnknownSync`, which throws on validation failure. Since this isn't wrapped in `Effect.try`, any validation error becomes an uncaught exception (defect) instead of a typed `ProviderReplayNdjsonParseError`. This breaks the function's declared error contract. Consider wrapping the call in `Effect.try` to catch the exception and convert it to the declared error type.
Evidence trail:
apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts lines 50-53: `decodeTranscript = Schema.decodeUnknownSync(ProviderReplayTranscript)` — throws on failure.
Line 116-118: `return decodeTranscript({...metadata, entries})` — called directly inside Effect.gen without Effect.try wrapper.
Lines 55-68 (`parseReplayRecord`): same pattern but correctly wrapped in `Effect.try`.
Line 80: function declares error type `ProviderReplayNdjsonParseError`.
packages/contracts/src/orchestrationV2.ts lines 1561-1568: `ProviderReplayTranscript` schema with `TrimmedNonEmptyString` fields that can fail validation.

Comment threadapps/server/src/orchestration-v2/ProviderAdapterRegistry.ts Outdated
Comment threadpackages/client-runtime/src/wsRpcClient.ts Outdated
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/EventSink.ts
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 1, 2026
…der adapters (t3-29f.6)
Assessment of upstream PR pingdotgg#2829 (pingdotgg/t3code) from juliusmarminge:
WIP wire orchestration v2 provider adapters with Codex and Claude adapters,
event sourcing, provider session management, and replay testkit.
Relevance to target issues:
- pingdotgg#2838 (session resume): HIGH — ProviderSessionManager persists session
IDs and separates startSession/resumeSession operations
- pingdotgg#2778 (subagent hang): MEDIUM — ProviderEventIngestor provides
infrastructure to forward permission events, but UI plumbing not yet wired
- pingdotgg#2886 (thread stuck working): HIGH — event-sourced projections replace
mutable state flags, eliminating sticky "working" states
The PR is a draft (34 commits, not merged). No OpenCode ACP adapter
exists yet in v2 — OpenCode would need its own adapter wired into the
ProviderAdapterRegistry. Recommend watching for merge and adding an
OpenCode adapter post-merge.
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
The upstream PR pingdotgg#2829 added orchestrationV2 methods to the WsRpcClient
interface. The test mock in service.threadSubscriptions.test.ts was
missing the orchestrationV2 property, causing a typecheck failure:
'Property orchestrationV2 is missing in type...'
Added orchestrationV2 mock with dispatchCommand, getThreadProjection,
subscribeShell, and subscribeThread as vi.fn() stubs.
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
The upstream PR pingdotgg#2829 targets a newer Effect version than our fork's
pinned effect@4.0.0-beta.73. Fixes:
- Replace Random.nextUUIDv4 with Crypto.randomUUIDv4 (beta.73 API)
- Fix deterministic Service tag keys to match fork convention
(include file path segments; e.g. Adapters/ClaudeAdapterV2/...)
- Replace Schema.decodeSync with Schema.decodeUnknownEffect inside
Effect.gen generators (tsgo schemaSyncInEffect rule)
- Replace inline Schema.encodeUnknownSync with module-level wrappers
to avoid schemaSyncInEffect rule inside generators
@github-actions

github-actionsBot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🚀 Expo continuous deployment is ready!

  • Project → t3-code
  • Platforms → android, ios
  • Scheme → t3code-preview
🤖 Android🍎 iOS
Fingerprintfe5a51f2e189da69dfc4c2cd458e6cfb5fdff2eaae3bd597809dfd7771d0898f735d172973d4c1c8
Build DetailsBuild Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
App version: 0.1.0
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Build Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
App version: 0.1.0
Git commit: eea0dcae4150df8341606520c074dc651ae7c00a
Update DetailsUpdate Permalink
DetailsBranch: pr-2829
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Update Permalink
DetailsBranch: pr-2829
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Update QR

Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
Comment threadapps/server/src/ws.ts
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
@juliusmarmingejuliusmarminge changed the title WIP: wire orchestration v2 provider adaptersfeat(orchestrator): introduce new orchestratorJun 14, 2026
Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
Comment on lines +109 to +128
Effect.gen(function* () {
const threadId = payloadInput.threadId ?? input.threadId;
const eventId = yield* idAllocator.allocate.event({
threadId,
providerSessionId: input.providerSessionId,
});
const occurredAt = yield* DateTime.now;
return yield* Schema.decodeUnknownEffect(OrchestrationV2DomainEvent)(
compactUndefined({
id: eventId,
type: payloadInput.type,
threadId,
runId: payloadInput.runId ?? input.runId,
nodeId: payloadInput.nodeId ?? input.nodeId,
provider: input.event.provider,
rawEventId: input.rawEventId,
occurredAt,
payload: payloadInput.payload,
}),
);

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.

🟡 Mediumorchestration-v2/ProviderEventIngestor.ts:109

In makeDomainEvent, the ?? operator on lines 121 and 123 treats explicit null as equivalent to undefined, causing payloadInput.runId ?? input.runId to fall back to input.runId when payloadInput.runId is explicitly null. Since the type is readonly runId?: RunId | null, this means explicit null values from the caller (e.g., input.event.node.runId being null on line 172) are incorrectly overwritten instead of preserved. Consider using === undefined checks like lines 161-162 and 210-211, or use payloadInput.runId === undefined ? input.runId : payloadInput.runId.

 const threadId = payloadInput.threadId ?? input.threadId;
- const runId = payloadInput.runId ?? input.runId;- const nodeId = payloadInput.nodeId ?? input.nodeId;+ const runId = payloadInput.runId === undefined ? input.runId : payloadInput.runId;+ const nodeId = payloadInput.nodeId === undefined ? input.nodeId : payloadInput.nodeId;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/ProviderEventIngestor.ts around lines 109-128:
In `makeDomainEvent`, the `??` operator on lines 121 and 123 treats explicit `null` as equivalent to `undefined`, causing `payloadInput.runId ?? input.runId` to fall back to `input.runId` when `payloadInput.runId` is explicitly `null`. Since the type is `readonly runId?: RunId | null`, this means explicit `null` values from the caller (e.g., `input.event.node.runId` being `null` on line 172) are incorrectly overwritten instead of preserved. Consider using `=== undefined` checks like lines 161-162 and 210-211, or use `payloadInput.runId === undefined ? input.runId : payloadInput.runId`.
Evidence trail:
apps/server/src/orchestration-v2/ProviderEventIngestor.ts lines 105-106 (payloadInput type with `RunId | null`), line 121 (`runId: payloadInput.runId ?? input.runId`), line 122 (`nodeId: payloadInput.nodeId ?? input.nodeId`), lines 161-162 and 210-211 (codebase uses `=== undefined` pattern elsewhere). packages/contracts/src/orchestrationV2.ts line 358 (`runId: Schema.NullOr(RunId)` on ExecutionNode - confirms null is a valid value), line 857 (`runId: Schema.optional(RunId)` on EventBase - domain event uses optional/undefined, not null). apps/server/src/orchestration-v2/ProviderEventIngestor.ts lines 80-81 (compactUndefined only strips undefined, not null).

Comment threadapps/server/src/orchestration-v2/Orchestrator.ts
Comment threadapps/web/src/routes/debug.orchestration-v2.tsx Outdated
Comment threadapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 79031a1 to 4e68dcbCompareJune 14, 2026 23:55
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 4e68dcb to c7539b9CompareJune 17, 2026 07:30
Comment threadapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/ProviderSessionManager.ts Outdated
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts Outdated
Comment threadapps/server/src/mcp/OrchestratorMcpService.ts Outdated
Comment threadapps/server/src/ws.ts Outdated
function nativeThreadId(provider: ProviderKind, thread: OrchestrationV2ProviderThread): string {
const id = thread.nativeThreadRef?.nativeId;
if (id === null || id === undefined || id.trim().length === 0) {
throw new ProviderAdapterProtocolError({

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.

🟡 MediumAdapters/AcpAdapterV2.ts:271

When nativeThreadId is called inside Effect.gen generators (e.g., lines 899, 1813), the thrown ProviderAdapterProtocolError becomes an untyped defect instead of a typed failure. This bypasses Effect.mapError and other typed error handlers, causing the error to propagate as an unexpected defect. Consider converting nativeThreadId to return Effect<string, ProviderAdapterProtocolError> and yielding it at each call site, or inlining the validation with yield* new ProviderAdapterProtocolError(...) so the failure is properly typed.

Also found in 1 other location(s)

apps/server/src/orchestration-v2/ThreadManagementService.ts:278

The statement return yield* managementError(...) cannot work correctly because managementError() returns a ThreadManagementError instance, not an Effect. The yield* operator in Effect.gen expects an Effect value. This should be return yield* Effect.fail(managementError(...)). The correct pattern is demonstrated elsewhere in this file (lines 241-246) where Effect.fail(managementError(...)) is properly used. This same bug pattern repeats at lines 291, 333, 357, 374, 390, 406, and 427.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts around line 271:
When `nativeThreadId` is called inside `Effect.gen` generators (e.g., lines 899, 1813), the thrown `ProviderAdapterProtocolError` becomes an untyped defect instead of a typed failure. This bypasses `Effect.mapError` and other typed error handlers, causing the error to propagate as an unexpected defect. Consider converting `nativeThreadId` to return `Effect<string, ProviderAdapterProtocolError>` and yielding it at each call site, or inlining the validation with `yield* new ProviderAdapterProtocolError(...)` so the failure is properly typed.
Evidence trail:
1. nativeThreadId function with throw: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts lines 268-277
2. Call site inside Effect.gen: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 899
3. Call site inside Effect.gen: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 1813
4. Correct yield* pattern for comparison: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 1808
5. ProviderAdapterProtocolError class definition: apps/server/src/orchestration-v2/ProviderAdapter.ts lines 316-327
6. Effect.gen implementation delegating to fromIteratorUnsafe: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 1104-1125
7. fromIteratorUnsafe calling iter.next() without try/catch: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 1285-1307
8. FiberImpl.runLoop catch block converting thrown errors to exitDie: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 646-650
9. die = exitDie producing Effect<never> (untyped): https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts line 947
Also found in 1 other location(s):
- apps/server/src/orchestration-v2/ThreadManagementService.ts:278 -- The statement `return yield* managementError(...)` cannot work correctly because `managementError()` returns a `ThreadManagementError` instance, not an `Effect`. The `yield*` operator in `Effect.gen` expects an Effect value. This should be `return yield* Effect.fail(managementError(...))`. The correct pattern is demonstrated elsewhere in this file (lines 241-246) where `Effect.fail(managementError(...))` is properly used. This same bug pattern repeats at lines 291, 333, 357, 374, 390, 406, and 427.

juliusmarmingeand others added 23 commits September 1, 2026 16:33
R12 follow-up
Insert persistent feedback blocks by their timestamp within the canonical timeline while preserving projected row order. Keep real optimistic sends appended and suppress duplicate local messages already committed by the server.
Implemented by GPT-5.6 Sol via Codex.
R20 follow-up
Give inspector reasoning markdown its projected source thread and retain the explicit environment fallback for proposed plans without a thread reference. Workspace links and images now resolve through their owning environment after removal of the active-environment fallback.
Implemented by GPT-5.6 Sol via Codex.
Read the inclusive cursor, a full history page, and a look-behind row so older history does not terminate after one page.
Finding: P01 pagination termination
Model: GPT-5.6 Sol via Codex
Cancel pending SDK requests before aborting the native session. Preserve per-admission cancellation state and treat stopped initial prompts as interruption instead of provider failure.
Finding: R14 prompt cancellation
Model: GPT-5.6 Sol via Codex
Keep the original cursor owner through ancestor traversal and preserve the history budget across empty intermediate forks. Verify exact paged history against the complete nested projection.
Finding: P01 nested lineage
Model: GPT-5.6 Sol via Codex
Retain pending admission after transient status failures and use one generation-owned retry worker. Ignore stale timers and duplicate evidence so older prompts cannot finish newer steering.
Finding: R14 status reconciliation
Model: GPT-5.6 Sol via Codex
Keep hidden local and inherited rows from consuming history pages. Preserve stop-request dependencies, source-run cutoffs, and imported history while loading related metadata from the selected cohort and using indexed watermark lookups.
Finding: P01 bounded history visibility
Model: GPT-5.6 Sol via Codex
Adapt grouped tool summaries and the floating working timer to V2 run, attempt, and queue state. Bring over the composer, keyboard, and disclosure transitions while retaining the V2 activity inspector and queue controls.
Keep OV2 web composer and grouping behavior intact; share only the existing command label parser with mobile.
Restores main features dropped by the policy replay: #8569 theme wiring,
settings search rework, #8803 workspace-mutation refresh (v2-adapted),
video + image previews (web and mobile, v2-adapted), #8862 Expo glass,
and the round's docs. Timeline thinking rows (#8984) stay on the v2
work-live system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v2 equivalents of main's #8984 and #8922: a "Working for ..." header
anchors the active run, the trailing live tool row survives between
actions in past tense instead of vanishing, and a shimmering Thinking
row marks reasoning gaps. During workspace preparation the header shows
"Setting up worktree..." (driven by the local dispatch flag or the v2
run's preparing status, so remote viewers see it too), the composer
footer span is gone, and draft promotion waits until the run starts or
startup fails instead of navigating mid-preparation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adopts the round's main features into the v2 architecture: the #9023
media rework (streamed videos, media-file assets, protocol-relative
links), #9098 shared live-activity row folded into the v2 working and
thinking rows, the #9084/#9078 Claude model catalog for v2 consumers,
a native #9005 OpenCode child-session abort in the v2 adapter, #9013's
landed LegendList patch, and per-environment sidebar provider entries.
For #8600 the server-side pieces land, but auto-settle evaluation stays
client-side (reading the new server-owned settings) until the v2
orchestrator grows its own settlement reactor; main's v1-only reactor
and coalescer additions are dropped with the rest of the v1 path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

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.

composerVideos and composerOtherFiles are computed but never rendered — the attachment list below still maps composerFiles (line 4034) into the generic FileIcon row, so ComposerVideoThumbnail (line 176), the new PlayIcon import (line 305), and attachVideoThumbnail / isPreviewableComposerVideo have no live consumer here. The result is a split treatment for the same attachment: a staged video is a plain file row in the composer, while the sent message renders it as a playable tile (MessagesTimeline.tsxuserVideos). The three unused locals will also trip unused-symbol checks.

Smallest fix: render composerVideos as thumbnail tiles (ComposerVideoThumbnail + buildExpandedImagePreview, matching the timeline tile) and feed the existing file-row list from composerOtherFiles; if the composer treatment is not landing in this PR, drop the two locals, ComposerVideoThumbnail, and the PlayIcon import instead so the file does not carry a half-wired path.

Posted via Macroscope — UI Consistency

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.

composerVideos and composerOtherFiles are computed but never rendered — the attachment list below still maps composerFiles (line 4034) into the generic FileIcon row, so ComposerVideoThumbnail (line 176), the new PlayIcon import (line 305), and attachVideoThumbnail / isPreviewableComposerVideo have no live consumer here. The result is a split treatment for the same attachment: a staged video is a plain file row in the composer, while the sent message renders it as a playable tile (MessagesTimeline.tsxuserVideos). The three unused locals will also trip unused-symbol checks.

Smallest fix: render composerVideos as thumbnail tiles (ComposerVideoThumbnail + buildExpandedImagePreview, matching the timeline tile) and feed the existing file-row list from composerOtherFiles; if the composer treatment is not landing in this PR, drop the two locals, ComposerVideoThumbnail, and the PlayIcon import instead so the file does not carry a half-wired path.

Posted via Macroscope — UI Consistency

!hasContent &&
(props.selectedThread.session?.status === "running" ||
props.selectedThread.session?.status === "starting");
const showStopAction = props.canStopThread;

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.

Stop hides send while composing

High Severity

showStopAction now follows canStopThread alone and no longer requires an empty composer. While a run is interruptible, typed text or attachments still replace Send/Queue with Stop, so follow-ups cannot be sent or queued until the run is interrupted.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

selectedThreadFeed,
selectedThreadKey,
]);
}, [props.onSendMessage, selectedThreadKey]);

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.

Send always re-anchors the feed

Medium Severity

handleSendMessage now always sets anchorMessageId to the just-sent id. The previous first-message-only policy in resolveThreadFeedSubmissionAnchor is no longer applied, so follow-ups and second outbox sends retarget the live-follow scroll.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

// Dictation keeps that focus while the composer switches to its compact pill.
const composerBottomInset = (
Platform.OS === "android" ? isKeyboardVisible : composerExpanded || composerFocused
)

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.

iOS composer inset ignores focus

Medium Severity

iOS composerBottomInset now keys only on composerExpanded, and the editor no longer updates composerFocused. Focusing the collapsed pill still applies home-indicator padding above the keyboard, leaving a gap between the composer and the IME.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

…ator
Ports #8600's server-owned settlement to orchestration v2 instead of
keeping client-side evaluation. A ThreadSettlementService sweep runs at
startup, on auto-settle settings changes, and once per minute: it
evaluates inactivity and merged or closed pull requests over v2 thread
shells and dispatches the new guarded thread.auto-settle command, which
rejects threads that changed after the sweep's snapshot or carry any
explicit override, then reuses the orchestrator's settle lifecycle.
With the server deciding, the clients drop their effectiveSettled
evaluation and partition on the persisted settledOverride like main.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@macroscopeapp

Copy link
Copy Markdown
Contributor

apps/server/src/cli/app.ts:232-239

(Posted at PR level: an inline comment could not be anchored on this file in the current diff.)

The Effect.tryPromisecatch in runAppCommand collapses six semantically distinct failures into one error whose distinction survives only as an English sentence inside cause: Schema.Defect() — response timeout, oversized response, non-JSON response, schema-invalid response, request-id mismatch, and "connection closed" all become DesktopAppUnreachableError, whose message asserts the desktop app could not be reached and tells the user to start it. In the four response-shaped cases the app did answer, so that caller-visible message is wrong, and cli/app.test.ts asserts on the prose (cause: { message: "The desktop app response is invalid." }), which makes it behavior rather than diagnostics.

The conventions ask that failures be distinguished by stable structural attributes with the message derived only from them, and that a translation boundary wrap only genuinely lower-level failures rather than flatten known ones. Consider having sendDesktopAppActivationRequest reject with tagged errors (or return a tagged result) so the CLI can keep DesktopAppUnreachableError for the connect-level ENOENT/ECONNREFUSED case its message describes, and add e.g. DesktopAppResponseInvalidError { requestId, workspaceRoot, byteLength } and DesktopAppResponseTimeoutError { requestId, timeoutMs } for the rest, each keeping the real socket error as cause where one exists.

Posted via Macroscope — Effect Service Conventions

Posted via Macroscope — Effect Service Conventions

@macroscopeapp

Copy link
Copy Markdown
Contributor

apps/server/src/cli/app.ts:232-239

(Posted at PR level: an inline comment could not be anchored on this file in the current diff.)

The Effect.tryPromisecatch in runAppCommand collapses six semantically distinct failures into one error whose distinction survives only as an English sentence inside cause: Schema.Defect() — response timeout, oversized response, non-JSON response, schema-invalid response, request-id mismatch, and "connection closed" all become DesktopAppUnreachableError, whose message asserts the desktop app could not be reached and tells the user to start it. In the four response-shaped cases the app did answer, so that caller-visible message is wrong, and cli/app.test.ts asserts on the prose (cause: { message: "The desktop app response is invalid." }), which makes it behavior rather than diagnostics.

The conventions ask that failures be distinguished by stable structural attributes with the message derived only from them, and that a translation boundary wrap only genuinely lower-level failures rather than flatten known ones. Consider having sendDesktopAppActivationRequest reject with tagged errors (or return a tagged result) so the CLI can keep DesktopAppUnreachableError for the connect-level ENOENT/ECONNREFUSED case its message describes, and add e.g. DesktopAppResponseInvalidError { requestId, workspaceRoot, byteLength } and DesktopAppResponseTimeoutError { requestId, timeoutMs } for the rest, each keeping the real socket error as cause where one exists.

Posted via Macroscope — Effect Service Conventions

Posted via Macroscope — Effect Service Conventions

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

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

@@ -2085,12 +2062,9 @@ export default function Sidebar() {
// memo exactly at the next wake boundary.
void snoozeWakeTick;
const preciseNow = new Date().toISOString();
const visible = threads.filter(

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.

Web sidebar pinned threads partition completely broken

High Severity

The partition loop declares const pinned: EnvironmentThreadShell[] = [] at line 2068 but never pushes anything into it. The old code had an else if (thread.pinnedAt != null) { pinned.push(thread); } branch between the snoozed and settled checks, but it was removed in this refactor. All pinned threads now fall into active instead. The mobile equivalent in threadListV2.ts correctly retains else if (thread.pinnedAt != null) { pinned.push(thread); } at line 421. This means pinnedThreads is always empty, the pinned section never renders, DnD reordering is inert, and reorderablePinnedKeys is an empty set.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

thread.session?.status === "running" &&
thread.session.activeTurnId != null
) {
if (action === "archive" && threadRuntimeIsActive(thread.runtime)) {

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.

Archive guard stricter than old threadCanArchive behavior

Medium Severity

The comment says "Archive keeps its original, narrower guard" but threadRuntimeIsActive is actually wider (more restrictive) than the replaced threadCanArchive. threadCanArchive allowed archiving queued threads without an activeRunId and all waiting threads; threadRuntimeIsActive blocks both. The old threadCanArchive is still imported at line 21 but now unused, confirming a mistaken substitution rather than an intentional tightening.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(orchestrator): introduce new orchestrator - #2829

Open
juliusmarminge wants to merge 330 commits into
mainfrom
t3code/codex-turn-mapping
Open

feat(orchestrator): introduce new orchestrator#2829
juliusmarminge wants to merge 330 commits into
mainfrom
t3code/codex-turn-mapping

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 27, 2026

Copy link
Copy Markdown
Member

Summary

  • wire orchestration V2 provider adapter registry/factory flow for Codex and Claude provider instances
  • add Claude replay/query primitives, native fork/rollback fixtures, subagent fixture coverage, and provider replay harness updates
  • update debugger model/provider picker and improve user-facing orchestration errors

Validation

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test -- src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts -t claudeAgent
  • bun run test -- src/orchestration-v2/testkit/ClaudeReplayFixtures.integration.test.ts
  • bun run test -- src/orchestration-v2/testkit/ThreadFork.integration.test.ts -t Claude

Notes

  • Draft PR for review of current branch state. Codex all-provider replay still needs schema alignment with latest app-server behavior before it can be treated as a full-suite signal.

Closes

Verified against the branch with code/commit evidence.

High confidence

Closes#4952
Closes#4873
Closes#4775
Closes#4795
Closes#4710
Closes#4668
Closes#4619
Closes#4584
Closes#4561
Closes#4713
Closes#4198
Closes#4452
Closes#3797
Closes#4232
Closes#3666
Closes#3580
Closes#2785
Closes#2789
Closes#3138
Closes#1404
Closes#231
Closes#216

Medium confidence (under review)

Closes#4568
Closes#4766
Closes#4495
Closes#4456
Closes#4399
Closes#3744
Closes#2921
Closes#3624
Closes#3149
Closes#2336
Closes#538
Closes#2173
Closes#2065

Note

Introduce new Orchestration V2 runtime with provider adapters, persistence, and client support

  • Adds a complete Orchestration V2 server runtime: event store, projection store, event sink, effect outbox/worker, ID allocator, command policy, thread management/launch/lifecycle/fork/settlement services, checkpoint capture/rollback, provider session manager, runtime recovery, and context handoff services across orchestration-v2/
  • Adds V2 provider adapters for Codex, Claude, Cursor, Grok, OpenCode, and ACP Registry, plus a provider adapter registry and driver framework with replay testkits for each adapter
  • Adds persistence migrations 044–052 for V2 event/projection tables, effect outbox, provider-session bindings, thread-launch workflows, application event sourcing, scheduled tasks, and legacy V1 import state
  • Adds MCP orchestration and worktree toolkits, a project service with enrichment, scheduled-task service, HTTP API layer, and a legacy V1 thread importer that reconciles old threads into V2 events
  • Updates web and mobile clients to consume V2 thread projections, shell snapshots, runtime status, queued-run management, thread relationships, history paging, and scheduled-task controls
  • Risk: legacy session/latestTurn fields are replaced by runtime/latestRun across web and mobile thread state; out-of-tree consumers of the old shell snapshot schema or V1 RPC methods will break. Cache schema version bumped to 3, discarding V1 cache envelopes on decode failure. Orchestration protocol version 2 is now required for client connections.

Macroscope summarized 6081201.


Note

High Risk
Mobile thread persistence, archive/stop guards, and approval/user-input handling now depend on V2 runtime semantics and a bumped cache schema, alongside removal of PR transfer regression reporting.

Overview
Removes the automated “thread transfer impact” PR comment pipeline by deleting the thread-transfer-report workflow and its trusted publisher scripts/tests, while CI still may emit transfer budget artifacts from the main workflow.

Mobile is wired to orchestration V2 end-to-end: SQLite shell/thread caches now use the shared ORCHESTRATION_CACHE_SCHEMA_VERSION and V2 snapshot shapes; the connection runtime loads bounded thread history plus a history controller instead of the old unbounded loader. Thread list/detail flows switch from legacy session/turn fields to runtime summaries (thread.runtime, threadRuntimeIsActive, dedicated archive eligibility), runtime request IDs for approvals and user input (including read-only UI when responseCapability is not live), and checkpoint/review data derived from thread projections rather than embedded checkpoint lists.

Thread UX additions and polish include visit watermark dispatch on open, a queue control and relationships banner on the feed, progressive history controls, a thread activity inspector with rollback, shared brand mark assets, extra adaptive theme tokens, and copy tweaks (“coding runtime”). CI installs build-essential so ACP process-tree pthread fixtures compile instead of soft-skipping. Smaller follow-ons: desktop environment user-data dir test expectations, Tailscale Effect typing, default persistComposerContextStrip, README appearance doc link, and marketing Cursor harness label.

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

@coderabbitai

coderabbitaiBot commented May 27, 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: e3efadfb-5f39-4c7c-970d-8aade3f16f07

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
  • Commit unit tests in branch t3code/codex-turn-mapping

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels May 27, 2026
Comment threadapps/server/src/orchestration-v2/EventStore.ts Outdated
Comment on lines +116 to +120
return decodeTranscript({
...metadata,
entries,
});
});

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.

🟢 Lowtestkit/ReplayTranscriptNdjson.ts:116

The call to decodeTranscript at line 116 invokes Schema.decodeUnknownSync, which throws on validation failure. Since this isn't wrapped in Effect.try, any validation error becomes an uncaught exception (defect) instead of a typed ProviderReplayNdjsonParseError. This breaks the function's declared error contract. Consider wrapping the call in Effect.try to catch the exception and convert it to the declared error type.

- return decodeTranscript({- ...metadata,- entries,- });+ return yield* Effect.try({+ try: () =>+ decodeTranscript({+ ...metadata,+ entries,+ }),+ catch: (cause) =>+ new ProviderReplayNdjsonLineParseError({+ lineNumber: lines.length,+ line: "<transcript validation>",+ cause,+ }),+ });
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts around lines 116-120:
The call to `decodeTranscript` at line 116 invokes `Schema.decodeUnknownSync`, which throws on validation failure. Since this isn't wrapped in `Effect.try`, any validation error becomes an uncaught exception (defect) instead of a typed `ProviderReplayNdjsonParseError`. This breaks the function's declared error contract. Consider wrapping the call in `Effect.try` to catch the exception and convert it to the declared error type.
Evidence trail:
apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts lines 50-53: `decodeTranscript = Schema.decodeUnknownSync(ProviderReplayTranscript)` — throws on failure.
Line 116-118: `return decodeTranscript({...metadata, entries})` — called directly inside Effect.gen without Effect.try wrapper.
Lines 55-68 (`parseReplayRecord`): same pattern but correctly wrapped in `Effect.try`.
Line 80: function declares error type `ProviderReplayNdjsonParseError`.
packages/contracts/src/orchestrationV2.ts lines 1561-1568: `ProviderReplayTranscript` schema with `TrimmedNonEmptyString` fields that can fail validation.

Comment threadapps/server/src/orchestration-v2/ProviderAdapterRegistry.ts Outdated
Comment threadpackages/client-runtime/src/wsRpcClient.ts Outdated
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/EventSink.ts
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 1, 2026
…der adapters (t3-29f.6)
Assessment of upstream PR pingdotgg#2829 (pingdotgg/t3code) from juliusmarminge:
WIP wire orchestration v2 provider adapters with Codex and Claude adapters,
event sourcing, provider session management, and replay testkit.
Relevance to target issues:
- pingdotgg#2838 (session resume): HIGH — ProviderSessionManager persists session
IDs and separates startSession/resumeSession operations
- pingdotgg#2778 (subagent hang): MEDIUM — ProviderEventIngestor provides
infrastructure to forward permission events, but UI plumbing not yet wired
- pingdotgg#2886 (thread stuck working): HIGH — event-sourced projections replace
mutable state flags, eliminating sticky "working" states
The PR is a draft (34 commits, not merged). No OpenCode ACP adapter
exists yet in v2 — OpenCode would need its own adapter wired into the
ProviderAdapterRegistry. Recommend watching for merge and adding an
OpenCode adapter post-merge.
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
The upstream PR pingdotgg#2829 added orchestrationV2 methods to the WsRpcClient
interface. The test mock in service.threadSubscriptions.test.ts was
missing the orchestrationV2 property, causing a typecheck failure:
'Property orchestrationV2 is missing in type...'
Added orchestrationV2 mock with dispatchCommand, getThreadProjection,
subscribeShell, and subscribeThread as vi.fn() stubs.
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
The upstream PR pingdotgg#2829 targets a newer Effect version than our fork's
pinned effect@4.0.0-beta.73. Fixes:
- Replace Random.nextUUIDv4 with Crypto.randomUUIDv4 (beta.73 API)
- Fix deterministic Service tag keys to match fork convention
(include file path segments; e.g. Adapters/ClaudeAdapterV2/...)
- Replace Schema.decodeSync with Schema.decodeUnknownEffect inside
Effect.gen generators (tsgo schemaSyncInEffect rule)
- Replace inline Schema.encodeUnknownSync with module-level wrappers
to avoid schemaSyncInEffect rule inside generators
@github-actions

github-actionsBot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🚀 Expo continuous deployment is ready!

  • Project → t3-code
  • Platforms → android, ios
  • Scheme → t3code-preview
🤖 Android🍎 iOS
Fingerprintfe5a51f2e189da69dfc4c2cd458e6cfb5fdff2eaae3bd597809dfd7771d0898f735d172973d4c1c8
Build DetailsBuild Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
App version: 0.1.0
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Build Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
App version: 0.1.0
Git commit: eea0dcae4150df8341606520c074dc651ae7c00a
Update DetailsUpdate Permalink
DetailsBranch: pr-2829
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Update Permalink
DetailsBranch: pr-2829
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Update QR

Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
Comment threadapps/server/src/ws.ts
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
@juliusmarmingejuliusmarminge changed the title WIP: wire orchestration v2 provider adaptersfeat(orchestrator): introduce new orchestratorJun 14, 2026
Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
Comment on lines +109 to +128
Effect.gen(function* () {
const threadId = payloadInput.threadId ?? input.threadId;
const eventId = yield* idAllocator.allocate.event({
threadId,
providerSessionId: input.providerSessionId,
});
const occurredAt = yield* DateTime.now;
return yield* Schema.decodeUnknownEffect(OrchestrationV2DomainEvent)(
compactUndefined({
id: eventId,
type: payloadInput.type,
threadId,
runId: payloadInput.runId ?? input.runId,
nodeId: payloadInput.nodeId ?? input.nodeId,
provider: input.event.provider,
rawEventId: input.rawEventId,
occurredAt,
payload: payloadInput.payload,
}),
);

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.

🟡 Mediumorchestration-v2/ProviderEventIngestor.ts:109

In makeDomainEvent, the ?? operator on lines 121 and 123 treats explicit null as equivalent to undefined, causing payloadInput.runId ?? input.runId to fall back to input.runId when payloadInput.runId is explicitly null. Since the type is readonly runId?: RunId | null, this means explicit null values from the caller (e.g., input.event.node.runId being null on line 172) are incorrectly overwritten instead of preserved. Consider using === undefined checks like lines 161-162 and 210-211, or use payloadInput.runId === undefined ? input.runId : payloadInput.runId.

 const threadId = payloadInput.threadId ?? input.threadId;
- const runId = payloadInput.runId ?? input.runId;- const nodeId = payloadInput.nodeId ?? input.nodeId;+ const runId = payloadInput.runId === undefined ? input.runId : payloadInput.runId;+ const nodeId = payloadInput.nodeId === undefined ? input.nodeId : payloadInput.nodeId;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/ProviderEventIngestor.ts around lines 109-128:
In `makeDomainEvent`, the `??` operator on lines 121 and 123 treats explicit `null` as equivalent to `undefined`, causing `payloadInput.runId ?? input.runId` to fall back to `input.runId` when `payloadInput.runId` is explicitly `null`. Since the type is `readonly runId?: RunId | null`, this means explicit `null` values from the caller (e.g., `input.event.node.runId` being `null` on line 172) are incorrectly overwritten instead of preserved. Consider using `=== undefined` checks like lines 161-162 and 210-211, or use `payloadInput.runId === undefined ? input.runId : payloadInput.runId`.
Evidence trail:
apps/server/src/orchestration-v2/ProviderEventIngestor.ts lines 105-106 (payloadInput type with `RunId | null`), line 121 (`runId: payloadInput.runId ?? input.runId`), line 122 (`nodeId: payloadInput.nodeId ?? input.nodeId`), lines 161-162 and 210-211 (codebase uses `=== undefined` pattern elsewhere). packages/contracts/src/orchestrationV2.ts line 358 (`runId: Schema.NullOr(RunId)` on ExecutionNode - confirms null is a valid value), line 857 (`runId: Schema.optional(RunId)` on EventBase - domain event uses optional/undefined, not null). apps/server/src/orchestration-v2/ProviderEventIngestor.ts lines 80-81 (compactUndefined only strips undefined, not null).

Comment threadapps/server/src/orchestration-v2/Orchestrator.ts
Comment threadapps/web/src/routes/debug.orchestration-v2.tsx Outdated
Comment threadapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 79031a1 to 4e68dcbCompareJune 14, 2026 23:55
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 4e68dcb to c7539b9CompareJune 17, 2026 07:30
Comment threadapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/ProviderSessionManager.ts Outdated
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts Outdated
Comment threadapps/server/src/mcp/OrchestratorMcpService.ts Outdated
Comment threadapps/server/src/ws.ts Outdated
function nativeThreadId(provider: ProviderKind, thread: OrchestrationV2ProviderThread): string {
const id = thread.nativeThreadRef?.nativeId;
if (id === null || id === undefined || id.trim().length === 0) {
throw new ProviderAdapterProtocolError({

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.

🟡 MediumAdapters/AcpAdapterV2.ts:271

When nativeThreadId is called inside Effect.gen generators (e.g., lines 899, 1813), the thrown ProviderAdapterProtocolError becomes an untyped defect instead of a typed failure. This bypasses Effect.mapError and other typed error handlers, causing the error to propagate as an unexpected defect. Consider converting nativeThreadId to return Effect<string, ProviderAdapterProtocolError> and yielding it at each call site, or inlining the validation with yield* new ProviderAdapterProtocolError(...) so the failure is properly typed.

Also found in 1 other location(s)

apps/server/src/orchestration-v2/ThreadManagementService.ts:278

The statement return yield* managementError(...) cannot work correctly because managementError() returns a ThreadManagementError instance, not an Effect. The yield* operator in Effect.gen expects an Effect value. This should be return yield* Effect.fail(managementError(...)). The correct pattern is demonstrated elsewhere in this file (lines 241-246) where Effect.fail(managementError(...)) is properly used. This same bug pattern repeats at lines 291, 333, 357, 374, 390, 406, and 427.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts around line 271:
When `nativeThreadId` is called inside `Effect.gen` generators (e.g., lines 899, 1813), the thrown `ProviderAdapterProtocolError` becomes an untyped defect instead of a typed failure. This bypasses `Effect.mapError` and other typed error handlers, causing the error to propagate as an unexpected defect. Consider converting `nativeThreadId` to return `Effect<string, ProviderAdapterProtocolError>` and yielding it at each call site, or inlining the validation with `yield* new ProviderAdapterProtocolError(...)` so the failure is properly typed.
Evidence trail:
1. nativeThreadId function with throw: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts lines 268-277
2. Call site inside Effect.gen: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 899
3. Call site inside Effect.gen: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 1813
4. Correct yield* pattern for comparison: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 1808
5. ProviderAdapterProtocolError class definition: apps/server/src/orchestration-v2/ProviderAdapter.ts lines 316-327
6. Effect.gen implementation delegating to fromIteratorUnsafe: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 1104-1125
7. fromIteratorUnsafe calling iter.next() without try/catch: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 1285-1307
8. FiberImpl.runLoop catch block converting thrown errors to exitDie: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 646-650
9. die = exitDie producing Effect<never> (untyped): https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts line 947
Also found in 1 other location(s):
- apps/server/src/orchestration-v2/ThreadManagementService.ts:278 -- The statement `return yield* managementError(...)` cannot work correctly because `managementError()` returns a `ThreadManagementError` instance, not an `Effect`. The `yield*` operator in `Effect.gen` expects an Effect value. This should be `return yield* Effect.fail(managementError(...))`. The correct pattern is demonstrated elsewhere in this file (lines 241-246) where `Effect.fail(managementError(...))` is properly used. This same bug pattern repeats at lines 291, 333, 357, 374, 390, 406, and 427.

juliusmarmingeand others added 23 commits September 1, 2026 16:33
R12 follow-up
Insert persistent feedback blocks by their timestamp within the canonical timeline while preserving projected row order. Keep real optimistic sends appended and suppress duplicate local messages already committed by the server.
Implemented by GPT-5.6 Sol via Codex.
R20 follow-up
Give inspector reasoning markdown its projected source thread and retain the explicit environment fallback for proposed plans without a thread reference. Workspace links and images now resolve through their owning environment after removal of the active-environment fallback.
Implemented by GPT-5.6 Sol via Codex.
Read the inclusive cursor, a full history page, and a look-behind row so older history does not terminate after one page.
Finding: P01 pagination termination
Model: GPT-5.6 Sol via Codex
Cancel pending SDK requests before aborting the native session. Preserve per-admission cancellation state and treat stopped initial prompts as interruption instead of provider failure.
Finding: R14 prompt cancellation
Model: GPT-5.6 Sol via Codex
Keep the original cursor owner through ancestor traversal and preserve the history budget across empty intermediate forks. Verify exact paged history against the complete nested projection.
Finding: P01 nested lineage
Model: GPT-5.6 Sol via Codex
Retain pending admission after transient status failures and use one generation-owned retry worker. Ignore stale timers and duplicate evidence so older prompts cannot finish newer steering.
Finding: R14 status reconciliation
Model: GPT-5.6 Sol via Codex
Keep hidden local and inherited rows from consuming history pages. Preserve stop-request dependencies, source-run cutoffs, and imported history while loading related metadata from the selected cohort and using indexed watermark lookups.
Finding: P01 bounded history visibility
Model: GPT-5.6 Sol via Codex
Adapt grouped tool summaries and the floating working timer to V2 run, attempt, and queue state. Bring over the composer, keyboard, and disclosure transitions while retaining the V2 activity inspector and queue controls.
Keep OV2 web composer and grouping behavior intact; share only the existing command label parser with mobile.
Restores main features dropped by the policy replay: #8569 theme wiring,
settings search rework, #8803 workspace-mutation refresh (v2-adapted),
video + image previews (web and mobile, v2-adapted), #8862 Expo glass,
and the round's docs. Timeline thinking rows (#8984) stay on the v2
work-live system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v2 equivalents of main's #8984 and #8922: a "Working for ..." header
anchors the active run, the trailing live tool row survives between
actions in past tense instead of vanishing, and a shimmering Thinking
row marks reasoning gaps. During workspace preparation the header shows
"Setting up worktree..." (driven by the local dispatch flag or the v2
run's preparing status, so remote viewers see it too), the composer
footer span is gone, and draft promotion waits until the run starts or
startup fails instead of navigating mid-preparation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adopts the round's main features into the v2 architecture: the #9023
media rework (streamed videos, media-file assets, protocol-relative
links), #9098 shared live-activity row folded into the v2 working and
thinking rows, the #9084/#9078 Claude model catalog for v2 consumers,
a native #9005 OpenCode child-session abort in the v2 adapter, #9013's
landed LegendList patch, and per-environment sidebar provider entries.
For #8600 the server-side pieces land, but auto-settle evaluation stays
client-side (reading the new server-owned settings) until the v2
orchestrator grows its own settlement reactor; main's v1-only reactor
and coalescer additions are dropped with the rest of the v1 path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

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.

composerVideos and composerOtherFiles are computed but never rendered — the attachment list below still maps composerFiles (line 4034) into the generic FileIcon row, so ComposerVideoThumbnail (line 176), the new PlayIcon import (line 305), and attachVideoThumbnail / isPreviewableComposerVideo have no live consumer here. The result is a split treatment for the same attachment: a staged video is a plain file row in the composer, while the sent message renders it as a playable tile (MessagesTimeline.tsxuserVideos). The three unused locals will also trip unused-symbol checks.

Smallest fix: render composerVideos as thumbnail tiles (ComposerVideoThumbnail + buildExpandedImagePreview, matching the timeline tile) and feed the existing file-row list from composerOtherFiles; if the composer treatment is not landing in this PR, drop the two locals, ComposerVideoThumbnail, and the PlayIcon import instead so the file does not carry a half-wired path.

Posted via Macroscope — UI Consistency

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.

composerVideos and composerOtherFiles are computed but never rendered — the attachment list below still maps composerFiles (line 4034) into the generic FileIcon row, so ComposerVideoThumbnail (line 176), the new PlayIcon import (line 305), and attachVideoThumbnail / isPreviewableComposerVideo have no live consumer here. The result is a split treatment for the same attachment: a staged video is a plain file row in the composer, while the sent message renders it as a playable tile (MessagesTimeline.tsxuserVideos). The three unused locals will also trip unused-symbol checks.

Smallest fix: render composerVideos as thumbnail tiles (ComposerVideoThumbnail + buildExpandedImagePreview, matching the timeline tile) and feed the existing file-row list from composerOtherFiles; if the composer treatment is not landing in this PR, drop the two locals, ComposerVideoThumbnail, and the PlayIcon import instead so the file does not carry a half-wired path.

Posted via Macroscope — UI Consistency

!hasContent &&
(props.selectedThread.session?.status === "running" ||
props.selectedThread.session?.status === "starting");
const showStopAction = props.canStopThread;

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.

Stop hides send while composing

High Severity

showStopAction now follows canStopThread alone and no longer requires an empty composer. While a run is interruptible, typed text or attachments still replace Send/Queue with Stop, so follow-ups cannot be sent or queued until the run is interrupted.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

selectedThreadFeed,
selectedThreadKey,
]);
}, [props.onSendMessage, selectedThreadKey]);

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.

Send always re-anchors the feed

Medium Severity

handleSendMessage now always sets anchorMessageId to the just-sent id. The previous first-message-only policy in resolveThreadFeedSubmissionAnchor is no longer applied, so follow-ups and second outbox sends retarget the live-follow scroll.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

// Dictation keeps that focus while the composer switches to its compact pill.
const composerBottomInset = (
Platform.OS === "android" ? isKeyboardVisible : composerExpanded || composerFocused
)

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.

iOS composer inset ignores focus

Medium Severity

iOS composerBottomInset now keys only on composerExpanded, and the editor no longer updates composerFocused. Focusing the collapsed pill still applies home-indicator padding above the keyboard, leaving a gap between the composer and the IME.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

…ator
Ports #8600's server-owned settlement to orchestration v2 instead of
keeping client-side evaluation. A ThreadSettlementService sweep runs at
startup, on auto-settle settings changes, and once per minute: it
evaluates inactivity and merged or closed pull requests over v2 thread
shells and dispatches the new guarded thread.auto-settle command, which
rejects threads that changed after the sweep's snapshot or carry any
explicit override, then reuses the orchestrator's settle lifecycle.
With the server deciding, the clients drop their effectiveSettled
evaluation and partition on the persisted settledOverride like main.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@macroscopeapp

Copy link
Copy Markdown
Contributor

apps/server/src/cli/app.ts:232-239

(Posted at PR level: an inline comment could not be anchored on this file in the current diff.)

The Effect.tryPromisecatch in runAppCommand collapses six semantically distinct failures into one error whose distinction survives only as an English sentence inside cause: Schema.Defect() — response timeout, oversized response, non-JSON response, schema-invalid response, request-id mismatch, and "connection closed" all become DesktopAppUnreachableError, whose message asserts the desktop app could not be reached and tells the user to start it. In the four response-shaped cases the app did answer, so that caller-visible message is wrong, and cli/app.test.ts asserts on the prose (cause: { message: "The desktop app response is invalid." }), which makes it behavior rather than diagnostics.

The conventions ask that failures be distinguished by stable structural attributes with the message derived only from them, and that a translation boundary wrap only genuinely lower-level failures rather than flatten known ones. Consider having sendDesktopAppActivationRequest reject with tagged errors (or return a tagged result) so the CLI can keep DesktopAppUnreachableError for the connect-level ENOENT/ECONNREFUSED case its message describes, and add e.g. DesktopAppResponseInvalidError { requestId, workspaceRoot, byteLength } and DesktopAppResponseTimeoutError { requestId, timeoutMs } for the rest, each keeping the real socket error as cause where one exists.

Posted via Macroscope — Effect Service Conventions

Posted via Macroscope — Effect Service Conventions

@macroscopeapp

Copy link
Copy Markdown
Contributor

apps/server/src/cli/app.ts:232-239

(Posted at PR level: an inline comment could not be anchored on this file in the current diff.)

The Effect.tryPromisecatch in runAppCommand collapses six semantically distinct failures into one error whose distinction survives only as an English sentence inside cause: Schema.Defect() — response timeout, oversized response, non-JSON response, schema-invalid response, request-id mismatch, and "connection closed" all become DesktopAppUnreachableError, whose message asserts the desktop app could not be reached and tells the user to start it. In the four response-shaped cases the app did answer, so that caller-visible message is wrong, and cli/app.test.ts asserts on the prose (cause: { message: "The desktop app response is invalid." }), which makes it behavior rather than diagnostics.

The conventions ask that failures be distinguished by stable structural attributes with the message derived only from them, and that a translation boundary wrap only genuinely lower-level failures rather than flatten known ones. Consider having sendDesktopAppActivationRequest reject with tagged errors (or return a tagged result) so the CLI can keep DesktopAppUnreachableError for the connect-level ENOENT/ECONNREFUSED case its message describes, and add e.g. DesktopAppResponseInvalidError { requestId, workspaceRoot, byteLength } and DesktopAppResponseTimeoutError { requestId, timeoutMs } for the rest, each keeping the real socket error as cause where one exists.

Posted via Macroscope — Effect Service Conventions

Posted via Macroscope — Effect Service Conventions

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

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

@@ -2085,12 +2062,9 @@ export default function Sidebar() {
// memo exactly at the next wake boundary.
void snoozeWakeTick;
const preciseNow = new Date().toISOString();
const visible = threads.filter(

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.

Web sidebar pinned threads partition completely broken

High Severity

The partition loop declares const pinned: EnvironmentThreadShell[] = [] at line 2068 but never pushes anything into it. The old code had an else if (thread.pinnedAt != null) { pinned.push(thread); } branch between the snoozed and settled checks, but it was removed in this refactor. All pinned threads now fall into active instead. The mobile equivalent in threadListV2.ts correctly retains else if (thread.pinnedAt != null) { pinned.push(thread); } at line 421. This means pinnedThreads is always empty, the pinned section never renders, DnD reordering is inert, and reorderablePinnedKeys is an empty set.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

thread.session?.status === "running" &&
thread.session.activeTurnId != null
) {
if (action === "archive" && threadRuntimeIsActive(thread.runtime)) {

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.

Archive guard stricter than old threadCanArchive behavior

Medium Severity

The comment says "Archive keeps its original, narrower guard" but threadRuntimeIsActive is actually wider (more restrictive) than the replaced threadCanArchive. threadCanArchive allowed archiving queued threads without an activeRunId and all waiting threads; threadRuntimeIsActive blocks both. The old threadCanArchive is still imported at line 21 but now unused, confirming a mistaken substitution rather than an intentional tightening.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(orchestrator): introduce new orchestrator - #2829

Open
juliusmarminge wants to merge 330 commits into
mainfrom
t3code/codex-turn-mapping
Open

feat(orchestrator): introduce new orchestrator#2829
juliusmarminge wants to merge 330 commits into
mainfrom
t3code/codex-turn-mapping

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 27, 2026

Copy link
Copy Markdown
Member

Summary

  • wire orchestration V2 provider adapter registry/factory flow for Codex and Claude provider instances
  • add Claude replay/query primitives, native fork/rollback fixtures, subagent fixture coverage, and provider replay harness updates
  • update debugger model/provider picker and improve user-facing orchestration errors

Validation

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test -- src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts -t claudeAgent
  • bun run test -- src/orchestration-v2/testkit/ClaudeReplayFixtures.integration.test.ts
  • bun run test -- src/orchestration-v2/testkit/ThreadFork.integration.test.ts -t Claude

Notes

  • Draft PR for review of current branch state. Codex all-provider replay still needs schema alignment with latest app-server behavior before it can be treated as a full-suite signal.

Closes

Verified against the branch with code/commit evidence.

High confidence

Closes#4952
Closes#4873
Closes#4775
Closes#4795
Closes#4710
Closes#4668
Closes#4619
Closes#4584
Closes#4561
Closes#4713
Closes#4198
Closes#4452
Closes#3797
Closes#4232
Closes#3666
Closes#3580
Closes#2785
Closes#2789
Closes#3138
Closes#1404
Closes#231
Closes#216

Medium confidence (under review)

Closes#4568
Closes#4766
Closes#4495
Closes#4456
Closes#4399
Closes#3744
Closes#2921
Closes#3624
Closes#3149
Closes#2336
Closes#538
Closes#2173
Closes#2065

Note

Introduce new Orchestration V2 runtime with provider adapters, persistence, and client support

  • Adds a complete Orchestration V2 server runtime: event store, projection store, event sink, effect outbox/worker, ID allocator, command policy, thread management/launch/lifecycle/fork/settlement services, checkpoint capture/rollback, provider session manager, runtime recovery, and context handoff services across orchestration-v2/
  • Adds V2 provider adapters for Codex, Claude, Cursor, Grok, OpenCode, and ACP Registry, plus a provider adapter registry and driver framework with replay testkits for each adapter
  • Adds persistence migrations 044–052 for V2 event/projection tables, effect outbox, provider-session bindings, thread-launch workflows, application event sourcing, scheduled tasks, and legacy V1 import state
  • Adds MCP orchestration and worktree toolkits, a project service with enrichment, scheduled-task service, HTTP API layer, and a legacy V1 thread importer that reconciles old threads into V2 events
  • Updates web and mobile clients to consume V2 thread projections, shell snapshots, runtime status, queued-run management, thread relationships, history paging, and scheduled-task controls
  • Risk: legacy session/latestTurn fields are replaced by runtime/latestRun across web and mobile thread state; out-of-tree consumers of the old shell snapshot schema or V1 RPC methods will break. Cache schema version bumped to 3, discarding V1 cache envelopes on decode failure. Orchestration protocol version 2 is now required for client connections.

Macroscope summarized 6081201.


Note

High Risk
Mobile thread persistence, archive/stop guards, and approval/user-input handling now depend on V2 runtime semantics and a bumped cache schema, alongside removal of PR transfer regression reporting.

Overview
Removes the automated “thread transfer impact” PR comment pipeline by deleting the thread-transfer-report workflow and its trusted publisher scripts/tests, while CI still may emit transfer budget artifacts from the main workflow.

Mobile is wired to orchestration V2 end-to-end: SQLite shell/thread caches now use the shared ORCHESTRATION_CACHE_SCHEMA_VERSION and V2 snapshot shapes; the connection runtime loads bounded thread history plus a history controller instead of the old unbounded loader. Thread list/detail flows switch from legacy session/turn fields to runtime summaries (thread.runtime, threadRuntimeIsActive, dedicated archive eligibility), runtime request IDs for approvals and user input (including read-only UI when responseCapability is not live), and checkpoint/review data derived from thread projections rather than embedded checkpoint lists.

Thread UX additions and polish include visit watermark dispatch on open, a queue control and relationships banner on the feed, progressive history controls, a thread activity inspector with rollback, shared brand mark assets, extra adaptive theme tokens, and copy tweaks (“coding runtime”). CI installs build-essential so ACP process-tree pthread fixtures compile instead of soft-skipping. Smaller follow-ons: desktop environment user-data dir test expectations, Tailscale Effect typing, default persistComposerContextStrip, README appearance doc link, and marketing Cursor harness label.

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

@coderabbitai

coderabbitaiBot commented May 27, 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: e3efadfb-5f39-4c7c-970d-8aade3f16f07

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
  • Commit unit tests in branch t3code/codex-turn-mapping

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels May 27, 2026
Comment threadapps/server/src/orchestration-v2/EventStore.ts Outdated
Comment on lines +116 to +120
return decodeTranscript({
...metadata,
entries,
});
});

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.

🟢 Lowtestkit/ReplayTranscriptNdjson.ts:116

The call to decodeTranscript at line 116 invokes Schema.decodeUnknownSync, which throws on validation failure. Since this isn't wrapped in Effect.try, any validation error becomes an uncaught exception (defect) instead of a typed ProviderReplayNdjsonParseError. This breaks the function's declared error contract. Consider wrapping the call in Effect.try to catch the exception and convert it to the declared error type.

- return decodeTranscript({- ...metadata,- entries,- });+ return yield* Effect.try({+ try: () =>+ decodeTranscript({+ ...metadata,+ entries,+ }),+ catch: (cause) =>+ new ProviderReplayNdjsonLineParseError({+ lineNumber: lines.length,+ line: "<transcript validation>",+ cause,+ }),+ });
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts around lines 116-120:
The call to `decodeTranscript` at line 116 invokes `Schema.decodeUnknownSync`, which throws on validation failure. Since this isn't wrapped in `Effect.try`, any validation error becomes an uncaught exception (defect) instead of a typed `ProviderReplayNdjsonParseError`. This breaks the function's declared error contract. Consider wrapping the call in `Effect.try` to catch the exception and convert it to the declared error type.
Evidence trail:
apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts lines 50-53: `decodeTranscript = Schema.decodeUnknownSync(ProviderReplayTranscript)` — throws on failure.
Line 116-118: `return decodeTranscript({...metadata, entries})` — called directly inside Effect.gen without Effect.try wrapper.
Lines 55-68 (`parseReplayRecord`): same pattern but correctly wrapped in `Effect.try`.
Line 80: function declares error type `ProviderReplayNdjsonParseError`.
packages/contracts/src/orchestrationV2.ts lines 1561-1568: `ProviderReplayTranscript` schema with `TrimmedNonEmptyString` fields that can fail validation.

Comment threadapps/server/src/orchestration-v2/ProviderAdapterRegistry.ts Outdated
Comment threadpackages/client-runtime/src/wsRpcClient.ts Outdated
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/EventSink.ts
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 1, 2026
…der adapters (t3-29f.6)
Assessment of upstream PR pingdotgg#2829 (pingdotgg/t3code) from juliusmarminge:
WIP wire orchestration v2 provider adapters with Codex and Claude adapters,
event sourcing, provider session management, and replay testkit.
Relevance to target issues:
- pingdotgg#2838 (session resume): HIGH — ProviderSessionManager persists session
IDs and separates startSession/resumeSession operations
- pingdotgg#2778 (subagent hang): MEDIUM — ProviderEventIngestor provides
infrastructure to forward permission events, but UI plumbing not yet wired
- pingdotgg#2886 (thread stuck working): HIGH — event-sourced projections replace
mutable state flags, eliminating sticky "working" states
The PR is a draft (34 commits, not merged). No OpenCode ACP adapter
exists yet in v2 — OpenCode would need its own adapter wired into the
ProviderAdapterRegistry. Recommend watching for merge and adding an
OpenCode adapter post-merge.
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
The upstream PR pingdotgg#2829 added orchestrationV2 methods to the WsRpcClient
interface. The test mock in service.threadSubscriptions.test.ts was
missing the orchestrationV2 property, causing a typecheck failure:
'Property orchestrationV2 is missing in type...'
Added orchestrationV2 mock with dispatchCommand, getThreadProjection,
subscribeShell, and subscribeThread as vi.fn() stubs.
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
The upstream PR pingdotgg#2829 targets a newer Effect version than our fork's
pinned effect@4.0.0-beta.73. Fixes:
- Replace Random.nextUUIDv4 with Crypto.randomUUIDv4 (beta.73 API)
- Fix deterministic Service tag keys to match fork convention
(include file path segments; e.g. Adapters/ClaudeAdapterV2/...)
- Replace Schema.decodeSync with Schema.decodeUnknownEffect inside
Effect.gen generators (tsgo schemaSyncInEffect rule)
- Replace inline Schema.encodeUnknownSync with module-level wrappers
to avoid schemaSyncInEffect rule inside generators
@github-actions

github-actionsBot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🚀 Expo continuous deployment is ready!

  • Project → t3-code
  • Platforms → android, ios
  • Scheme → t3code-preview
🤖 Android🍎 iOS
Fingerprintfe5a51f2e189da69dfc4c2cd458e6cfb5fdff2eaae3bd597809dfd7771d0898f735d172973d4c1c8
Build DetailsBuild Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
App version: 0.1.0
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Build Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
App version: 0.1.0
Git commit: eea0dcae4150df8341606520c074dc651ae7c00a
Update DetailsUpdate Permalink
DetailsBranch: pr-2829
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Update Permalink
DetailsBranch: pr-2829
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Update QR

Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
Comment threadapps/server/src/ws.ts
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
@juliusmarmingejuliusmarminge changed the title WIP: wire orchestration v2 provider adaptersfeat(orchestrator): introduce new orchestratorJun 14, 2026
Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
Comment on lines +109 to +128
Effect.gen(function* () {
const threadId = payloadInput.threadId ?? input.threadId;
const eventId = yield* idAllocator.allocate.event({
threadId,
providerSessionId: input.providerSessionId,
});
const occurredAt = yield* DateTime.now;
return yield* Schema.decodeUnknownEffect(OrchestrationV2DomainEvent)(
compactUndefined({
id: eventId,
type: payloadInput.type,
threadId,
runId: payloadInput.runId ?? input.runId,
nodeId: payloadInput.nodeId ?? input.nodeId,
provider: input.event.provider,
rawEventId: input.rawEventId,
occurredAt,
payload: payloadInput.payload,
}),
);

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.

🟡 Mediumorchestration-v2/ProviderEventIngestor.ts:109

In makeDomainEvent, the ?? operator on lines 121 and 123 treats explicit null as equivalent to undefined, causing payloadInput.runId ?? input.runId to fall back to input.runId when payloadInput.runId is explicitly null. Since the type is readonly runId?: RunId | null, this means explicit null values from the caller (e.g., input.event.node.runId being null on line 172) are incorrectly overwritten instead of preserved. Consider using === undefined checks like lines 161-162 and 210-211, or use payloadInput.runId === undefined ? input.runId : payloadInput.runId.

 const threadId = payloadInput.threadId ?? input.threadId;
- const runId = payloadInput.runId ?? input.runId;- const nodeId = payloadInput.nodeId ?? input.nodeId;+ const runId = payloadInput.runId === undefined ? input.runId : payloadInput.runId;+ const nodeId = payloadInput.nodeId === undefined ? input.nodeId : payloadInput.nodeId;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/ProviderEventIngestor.ts around lines 109-128:
In `makeDomainEvent`, the `??` operator on lines 121 and 123 treats explicit `null` as equivalent to `undefined`, causing `payloadInput.runId ?? input.runId` to fall back to `input.runId` when `payloadInput.runId` is explicitly `null`. Since the type is `readonly runId?: RunId | null`, this means explicit `null` values from the caller (e.g., `input.event.node.runId` being `null` on line 172) are incorrectly overwritten instead of preserved. Consider using `=== undefined` checks like lines 161-162 and 210-211, or use `payloadInput.runId === undefined ? input.runId : payloadInput.runId`.
Evidence trail:
apps/server/src/orchestration-v2/ProviderEventIngestor.ts lines 105-106 (payloadInput type with `RunId | null`), line 121 (`runId: payloadInput.runId ?? input.runId`), line 122 (`nodeId: payloadInput.nodeId ?? input.nodeId`), lines 161-162 and 210-211 (codebase uses `=== undefined` pattern elsewhere). packages/contracts/src/orchestrationV2.ts line 358 (`runId: Schema.NullOr(RunId)` on ExecutionNode - confirms null is a valid value), line 857 (`runId: Schema.optional(RunId)` on EventBase - domain event uses optional/undefined, not null). apps/server/src/orchestration-v2/ProviderEventIngestor.ts lines 80-81 (compactUndefined only strips undefined, not null).

Comment threadapps/server/src/orchestration-v2/Orchestrator.ts
Comment threadapps/web/src/routes/debug.orchestration-v2.tsx Outdated
Comment threadapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 79031a1 to 4e68dcbCompareJune 14, 2026 23:55
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 4e68dcb to c7539b9CompareJune 17, 2026 07:30
Comment threadapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/ProviderSessionManager.ts Outdated
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts Outdated
Comment threadapps/server/src/mcp/OrchestratorMcpService.ts Outdated
Comment threadapps/server/src/ws.ts Outdated
function nativeThreadId(provider: ProviderKind, thread: OrchestrationV2ProviderThread): string {
const id = thread.nativeThreadRef?.nativeId;
if (id === null || id === undefined || id.trim().length === 0) {
throw new ProviderAdapterProtocolError({

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.

🟡 MediumAdapters/AcpAdapterV2.ts:271

When nativeThreadId is called inside Effect.gen generators (e.g., lines 899, 1813), the thrown ProviderAdapterProtocolError becomes an untyped defect instead of a typed failure. This bypasses Effect.mapError and other typed error handlers, causing the error to propagate as an unexpected defect. Consider converting nativeThreadId to return Effect<string, ProviderAdapterProtocolError> and yielding it at each call site, or inlining the validation with yield* new ProviderAdapterProtocolError(...) so the failure is properly typed.

Also found in 1 other location(s)

apps/server/src/orchestration-v2/ThreadManagementService.ts:278

The statement return yield* managementError(...) cannot work correctly because managementError() returns a ThreadManagementError instance, not an Effect. The yield* operator in Effect.gen expects an Effect value. This should be return yield* Effect.fail(managementError(...)). The correct pattern is demonstrated elsewhere in this file (lines 241-246) where Effect.fail(managementError(...)) is properly used. This same bug pattern repeats at lines 291, 333, 357, 374, 390, 406, and 427.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts around line 271:
When `nativeThreadId` is called inside `Effect.gen` generators (e.g., lines 899, 1813), the thrown `ProviderAdapterProtocolError` becomes an untyped defect instead of a typed failure. This bypasses `Effect.mapError` and other typed error handlers, causing the error to propagate as an unexpected defect. Consider converting `nativeThreadId` to return `Effect<string, ProviderAdapterProtocolError>` and yielding it at each call site, or inlining the validation with `yield* new ProviderAdapterProtocolError(...)` so the failure is properly typed.
Evidence trail:
1. nativeThreadId function with throw: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts lines 268-277
2. Call site inside Effect.gen: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 899
3. Call site inside Effect.gen: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 1813
4. Correct yield* pattern for comparison: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 1808
5. ProviderAdapterProtocolError class definition: apps/server/src/orchestration-v2/ProviderAdapter.ts lines 316-327
6. Effect.gen implementation delegating to fromIteratorUnsafe: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 1104-1125
7. fromIteratorUnsafe calling iter.next() without try/catch: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 1285-1307
8. FiberImpl.runLoop catch block converting thrown errors to exitDie: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 646-650
9. die = exitDie producing Effect<never> (untyped): https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts line 947
Also found in 1 other location(s):
- apps/server/src/orchestration-v2/ThreadManagementService.ts:278 -- The statement `return yield* managementError(...)` cannot work correctly because `managementError()` returns a `ThreadManagementError` instance, not an `Effect`. The `yield*` operator in `Effect.gen` expects an Effect value. This should be `return yield* Effect.fail(managementError(...))`. The correct pattern is demonstrated elsewhere in this file (lines 241-246) where `Effect.fail(managementError(...))` is properly used. This same bug pattern repeats at lines 291, 333, 357, 374, 390, 406, and 427.

juliusmarmingeand others added 23 commits September 1, 2026 16:33
R12 follow-up
Insert persistent feedback blocks by their timestamp within the canonical timeline while preserving projected row order. Keep real optimistic sends appended and suppress duplicate local messages already committed by the server.
Implemented by GPT-5.6 Sol via Codex.
R20 follow-up
Give inspector reasoning markdown its projected source thread and retain the explicit environment fallback for proposed plans without a thread reference. Workspace links and images now resolve through their owning environment after removal of the active-environment fallback.
Implemented by GPT-5.6 Sol via Codex.
Read the inclusive cursor, a full history page, and a look-behind row so older history does not terminate after one page.
Finding: P01 pagination termination
Model: GPT-5.6 Sol via Codex
Cancel pending SDK requests before aborting the native session. Preserve per-admission cancellation state and treat stopped initial prompts as interruption instead of provider failure.
Finding: R14 prompt cancellation
Model: GPT-5.6 Sol via Codex
Keep the original cursor owner through ancestor traversal and preserve the history budget across empty intermediate forks. Verify exact paged history against the complete nested projection.
Finding: P01 nested lineage
Model: GPT-5.6 Sol via Codex
Retain pending admission after transient status failures and use one generation-owned retry worker. Ignore stale timers and duplicate evidence so older prompts cannot finish newer steering.
Finding: R14 status reconciliation
Model: GPT-5.6 Sol via Codex
Keep hidden local and inherited rows from consuming history pages. Preserve stop-request dependencies, source-run cutoffs, and imported history while loading related metadata from the selected cohort and using indexed watermark lookups.
Finding: P01 bounded history visibility
Model: GPT-5.6 Sol via Codex
Adapt grouped tool summaries and the floating working timer to V2 run, attempt, and queue state. Bring over the composer, keyboard, and disclosure transitions while retaining the V2 activity inspector and queue controls.
Keep OV2 web composer and grouping behavior intact; share only the existing command label parser with mobile.
Restores main features dropped by the policy replay: #8569 theme wiring,
settings search rework, #8803 workspace-mutation refresh (v2-adapted),
video + image previews (web and mobile, v2-adapted), #8862 Expo glass,
and the round's docs. Timeline thinking rows (#8984) stay on the v2
work-live system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v2 equivalents of main's #8984 and #8922: a "Working for ..." header
anchors the active run, the trailing live tool row survives between
actions in past tense instead of vanishing, and a shimmering Thinking
row marks reasoning gaps. During workspace preparation the header shows
"Setting up worktree..." (driven by the local dispatch flag or the v2
run's preparing status, so remote viewers see it too), the composer
footer span is gone, and draft promotion waits until the run starts or
startup fails instead of navigating mid-preparation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adopts the round's main features into the v2 architecture: the #9023
media rework (streamed videos, media-file assets, protocol-relative
links), #9098 shared live-activity row folded into the v2 working and
thinking rows, the #9084/#9078 Claude model catalog for v2 consumers,
a native #9005 OpenCode child-session abort in the v2 adapter, #9013's
landed LegendList patch, and per-environment sidebar provider entries.
For #8600 the server-side pieces land, but auto-settle evaluation stays
client-side (reading the new server-owned settings) until the v2
orchestrator grows its own settlement reactor; main's v1-only reactor
and coalescer additions are dropped with the rest of the v1 path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

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.

composerVideos and composerOtherFiles are computed but never rendered — the attachment list below still maps composerFiles (line 4034) into the generic FileIcon row, so ComposerVideoThumbnail (line 176), the new PlayIcon import (line 305), and attachVideoThumbnail / isPreviewableComposerVideo have no live consumer here. The result is a split treatment for the same attachment: a staged video is a plain file row in the composer, while the sent message renders it as a playable tile (MessagesTimeline.tsxuserVideos). The three unused locals will also trip unused-symbol checks.

Smallest fix: render composerVideos as thumbnail tiles (ComposerVideoThumbnail + buildExpandedImagePreview, matching the timeline tile) and feed the existing file-row list from composerOtherFiles; if the composer treatment is not landing in this PR, drop the two locals, ComposerVideoThumbnail, and the PlayIcon import instead so the file does not carry a half-wired path.

Posted via Macroscope — UI Consistency

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.

composerVideos and composerOtherFiles are computed but never rendered — the attachment list below still maps composerFiles (line 4034) into the generic FileIcon row, so ComposerVideoThumbnail (line 176), the new PlayIcon import (line 305), and attachVideoThumbnail / isPreviewableComposerVideo have no live consumer here. The result is a split treatment for the same attachment: a staged video is a plain file row in the composer, while the sent message renders it as a playable tile (MessagesTimeline.tsxuserVideos). The three unused locals will also trip unused-symbol checks.

Smallest fix: render composerVideos as thumbnail tiles (ComposerVideoThumbnail + buildExpandedImagePreview, matching the timeline tile) and feed the existing file-row list from composerOtherFiles; if the composer treatment is not landing in this PR, drop the two locals, ComposerVideoThumbnail, and the PlayIcon import instead so the file does not carry a half-wired path.

Posted via Macroscope — UI Consistency

!hasContent &&
(props.selectedThread.session?.status === "running" ||
props.selectedThread.session?.status === "starting");
const showStopAction = props.canStopThread;

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.

Stop hides send while composing

High Severity

showStopAction now follows canStopThread alone and no longer requires an empty composer. While a run is interruptible, typed text or attachments still replace Send/Queue with Stop, so follow-ups cannot be sent or queued until the run is interrupted.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

selectedThreadFeed,
selectedThreadKey,
]);
}, [props.onSendMessage, selectedThreadKey]);

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.

Send always re-anchors the feed

Medium Severity

handleSendMessage now always sets anchorMessageId to the just-sent id. The previous first-message-only policy in resolveThreadFeedSubmissionAnchor is no longer applied, so follow-ups and second outbox sends retarget the live-follow scroll.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

// Dictation keeps that focus while the composer switches to its compact pill.
const composerBottomInset = (
Platform.OS === "android" ? isKeyboardVisible : composerExpanded || composerFocused
)

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.

iOS composer inset ignores focus

Medium Severity

iOS composerBottomInset now keys only on composerExpanded, and the editor no longer updates composerFocused. Focusing the collapsed pill still applies home-indicator padding above the keyboard, leaving a gap between the composer and the IME.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

…ator
Ports #8600's server-owned settlement to orchestration v2 instead of
keeping client-side evaluation. A ThreadSettlementService sweep runs at
startup, on auto-settle settings changes, and once per minute: it
evaluates inactivity and merged or closed pull requests over v2 thread
shells and dispatches the new guarded thread.auto-settle command, which
rejects threads that changed after the sweep's snapshot or carry any
explicit override, then reuses the orchestrator's settle lifecycle.
With the server deciding, the clients drop their effectiveSettled
evaluation and partition on the persisted settledOverride like main.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@macroscopeapp

Copy link
Copy Markdown
Contributor

apps/server/src/cli/app.ts:232-239

(Posted at PR level: an inline comment could not be anchored on this file in the current diff.)

The Effect.tryPromisecatch in runAppCommand collapses six semantically distinct failures into one error whose distinction survives only as an English sentence inside cause: Schema.Defect() — response timeout, oversized response, non-JSON response, schema-invalid response, request-id mismatch, and "connection closed" all become DesktopAppUnreachableError, whose message asserts the desktop app could not be reached and tells the user to start it. In the four response-shaped cases the app did answer, so that caller-visible message is wrong, and cli/app.test.ts asserts on the prose (cause: { message: "The desktop app response is invalid." }), which makes it behavior rather than diagnostics.

The conventions ask that failures be distinguished by stable structural attributes with the message derived only from them, and that a translation boundary wrap only genuinely lower-level failures rather than flatten known ones. Consider having sendDesktopAppActivationRequest reject with tagged errors (or return a tagged result) so the CLI can keep DesktopAppUnreachableError for the connect-level ENOENT/ECONNREFUSED case its message describes, and add e.g. DesktopAppResponseInvalidError { requestId, workspaceRoot, byteLength } and DesktopAppResponseTimeoutError { requestId, timeoutMs } for the rest, each keeping the real socket error as cause where one exists.

Posted via Macroscope — Effect Service Conventions

Posted via Macroscope — Effect Service Conventions

@macroscopeapp

Copy link
Copy Markdown
Contributor

apps/server/src/cli/app.ts:232-239

(Posted at PR level: an inline comment could not be anchored on this file in the current diff.)

The Effect.tryPromisecatch in runAppCommand collapses six semantically distinct failures into one error whose distinction survives only as an English sentence inside cause: Schema.Defect() — response timeout, oversized response, non-JSON response, schema-invalid response, request-id mismatch, and "connection closed" all become DesktopAppUnreachableError, whose message asserts the desktop app could not be reached and tells the user to start it. In the four response-shaped cases the app did answer, so that caller-visible message is wrong, and cli/app.test.ts asserts on the prose (cause: { message: "The desktop app response is invalid." }), which makes it behavior rather than diagnostics.

The conventions ask that failures be distinguished by stable structural attributes with the message derived only from them, and that a translation boundary wrap only genuinely lower-level failures rather than flatten known ones. Consider having sendDesktopAppActivationRequest reject with tagged errors (or return a tagged result) so the CLI can keep DesktopAppUnreachableError for the connect-level ENOENT/ECONNREFUSED case its message describes, and add e.g. DesktopAppResponseInvalidError { requestId, workspaceRoot, byteLength } and DesktopAppResponseTimeoutError { requestId, timeoutMs } for the rest, each keeping the real socket error as cause where one exists.

Posted via Macroscope — Effect Service Conventions

Posted via Macroscope — Effect Service Conventions

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

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

@@ -2085,12 +2062,9 @@ export default function Sidebar() {
// memo exactly at the next wake boundary.
void snoozeWakeTick;
const preciseNow = new Date().toISOString();
const visible = threads.filter(

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.

Web sidebar pinned threads partition completely broken

High Severity

The partition loop declares const pinned: EnvironmentThreadShell[] = [] at line 2068 but never pushes anything into it. The old code had an else if (thread.pinnedAt != null) { pinned.push(thread); } branch between the snoozed and settled checks, but it was removed in this refactor. All pinned threads now fall into active instead. The mobile equivalent in threadListV2.ts correctly retains else if (thread.pinnedAt != null) { pinned.push(thread); } at line 421. This means pinnedThreads is always empty, the pinned section never renders, DnD reordering is inert, and reorderablePinnedKeys is an empty set.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

thread.session?.status === "running" &&
thread.session.activeTurnId != null
) {
if (action === "archive" && threadRuntimeIsActive(thread.runtime)) {

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.

Archive guard stricter than old threadCanArchive behavior

Medium Severity

The comment says "Archive keeps its original, narrower guard" but threadRuntimeIsActive is actually wider (more restrictive) than the replaced threadCanArchive. threadCanArchive allowed archiving queued threads without an activeRunId and all waiting threads; threadRuntimeIsActive blocks both. The old threadCanArchive is still imported at line 21 but now unused, confirming a mistaken substitution rather than an intentional tightening.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(orchestrator): introduce new orchestrator - #2829

Open
juliusmarminge wants to merge 330 commits into
mainfrom
t3code/codex-turn-mapping
Open

feat(orchestrator): introduce new orchestrator#2829
juliusmarminge wants to merge 330 commits into
mainfrom
t3code/codex-turn-mapping

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented May 27, 2026

Copy link
Copy Markdown
Member

Summary

  • wire orchestration V2 provider adapter registry/factory flow for Codex and Claude provider instances
  • add Claude replay/query primitives, native fork/rollback fixtures, subagent fixture coverage, and provider replay harness updates
  • update debugger model/provider picker and improve user-facing orchestration errors

Validation

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test -- src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts -t claudeAgent
  • bun run test -- src/orchestration-v2/testkit/ClaudeReplayFixtures.integration.test.ts
  • bun run test -- src/orchestration-v2/testkit/ThreadFork.integration.test.ts -t Claude

Notes

  • Draft PR for review of current branch state. Codex all-provider replay still needs schema alignment with latest app-server behavior before it can be treated as a full-suite signal.

Closes

Verified against the branch with code/commit evidence.

High confidence

Closes#4952
Closes#4873
Closes#4775
Closes#4795
Closes#4710
Closes#4668
Closes#4619
Closes#4584
Closes#4561
Closes#4713
Closes#4198
Closes#4452
Closes#3797
Closes#4232
Closes#3666
Closes#3580
Closes#2785
Closes#2789
Closes#3138
Closes#1404
Closes#231
Closes#216

Medium confidence (under review)

Closes#4568
Closes#4766
Closes#4495
Closes#4456
Closes#4399
Closes#3744
Closes#2921
Closes#3624
Closes#3149
Closes#2336
Closes#538
Closes#2173
Closes#2065

Note

Introduce new Orchestration V2 runtime with provider adapters, persistence, and client support

  • Adds a complete Orchestration V2 server runtime: event store, projection store, event sink, effect outbox/worker, ID allocator, command policy, thread management/launch/lifecycle/fork/settlement services, checkpoint capture/rollback, provider session manager, runtime recovery, and context handoff services across orchestration-v2/
  • Adds V2 provider adapters for Codex, Claude, Cursor, Grok, OpenCode, and ACP Registry, plus a provider adapter registry and driver framework with replay testkits for each adapter
  • Adds persistence migrations 044–052 for V2 event/projection tables, effect outbox, provider-session bindings, thread-launch workflows, application event sourcing, scheduled tasks, and legacy V1 import state
  • Adds MCP orchestration and worktree toolkits, a project service with enrichment, scheduled-task service, HTTP API layer, and a legacy V1 thread importer that reconciles old threads into V2 events
  • Updates web and mobile clients to consume V2 thread projections, shell snapshots, runtime status, queued-run management, thread relationships, history paging, and scheduled-task controls
  • Risk: legacy session/latestTurn fields are replaced by runtime/latestRun across web and mobile thread state; out-of-tree consumers of the old shell snapshot schema or V1 RPC methods will break. Cache schema version bumped to 3, discarding V1 cache envelopes on decode failure. Orchestration protocol version 2 is now required for client connections.

Macroscope summarized 6081201.


Note

High Risk
Mobile thread persistence, archive/stop guards, and approval/user-input handling now depend on V2 runtime semantics and a bumped cache schema, alongside removal of PR transfer regression reporting.

Overview
Removes the automated “thread transfer impact” PR comment pipeline by deleting the thread-transfer-report workflow and its trusted publisher scripts/tests, while CI still may emit transfer budget artifacts from the main workflow.

Mobile is wired to orchestration V2 end-to-end: SQLite shell/thread caches now use the shared ORCHESTRATION_CACHE_SCHEMA_VERSION and V2 snapshot shapes; the connection runtime loads bounded thread history plus a history controller instead of the old unbounded loader. Thread list/detail flows switch from legacy session/turn fields to runtime summaries (thread.runtime, threadRuntimeIsActive, dedicated archive eligibility), runtime request IDs for approvals and user input (including read-only UI when responseCapability is not live), and checkpoint/review data derived from thread projections rather than embedded checkpoint lists.

Thread UX additions and polish include visit watermark dispatch on open, a queue control and relationships banner on the feed, progressive history controls, a thread activity inspector with rollback, shared brand mark assets, extra adaptive theme tokens, and copy tweaks (“coding runtime”). CI installs build-essential so ACP process-tree pthread fixtures compile instead of soft-skipping. Smaller follow-ons: desktop environment user-data dir test expectations, Tailscale Effect typing, default persistComposerContextStrip, README appearance doc link, and marketing Cursor harness label.

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

@coderabbitai

coderabbitaiBot commented May 27, 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: e3efadfb-5f39-4c7c-970d-8aade3f16f07

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
  • Commit unit tests in branch t3code/codex-turn-mapping

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels May 27, 2026
Comment threadapps/server/src/orchestration-v2/EventStore.ts Outdated
Comment on lines +116 to +120
return decodeTranscript({
...metadata,
entries,
});
});

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.

🟢 Lowtestkit/ReplayTranscriptNdjson.ts:116

The call to decodeTranscript at line 116 invokes Schema.decodeUnknownSync, which throws on validation failure. Since this isn't wrapped in Effect.try, any validation error becomes an uncaught exception (defect) instead of a typed ProviderReplayNdjsonParseError. This breaks the function's declared error contract. Consider wrapping the call in Effect.try to catch the exception and convert it to the declared error type.

- return decodeTranscript({- ...metadata,- entries,- });+ return yield* Effect.try({+ try: () =>+ decodeTranscript({+ ...metadata,+ entries,+ }),+ catch: (cause) =>+ new ProviderReplayNdjsonLineParseError({+ lineNumber: lines.length,+ line: "<transcript validation>",+ cause,+ }),+ });
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts around lines 116-120:
The call to `decodeTranscript` at line 116 invokes `Schema.decodeUnknownSync`, which throws on validation failure. Since this isn't wrapped in `Effect.try`, any validation error becomes an uncaught exception (defect) instead of a typed `ProviderReplayNdjsonParseError`. This breaks the function's declared error contract. Consider wrapping the call in `Effect.try` to catch the exception and convert it to the declared error type.
Evidence trail:
apps/server/src/orchestration-v2/testkit/ReplayTranscriptNdjson.ts lines 50-53: `decodeTranscript = Schema.decodeUnknownSync(ProviderReplayTranscript)` — throws on failure.
Line 116-118: `return decodeTranscript({...metadata, entries})` — called directly inside Effect.gen without Effect.try wrapper.
Lines 55-68 (`parseReplayRecord`): same pattern but correctly wrapped in `Effect.try`.
Line 80: function declares error type `ProviderReplayNdjsonParseError`.
packages/contracts/src/orchestrationV2.ts lines 1561-1568: `ProviderReplayTranscript` schema with `TrimmedNonEmptyString` fields that can fail validation.

Comment threadapps/server/src/orchestration-v2/ProviderAdapterRegistry.ts Outdated
Comment threadpackages/client-runtime/src/wsRpcClient.ts Outdated
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/EventSink.ts
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 1, 2026
…der adapters (t3-29f.6)
Assessment of upstream PR pingdotgg#2829 (pingdotgg/t3code) from juliusmarminge:
WIP wire orchestration v2 provider adapters with Codex and Claude adapters,
event sourcing, provider session management, and replay testkit.
Relevance to target issues:
- pingdotgg#2838 (session resume): HIGH — ProviderSessionManager persists session
IDs and separates startSession/resumeSession operations
- pingdotgg#2778 (subagent hang): MEDIUM — ProviderEventIngestor provides
infrastructure to forward permission events, but UI plumbing not yet wired
- pingdotgg#2886 (thread stuck working): HIGH — event-sourced projections replace
mutable state flags, eliminating sticky "working" states
The PR is a draft (34 commits, not merged). No OpenCode ACP adapter
exists yet in v2 — OpenCode would need its own adapter wired into the
ProviderAdapterRegistry. Recommend watching for merge and adding an
OpenCode adapter post-merge.
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
The upstream PR pingdotgg#2829 added orchestrationV2 methods to the WsRpcClient
interface. The test mock in service.threadSubscriptions.test.ts was
missing the orchestrationV2 property, causing a typecheck failure:
'Property orchestrationV2 is missing in type...'
Added orchestrationV2 mock with dispatchCommand, getThreadProjection,
subscribeShell, and subscribeThread as vi.fn() stubs.
duncan4123 pushed a commit to duncan4123/t3code that referenced this pull request Jun 2, 2026
The upstream PR pingdotgg#2829 targets a newer Effect version than our fork's
pinned effect@4.0.0-beta.73. Fixes:
- Replace Random.nextUUIDv4 with Crypto.randomUUIDv4 (beta.73 API)
- Fix deterministic Service tag keys to match fork convention
(include file path segments; e.g. Adapters/ClaudeAdapterV2/...)
- Replace Schema.decodeSync with Schema.decodeUnknownEffect inside
Effect.gen generators (tsgo schemaSyncInEffect rule)
- Replace inline Schema.encodeUnknownSync with module-level wrappers
to avoid schemaSyncInEffect rule inside generators
@github-actions

github-actionsBot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🚀 Expo continuous deployment is ready!

  • Project → t3-code
  • Platforms → android, ios
  • Scheme → t3code-preview
🤖 Android🍎 iOS
Fingerprintfe5a51f2e189da69dfc4c2cd458e6cfb5fdff2eaae3bd597809dfd7771d0898f735d172973d4c1c8
Build DetailsBuild Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
App version: 0.1.0
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Build Permalink
DetailsDistribution: INTERNAL
Build profile: preview:dev
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
App version: 0.1.0
Git commit: eea0dcae4150df8341606520c074dc651ae7c00a
Update DetailsUpdate Permalink
DetailsBranch: pr-2829
Runtime version: fe5a51f2e189da69dfc4c2cd458e6cfb5fdff2ea
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Update Permalink
DetailsBranch: pr-2829
Runtime version: ae3bd597809dfd7771d0898f735d172973d4c1c8
Git commit: 1d5a64460414f9a2c6ff4a5e4f977932228b7f1b
Update QR

Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
Comment threadapps/server/src/ws.ts
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
@juliusmarmingejuliusmarminge changed the title WIP: wire orchestration v2 provider adaptersfeat(orchestrator): introduce new orchestratorJun 14, 2026
Comment threadapps/server/src/orchestration-v2/RunExecutionService.ts
Comment on lines +109 to +128
Effect.gen(function* () {
const threadId = payloadInput.threadId ?? input.threadId;
const eventId = yield* idAllocator.allocate.event({
threadId,
providerSessionId: input.providerSessionId,
});
const occurredAt = yield* DateTime.now;
return yield* Schema.decodeUnknownEffect(OrchestrationV2DomainEvent)(
compactUndefined({
id: eventId,
type: payloadInput.type,
threadId,
runId: payloadInput.runId ?? input.runId,
nodeId: payloadInput.nodeId ?? input.nodeId,
provider: input.event.provider,
rawEventId: input.rawEventId,
occurredAt,
payload: payloadInput.payload,
}),
);

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.

🟡 Mediumorchestration-v2/ProviderEventIngestor.ts:109

In makeDomainEvent, the ?? operator on lines 121 and 123 treats explicit null as equivalent to undefined, causing payloadInput.runId ?? input.runId to fall back to input.runId when payloadInput.runId is explicitly null. Since the type is readonly runId?: RunId | null, this means explicit null values from the caller (e.g., input.event.node.runId being null on line 172) are incorrectly overwritten instead of preserved. Consider using === undefined checks like lines 161-162 and 210-211, or use payloadInput.runId === undefined ? input.runId : payloadInput.runId.

 const threadId = payloadInput.threadId ?? input.threadId;
- const runId = payloadInput.runId ?? input.runId;- const nodeId = payloadInput.nodeId ?? input.nodeId;+ const runId = payloadInput.runId === undefined ? input.runId : payloadInput.runId;+ const nodeId = payloadInput.nodeId === undefined ? input.nodeId : payloadInput.nodeId;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/ProviderEventIngestor.ts around lines 109-128:
In `makeDomainEvent`, the `??` operator on lines 121 and 123 treats explicit `null` as equivalent to `undefined`, causing `payloadInput.runId ?? input.runId` to fall back to `input.runId` when `payloadInput.runId` is explicitly `null`. Since the type is `readonly runId?: RunId | null`, this means explicit `null` values from the caller (e.g., `input.event.node.runId` being `null` on line 172) are incorrectly overwritten instead of preserved. Consider using `=== undefined` checks like lines 161-162 and 210-211, or use `payloadInput.runId === undefined ? input.runId : payloadInput.runId`.
Evidence trail:
apps/server/src/orchestration-v2/ProviderEventIngestor.ts lines 105-106 (payloadInput type with `RunId | null`), line 121 (`runId: payloadInput.runId ?? input.runId`), line 122 (`nodeId: payloadInput.nodeId ?? input.nodeId`), lines 161-162 and 210-211 (codebase uses `=== undefined` pattern elsewhere). packages/contracts/src/orchestrationV2.ts line 358 (`runId: Schema.NullOr(RunId)` on ExecutionNode - confirms null is a valid value), line 857 (`runId: Schema.optional(RunId)` on EventBase - domain event uses optional/undefined, not null). apps/server/src/orchestration-v2/ProviderEventIngestor.ts lines 80-81 (compactUndefined only strips undefined, not null).

Comment threadapps/server/src/orchestration-v2/Orchestrator.ts
Comment threadapps/web/src/routes/debug.orchestration-v2.tsx Outdated
Comment threadapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 79031a1 to 4e68dcbCompareJune 14, 2026 23:55
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 4e68dcb to c7539b9CompareJune 17, 2026 07:30
Comment threadapps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts
Comment threadapps/server/src/orchestration-v2/ProviderSessionManager.ts Outdated
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts Outdated
Comment threadapps/server/src/mcp/OrchestratorMcpService.ts Outdated
Comment threadapps/server/src/ws.ts Outdated
function nativeThreadId(provider: ProviderKind, thread: OrchestrationV2ProviderThread): string {
const id = thread.nativeThreadRef?.nativeId;
if (id === null || id === undefined || id.trim().length === 0) {
throw new ProviderAdapterProtocolError({

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.

🟡 MediumAdapters/AcpAdapterV2.ts:271

When nativeThreadId is called inside Effect.gen generators (e.g., lines 899, 1813), the thrown ProviderAdapterProtocolError becomes an untyped defect instead of a typed failure. This bypasses Effect.mapError and other typed error handlers, causing the error to propagate as an unexpected defect. Consider converting nativeThreadId to return Effect<string, ProviderAdapterProtocolError> and yielding it at each call site, or inlining the validation with yield* new ProviderAdapterProtocolError(...) so the failure is properly typed.

Also found in 1 other location(s)

apps/server/src/orchestration-v2/ThreadManagementService.ts:278

The statement return yield* managementError(...) cannot work correctly because managementError() returns a ThreadManagementError instance, not an Effect. The yield* operator in Effect.gen expects an Effect value. This should be return yield* Effect.fail(managementError(...)). The correct pattern is demonstrated elsewhere in this file (lines 241-246) where Effect.fail(managementError(...)) is properly used. This same bug pattern repeats at lines 291, 333, 357, 374, 390, 406, and 427.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts around line 271:
When `nativeThreadId` is called inside `Effect.gen` generators (e.g., lines 899, 1813), the thrown `ProviderAdapterProtocolError` becomes an untyped defect instead of a typed failure. This bypasses `Effect.mapError` and other typed error handlers, causing the error to propagate as an unexpected defect. Consider converting `nativeThreadId` to return `Effect<string, ProviderAdapterProtocolError>` and yielding it at each call site, or inlining the validation with `yield* new ProviderAdapterProtocolError(...)` so the failure is properly typed.
Evidence trail:
1. nativeThreadId function with throw: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts lines 268-277
2. Call site inside Effect.gen: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 899
3. Call site inside Effect.gen: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 1813
4. Correct yield* pattern for comparison: apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts line 1808
5. ProviderAdapterProtocolError class definition: apps/server/src/orchestration-v2/ProviderAdapter.ts lines 316-327
6. Effect.gen implementation delegating to fromIteratorUnsafe: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 1104-1125
7. fromIteratorUnsafe calling iter.next() without try/catch: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 1285-1307
8. FiberImpl.runLoop catch block converting thrown errors to exitDie: https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts lines 646-650
9. die = exitDie producing Effect<never> (untyped): https://github.com/Effect-TS/effect-smol packages/effect/src/internal/effect.ts line 947
Also found in 1 other location(s):
- apps/server/src/orchestration-v2/ThreadManagementService.ts:278 -- The statement `return yield* managementError(...)` cannot work correctly because `managementError()` returns a `ThreadManagementError` instance, not an `Effect`. The `yield*` operator in `Effect.gen` expects an Effect value. This should be `return yield* Effect.fail(managementError(...))`. The correct pattern is demonstrated elsewhere in this file (lines 241-246) where `Effect.fail(managementError(...))` is properly used. This same bug pattern repeats at lines 291, 333, 357, 374, 390, 406, and 427.

juliusmarmingeand others added 23 commits September 1, 2026 16:33
R12 follow-up
Insert persistent feedback blocks by their timestamp within the canonical timeline while preserving projected row order. Keep real optimistic sends appended and suppress duplicate local messages already committed by the server.
Implemented by GPT-5.6 Sol via Codex.
R20 follow-up
Give inspector reasoning markdown its projected source thread and retain the explicit environment fallback for proposed plans without a thread reference. Workspace links and images now resolve through their owning environment after removal of the active-environment fallback.
Implemented by GPT-5.6 Sol via Codex.
Read the inclusive cursor, a full history page, and a look-behind row so older history does not terminate after one page.
Finding: P01 pagination termination
Model: GPT-5.6 Sol via Codex
Cancel pending SDK requests before aborting the native session. Preserve per-admission cancellation state and treat stopped initial prompts as interruption instead of provider failure.
Finding: R14 prompt cancellation
Model: GPT-5.6 Sol via Codex
Keep the original cursor owner through ancestor traversal and preserve the history budget across empty intermediate forks. Verify exact paged history against the complete nested projection.
Finding: P01 nested lineage
Model: GPT-5.6 Sol via Codex
Retain pending admission after transient status failures and use one generation-owned retry worker. Ignore stale timers and duplicate evidence so older prompts cannot finish newer steering.
Finding: R14 status reconciliation
Model: GPT-5.6 Sol via Codex
Keep hidden local and inherited rows from consuming history pages. Preserve stop-request dependencies, source-run cutoffs, and imported history while loading related metadata from the selected cohort and using indexed watermark lookups.
Finding: P01 bounded history visibility
Model: GPT-5.6 Sol via Codex
Adapt grouped tool summaries and the floating working timer to V2 run, attempt, and queue state. Bring over the composer, keyboard, and disclosure transitions while retaining the V2 activity inspector and queue controls.
Keep OV2 web composer and grouping behavior intact; share only the existing command label parser with mobile.
Restores main features dropped by the policy replay: #8569 theme wiring,
settings search rework, #8803 workspace-mutation refresh (v2-adapted),
video + image previews (web and mobile, v2-adapted), #8862 Expo glass,
and the round's docs. Timeline thinking rows (#8984) stay on the v2
work-live system.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The v2 equivalents of main's #8984 and #8922: a "Working for ..." header
anchors the active run, the trailing live tool row survives between
actions in past tense instead of vanishing, and a shimmering Thinking
row marks reasoning gaps. During workspace preparation the header shows
"Setting up worktree..." (driven by the local dispatch flag or the v2
run's preparing status, so remote viewers see it too), the composer
footer span is gone, and draft promotion waits until the run starts or
startup fails instead of navigating mid-preparation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adopts the round's main features into the v2 architecture: the #9023
media rework (streamed videos, media-file assets, protocol-relative
links), #9098 shared live-activity row folded into the v2 working and
thinking rows, the #9084/#9078 Claude model catalog for v2 consumers,
a native #9005 OpenCode child-session abort in the v2 adapter, #9013's
landed LegendList patch, and per-environment sidebar provider entries.
For #8600 the server-side pieces land, but auto-settle evaluation stays
client-side (reading the new server-owned settings) until the v2
orchestrator grows its own settlement reactor; main's v1-only reactor
and coalescer additions are dropped with the rest of the v1 path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

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.

composerVideos and composerOtherFiles are computed but never rendered — the attachment list below still maps composerFiles (line 4034) into the generic FileIcon row, so ComposerVideoThumbnail (line 176), the new PlayIcon import (line 305), and attachVideoThumbnail / isPreviewableComposerVideo have no live consumer here. The result is a split treatment for the same attachment: a staged video is a plain file row in the composer, while the sent message renders it as a playable tile (MessagesTimeline.tsxuserVideos). The three unused locals will also trip unused-symbol checks.

Smallest fix: render composerVideos as thumbnail tiles (ComposerVideoThumbnail + buildExpandedImagePreview, matching the timeline tile) and feed the existing file-row list from composerOtherFiles; if the composer treatment is not landing in this PR, drop the two locals, ComposerVideoThumbnail, and the PlayIcon import instead so the file does not carry a half-wired path.

Posted via Macroscope — UI Consistency

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.

composerVideos and composerOtherFiles are computed but never rendered — the attachment list below still maps composerFiles (line 4034) into the generic FileIcon row, so ComposerVideoThumbnail (line 176), the new PlayIcon import (line 305), and attachVideoThumbnail / isPreviewableComposerVideo have no live consumer here. The result is a split treatment for the same attachment: a staged video is a plain file row in the composer, while the sent message renders it as a playable tile (MessagesTimeline.tsxuserVideos). The three unused locals will also trip unused-symbol checks.

Smallest fix: render composerVideos as thumbnail tiles (ComposerVideoThumbnail + buildExpandedImagePreview, matching the timeline tile) and feed the existing file-row list from composerOtherFiles; if the composer treatment is not landing in this PR, drop the two locals, ComposerVideoThumbnail, and the PlayIcon import instead so the file does not carry a half-wired path.

Posted via Macroscope — UI Consistency

!hasContent &&
(props.selectedThread.session?.status === "running" ||
props.selectedThread.session?.status === "starting");
const showStopAction = props.canStopThread;

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.

Stop hides send while composing

High Severity

showStopAction now follows canStopThread alone and no longer requires an empty composer. While a run is interruptible, typed text or attachments still replace Send/Queue with Stop, so follow-ups cannot be sent or queued until the run is interrupted.

Additional Locations (2)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

selectedThreadFeed,
selectedThreadKey,
]);
}, [props.onSendMessage, selectedThreadKey]);

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.

Send always re-anchors the feed

Medium Severity

handleSendMessage now always sets anchorMessageId to the just-sent id. The previous first-message-only policy in resolveThreadFeedSubmissionAnchor is no longer applied, so follow-ups and second outbox sends retarget the live-follow scroll.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

// Dictation keeps that focus while the composer switches to its compact pill.
const composerBottomInset = (
Platform.OS === "android" ? isKeyboardVisible : composerExpanded || composerFocused
)

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.

iOS composer inset ignores focus

Medium Severity

iOS composerBottomInset now keys only on composerExpanded, and the editor no longer updates composerFocused. Focusing the collapsed pill still applies home-indicator padding above the keyboard, leaving a gap between the composer and the IME.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 7fee604. Configure here.

…ator
Ports #8600's server-owned settlement to orchestration v2 instead of
keeping client-side evaluation. A ThreadSettlementService sweep runs at
startup, on auto-settle settings changes, and once per minute: it
evaluates inactivity and merged or closed pull requests over v2 thread
shells and dispatches the new guarded thread.auto-settle command, which
rejects threads that changed after the sweep's snapshot or carry any
explicit override, then reuses the orchestrator's settle lifecycle.
With the server deciding, the clients drop their effectiveSettled
evaluation and partition on the persisted settledOverride like main.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@macroscopeapp

Copy link
Copy Markdown
Contributor

apps/server/src/cli/app.ts:232-239

(Posted at PR level: an inline comment could not be anchored on this file in the current diff.)

The Effect.tryPromisecatch in runAppCommand collapses six semantically distinct failures into one error whose distinction survives only as an English sentence inside cause: Schema.Defect() — response timeout, oversized response, non-JSON response, schema-invalid response, request-id mismatch, and "connection closed" all become DesktopAppUnreachableError, whose message asserts the desktop app could not be reached and tells the user to start it. In the four response-shaped cases the app did answer, so that caller-visible message is wrong, and cli/app.test.ts asserts on the prose (cause: { message: "The desktop app response is invalid." }), which makes it behavior rather than diagnostics.

The conventions ask that failures be distinguished by stable structural attributes with the message derived only from them, and that a translation boundary wrap only genuinely lower-level failures rather than flatten known ones. Consider having sendDesktopAppActivationRequest reject with tagged errors (or return a tagged result) so the CLI can keep DesktopAppUnreachableError for the connect-level ENOENT/ECONNREFUSED case its message describes, and add e.g. DesktopAppResponseInvalidError { requestId, workspaceRoot, byteLength } and DesktopAppResponseTimeoutError { requestId, timeoutMs } for the rest, each keeping the real socket error as cause where one exists.

Posted via Macroscope — Effect Service Conventions

Posted via Macroscope — Effect Service Conventions

@macroscopeapp

Copy link
Copy Markdown
Contributor

apps/server/src/cli/app.ts:232-239

(Posted at PR level: an inline comment could not be anchored on this file in the current diff.)

The Effect.tryPromisecatch in runAppCommand collapses six semantically distinct failures into one error whose distinction survives only as an English sentence inside cause: Schema.Defect() — response timeout, oversized response, non-JSON response, schema-invalid response, request-id mismatch, and "connection closed" all become DesktopAppUnreachableError, whose message asserts the desktop app could not be reached and tells the user to start it. In the four response-shaped cases the app did answer, so that caller-visible message is wrong, and cli/app.test.ts asserts on the prose (cause: { message: "The desktop app response is invalid." }), which makes it behavior rather than diagnostics.

The conventions ask that failures be distinguished by stable structural attributes with the message derived only from them, and that a translation boundary wrap only genuinely lower-level failures rather than flatten known ones. Consider having sendDesktopAppActivationRequest reject with tagged errors (or return a tagged result) so the CLI can keep DesktopAppUnreachableError for the connect-level ENOENT/ECONNREFUSED case its message describes, and add e.g. DesktopAppResponseInvalidError { requestId, workspaceRoot, byteLength } and DesktopAppResponseTimeoutError { requestId, timeoutMs } for the rest, each keeping the real socket error as cause where one exists.

Posted via Macroscope — Effect Service Conventions

Posted via Macroscope — Effect Service Conventions

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

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON, but a cloud agent failed to start.

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

@@ -2085,12 +2062,9 @@ export default function Sidebar() {
// memo exactly at the next wake boundary.
void snoozeWakeTick;
const preciseNow = new Date().toISOString();
const visible = threads.filter(

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.

Web sidebar pinned threads partition completely broken

High Severity

The partition loop declares const pinned: EnvironmentThreadShell[] = [] at line 2068 but never pushes anything into it. The old code had an else if (thread.pinnedAt != null) { pinned.push(thread); } branch between the snoozed and settled checks, but it was removed in this refactor. All pinned threads now fall into active instead. The mobile equivalent in threadListV2.ts correctly retains else if (thread.pinnedAt != null) { pinned.push(thread); } at line 421. This means pinnedThreads is always empty, the pinned section never renders, DnD reordering is inert, and reorderablePinnedKeys is an empty set.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

thread.session?.status === "running" &&
thread.session.activeTurnId != null
) {
if (action === "archive" && threadRuntimeIsActive(thread.runtime)) {

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.

Archive guard stricter than old threadCanArchive behavior

Medium Severity

The comment says "Archive keeps its original, narrower guard" but threadRuntimeIsActive is actually wider (more restrictive) than the replaced threadCanArchive. threadCanArchive allowed archiving queued threads without an activeRunId and all waiting threads; threadRuntimeIsActive blocks both. The old threadCanArchive is still imported at line 21 but now unused, confirming a mistaken substitution rather than an intentional tightening.

Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 6081201. Configure here.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment