fix(orchestrator): Keep Home, steers, and errors honest when work ends - #5459

Open
mwolson wants to merge 348 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orchestrator-settled-lifecycle
Open

fix(orchestrator): Keep Home, steers, and errors honest when work ends#5459
mwolson wants to merge 348 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orchestrator-settled-lifecycle

Conversation

@mwolson

@mwolsonmwolson commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What Changed

You can steer a turn while the agent is still working. The run could settle anyway, and the later answer vanished. That later text now stays on the thread.

A provider that never started could leave the thread looking alive forever. Exhausted retries now fail the run.

You settle a thread, and Home still treats it like live work. Missing snapshots keep retrying, archived threads drift back, and Waiting keeps refreshing stale rows. Settled now stays settled. A gone thread stops retrying.

You dismiss a thread error, and a later one never appears, or the old one never leaves. Dismissal lasts until a later error. Plan mode off still left the Build/Plan toggle in the composer after the v2 chat rewrite. The toggle is hidden in that case.

Why

Settle meant the useful work was over in the UI while the agent, the outbox, or a later error was still in play. Home, Waiting, and the composer should follow that same rule.

Julius's bounded thread history on current CTM stays as shipped.

UI Changes

Steered output stays in the thread. Failed starts no longer hang as if they were still starting. Home and Waiting settled state is quieter. Thread error banners dismiss until a later error. Build/Plan is hidden when plan mode is disabled.

No before/after screenshots.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Changes core orchestration paths—settlement merging, command commit guards, Claude turn lifecycle, and process-loss reconciliation—with race-sensitive behavior that can leave runs stuck or drop recovery if callers mishandle committed: false.

Overview
Threads gain settledOverrideAt so settle/keep-active pins have a stable establishment time that survives renames and is exposed on shells and mobile projections.

Settlement is tightened end-to-end: settle is blocked on queued runs, pending runtime requests, and provider background work; archived threads cannot settle/unsettle; pin/unsettle paths maintain the new timestamp; message dispatch clears overrides via a shared helper. Settled/unsettled events are merged through reduceThreadSettlementEvent in memory and in SQL so stale full-thread payloads cannot overwrite title or other metadata. Provider ingest tests document when pending thread-scoped requests should emit thread.unsettled without waking keep-active or non-activity updates.

Claude steering tracks per-turn steer counts and frame counts, detects steering handoff results before terminalizing, marks in-flight tools interrupted, and bounds silent handoffs with a timeout that fails the turn if no new frames arrive. Turn finalization is claimed up front to avoid duplicate terminals when the handoff fiber races the frame handler; new turns wait while a turn is finalizing.

Recovery adds optional updatedBefore filters on outbox cancel/reconcile and expectedThreadUpdatedAt on commitCommand so stale recovery writes no-op when the thread has already advanced.

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

Note

Add Orchestration V2 system with provider adapters, persistence, and V2 UI migration

  • Introduces the complete Orchestration V2 architecture: contracts, event store, projection store, command receipts, effect outbox/worker, runtime recovery, and database migrations 044–052
  • Adds V2 provider adapters for Claude, Codex, Cursor, Grok, OpenCode, and ACP Registry through the ProviderAdapterV2 service contract and driver registry, with replay testkits for deterministic testing
  • Implements thread lifecycle services for launch, fork, settlement, checkpoint capture/rollback, context handoff, continuation, run finalization, and provider-session management with idle timeout and shutdown
  • Migrates client-runtime, web, and mobile state from legacy orchestration types to V2 thread projections, bounded shell snapshots, runtime summaries, and progressive history paging
  • Adds MCP orchestrator and worktree toolkits, scheduled-task service with interval and fixed-time scheduling, T3 orchestration instructions, and a legacy V1 thread importer with idempotent shell and transcript hydration
  • Risk: migrations 044–052 create new schema tables and copy existing V2/project data into a shared application event log; EnvironmentThreadState in threadState.ts replaces legacy thread/page fields with OrchestrationV2ThreadProjection and ThreadHistoryMeta, removing threadHasOlderTurns; RPC authorization scopes in RpcAuthorization.ts are remapped to V2 method constants

Macroscope summarized 674b0e4.

@coderabbitai

coderabbitaiBot commented Aug 5, 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: 7726a859-eb6b-4efc-9e83-8c6c368e6e50

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 5, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One convention issue found in the new provider-ingest settlement code: a statically known tagged failure is recovered with Effect.catchTag instead of Effect.catchTags.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts
Comment threadpackages/client-runtime/src/state/shellReducer.ts Outdated
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts
@macroscopeapp

macroscopeappBot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This is a 964-file Orchestration V2 architecture and persistence migration that adds provider integrations, new workflows, protocol/schema changes, and product-default changes across server, web, and mobile. Unresolved projection and provider-lifecycle race findings add material consistency risk beyond the scope of an auto-approvable fix.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts Outdated
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from 5229063 to 6c322c7CompareAugust 6, 2026 00:13
@mwolsonmwolson changed the title fix(orchestrator): Stabilize recovery and settled-thread lifecyclefix(orchestrator): Stabilize settled-thread lifecycleAug 6, 2026
Comment threadpackages/shared/src/orchestrationV2Settled.ts
Comment threadpackages/shared/src/orchestrationV2Settled.ts
@mwolsonmwolson changed the title fix(orchestrator): Stabilize settled-thread lifecyclefix(orchestrator): Keep Home and settle honest when work endsAug 6, 2026
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 6, 2026
Port pingdotgg#5459 settled-thread lifecycle follow-ups: pin clear on settle, preserve
newer updatedAt on activity unsettle, fresher archive membership, settle runs
filter, and typed activity-unsettle prep errors.
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 25de21d to 0af2a6eCompareAugust 7, 2026 12:10
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 8, 2026
pingdotgg#5459's ProjectionSnapshotQuery.settled test must provide the same
ThreadBackgroundLiveness and ThreadPlanProgress layers the live snapshot
query now requires on CTM.
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from 0a6ccc1 to 5908b04CompareAugust 8, 2026 18:43
Comment threadpackages/shared/src/orchestrationV2Settled.ts
Comment threadapps/server/src/orchestration-v2/ProjectionStore.ts
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts Outdated
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 22bd872 to a27c1ccCompareAugust 10, 2026 17:05
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 11, 2026
Comment threadapps/server/src/orchestration-v2/ProjectionStore.ts
Comment threadapps/mobile/src/state/use-thread-selection.ts
Comment threadpackages/client-runtime/src/state/shell.ts
Comment threadpackages/contracts/src/orchestrationV2.ts
Comment threadapps/server/src/orchestration-v2/EffectOutbox.ts
Comment threadpackages/client-runtime/src/state/threads.ts

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

Effect service conventions review of the settle-lifecycle changes. The previously flagged Effect.catchTag usage in ProviderEventIngestor.ts is now Effect.catchTags({ ... }), and the new/changed service code (ProjectionStore, EventSink, EffectOutbox, ProviderRuntimeRecoveryService, ProviderEventIngestor, shared settlement helpers) follows the import, layer, and dependency-acquisition rules. Two small change-discipline findings in packages/client-runtime/src/state/threadSettled.ts.

Posted via Macroscope — Effect Service Conventions

Comment threadpackages/client-runtime/src/state/threadSettled.ts Outdated
Comment threadpackages/client-runtime/src/state/threadSettled.ts
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from dae270f to b27bee8CompareAugust 11, 2026 13:41
Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts Outdated
Comment threadpackages/client-runtime/src/state/threadSettled.ts Outdated
Comment threadpackages/client-runtime/src/state/shell.ts
Comment threadpackages/client-runtime/src/state/threadSettled.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from a186d64 to 5b1a115CompareAugust 12, 2026 23:19
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from a6e0049 to 50a57b6CompareSeptember 1, 2026 05:36
The v2 equivalents of main's pingdotgg#8984 and pingdotgg#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>
mwolson added a commit to mwolson/t3code that referenced this pull request Sep 1, 2026
OpenCode 2 now writes an occurrence timestamp when lastError is set or
cleared. The same message after a clear is a new occurrence; unchanged
text on an uncleared error keeps the original timestamp.
The optional lastErrorAt field is added to the provider-session contract
so the adapter can persist it without depending on pingdotgg#5459 settled-thread
lifecycle.
Stop retrying definitive missing thread snapshots, normalize V2 shell archive
membership, preserve provider retry metadata, and harden settled projection
ordering, activity unsettle, ingest timestamps, snapshot reads, and Waiting
lifecycle invariants.
Settlement-only merge now applies pinnedAt on explicit settle so pins still
clear, and activity unsettles no longer rewind updatedAt behind a newer
metadata bump.
Add settledOverrideAt on V2 app threads so keep-active and settled pins keep a
stable establishment clock across metadata renames. Prefer that field in the
activity-unsettle ordering guard, with settledAt/updatedAt fallbacks for
pre-field rows, and stamp it on settle, keep-active, and activity clear paths.
Pass settledOverrideAt through the client reduceThreadSettlementEvent path so
live sync matches the server pin floor, stamp a new keep-active floor when pin
promotes a settled thread, and keep existing active floors when re-pinning.
Re-apply the stranded ChatView and ChatComposer half of pingdotgg#5551 on CTM. Main
already gates the Build/Plan toggle behind planModeEnabled, but the v2 ChatView
rewrite left the beta setting and Beta panel in place while re-exposing the
toggle, /plan and /default, and Shift+Tab.
When the flag is off, force the effective interaction mode to default so a
thread with a stored plan mode cannot stay trapped after the control is hidden.
The next send persists default. Slash commands send as plain text, and the
keyboard shortcut is a no-op.
pingdotgg#6123 thread-error-banner-dismiss remembers the remount-safe banner key as
thread plus message. After that landed on CTM, dismissing one durable
failure hid later identical text for the rest of the app session.
Key the session mask with the occurrence timestamp when present so a later
run with the same lastError still shows.
The 30-minute idle reaper still tears down the live provider process. It
no longer writes lastError null on idle, manual, or server stop. Release
reads the latest projected session so an adapter-stamped error survives
process teardown.
A repeated process failure must get a new occurrence timestamp so a
dismissed banner can reappear. Idle release still preserves the prior
timestamp. The web banner key uses lastErrorAt only and no longer
falls back to updatedAt.
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from ae21654 to 57fca87CompareSeptember 1, 2026 23:52
const nextThread = reduceThreadSettlementEvent({
current: currentThread,
eventType: event.type,
settlement: {

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/ProjectionStore.ts:1261

thread.unsettled events lose event.payload.unsettledAt in the durable projection, so shell snapshots report unsettledAt as the previous value (often null) instead of when the thread was explicitly kept active. Include unsettledAt in the settlement merge.

 settlement: {
+ unsettledAt: event.payload.unsettledAt ?? null,
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/ProjectionStore.ts around line 1261:
`thread.unsettled` events lose `event.payload.unsettledAt` in the durable projection, so shell snapshots report `unsettledAt` as the previous value (often `null`) instead of when the thread was explicitly kept active. Include `unsettledAt` in the settlement merge.

// Only a definitive missing thread skips the candidate. Other read
// failures must surface so callers can retry rather than silently
// store activity without an unsettle attempt.
const projectionOption = yield* projectionStore.getThreadProjection(threadId).pipe(

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:356

Archived threads receive a thread.unsettled event for pending non-auth runtime-request.updated activity, clearing their settledOverride instead of leaving archived settlement state unchanged. getThreadProjection returns the still-present archived projection, and this path does not reject it before appending the synthetic event; restrict candidate generation to active threads.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/ProviderEventIngestor.ts around line 356:
Archived threads receive a `thread.unsettled` event for pending non-auth `runtime-request.updated` activity, clearing their `settledOverride` instead of leaving archived settlement state unchanged. `getThreadProjection` returns the still-present archived projection, and this path does not reject it before appending the synthetic event; restrict candidate generation to active threads.

Comment threadapps/server/src/orchestration-v2/ProviderSessionManager.ts
if (nextThread === projection.thread) {
return projection;
}
return { ...base, thread: nextThread };

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.

🟡 Mediumstate/orchestrationV2Projection.ts:207

A delayed thread.unsettled rewinds projection.updatedAt to event.occurredAt, even when the projection already has a newer timestamp from a concurrent metadata update. Although reduceThreadSettlementEvent preserves the newer thread.updatedAt, spreading base here still makes client ordering/activity views treat the thread as stale; preserve the later of the existing projection timestamp and event.occurredAt, as the server reducer does.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/orchestrationV2Projection.ts around line 207:
A delayed `thread.unsettled` rewinds `projection.updatedAt` to `event.occurredAt`, even when the projection already has a newer timestamp from a concurrent metadata update. Although `reduceThreadSettlementEvent` preserves the newer `thread.updatedAt`, spreading `base` here still makes client ordering/activity views treat the thread as stale; preserve the later of the existing projection timestamp and `event.occurredAt`, as the server reducer does.

@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 OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 57fca87. Configure here.

Comment threadapps/server/src/orchestration-v2/ProviderTurnStartService.ts
const next = new Set(current);
next.add(input.context.providerTurnId);
return next;
});

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.

Finalize claim races new turns

Medium Severity

finalizeActiveTurn nulls activeTurn and only then adds the id to finalizingTurnIds. startTurn checks those refs separately, so a concurrent start (the handoff timeout is forked) can observe both empty and open a second provider turn while the first is still emitting terminals.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 57fca87. Configure here.

@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 6081201 to 95bacddCompareSeptember 2, 2026 07:19
…d-letters
Wire failFromDeadLetter onto the turn-start service and compensate before
the outbox fails a start or restart that exhausted retries, matching the
v2.1 port.

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

Scope note: only apps/web/src/** TS/TSX/CSS changes were reviewed. Findings are the newly added global CSS in index.css (dead/duplicated rules) and the composer context strip's motion contract, which changed lines break while the code that depends on it is retained.

Posted via Macroscope — UI Consistency

aria-label="Run on"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The strip's compact/expand FLIP animation still keys on [data-composer-context-control] (BranchToolbar.tsx:232, consumed by useLabelsOverflow), but this PR removes that attribute from the environment selector, the env-mode selector (locked and select paths) and the branch selector. The only remaining carrier is the vertical Separator at BranchToolbar.tsx:545, so collapsing now animates a 1px separator while the actual controls jump to their new positions. Consider keeping the attribute on the toolbar-mode triggers (it is inert in panel mode), or removing the FLIP pass as well if the motion is intentionally gone.

Suggested change
aria-label="Run on"
aria-label="Run on"
data-composer-context-control

Posted via Macroscope — UI Consistency

Comment on lines +826 to +836
.glass-opacity-slider {
--glass-slider-progress: 0%;
--glass-slider-fill-offset: 0.5rem;
--glass-slider-fill-position: calc(
var(--glass-slider-progress) + var(--glass-slider-fill-offset)
);
appearance: none;
height: 1.5rem;
background: transparent;
cursor: pointer;
}

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.

This whole .glass-opacity-slider block (through the forced-colors rules) is unreferenced — the glass-opacity input in SettingsPanels.tsx:1111 uses settings-slider with --settings-slider-progress, and no literal or composed glass-opacity-slider class exists anywhere in the repo. It is also a verbatim copy of .settings-slider below with only the custom-property names changed, so it adds a second owner for the same slider treatment. Suggest dropping it (or, if a distinct slider is intended, wiring a call site and deriving it from .settings-slider rather than duplicating the vendor pseudo-element rules).

Posted via Macroscope — UI Consistency

Comment on lines +797 to +810
.alert-glass {
--alert-glass-tint: transparent;

background:
linear-gradient(
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent),
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent)
),
color-mix(in srgb, var(--background) var(--glass-opacity), transparent) !important;
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
}

.alert-glass[data-variant="error"] {

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.

alert-glass is already defined as @utility alert-glass (line 284) and is what ProviderStatusBanner consumes. This second definition in @layer components duplicates it and omits the utility's @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) opaque fallback; because !important declarations resolve in reverse layer order, this components-layer background: ... !important outranks the utility's important fallback, so browsers without backdrop-filter get a translucent, unblurred banner instead of the solid background. Suggest deleting this block and keeping the @utility as the single owner.

Posted via Macroscope — UI Consistency

Comment on lines +765 to +774
.surface-subheader {
@apply flex h-10 min-h-10 shrink-0 items-center border-b border-border/60 bg-background;
}

[data-preview-panel-mode="inline"] [data-right-panel-surface-content] [data-surface-subheader] {
height: calc(var(--spacing) * 7);
min-height: calc(var(--spacing) * 7);
margin-bottom: calc(var(--spacing) * 3);
border-bottom-color: transparent;
}

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.

.surface-subheader has no consumer: all four subheaders (DiffPanelShell, FileBrowserPanel, FilePreviewPanel, PreviewChromeRow) still carry the same geometry inline and only set data-surface-subheader. The inline-mode rule below is also redundant — every carrier already has in-data-[preview-panel-mode=inline]:h-7/min-h-7/mb-3/border-b-transparent, and those utilities sort after @layer components, so they win wherever this rule matches. Either apply surface-subheader at those call sites and drop the duplicated utility strings, or drop both rules so the shared geometry keeps one owner.

Posted via Macroscope — UI Consistency

className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The label previously animated transform/opacity on an inner data-composer-label-motion span with motion-reduce:transform-none motion-reduce:transition-opacity; this replaces it with a max-width transition and no reduced-motion guard, so reduced-motion users now get an animated layout change on every collapse. Same class appears at line 79 and in BranchToolbarEnvModeSelector/BranchToolbarBranchSelector.

Suggested change
className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0"
className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"

Posted via Macroscope — UI Consistency

"hover:!bg-black/[0.055] data-pressed:!bg-black/[0.055] dark:hover:!bg-white/[0.075] dark:data-pressed:!bg-white/[0.075]";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These row classes override Button's core contract at the call site — height (h-9 sm:h-9), radius, padding, and base/hover/pressed background, with ! needed to beat the ghost variant's own hover colors and raw black/white alpha instead of semantic tokens. Since this treatment is now shared by every panel row (select rows, link rows, split halves, icon actions), a named Button size/variant (e.g. size="panel-row" plus a muted-ghost variant) would express it in the primitive and drop the !important fights and the sm: re-statements; contextual width/color could stay here.

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $250.50, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

FileDiff SizeEstimate
apps/server/src/orchestration-v2/Orchestrator.ts280.33KB$14.02
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts272.17KB$13.61
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts238.01KB$11.90

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mwolson@t3dotgg@juliusmarminge@PixPMusic@nsxdavid@Yusuf007R@maria-rcks
, '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

fix(orchestrator): Keep Home, steers, and errors honest when work ends - #5459

Open
mwolson wants to merge 348 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orchestrator-settled-lifecycle
Open

fix(orchestrator): Keep Home, steers, and errors honest when work ends#5459
mwolson wants to merge 348 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orchestrator-settled-lifecycle

Conversation

@mwolson

@mwolsonmwolson commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What Changed

You can steer a turn while the agent is still working. The run could settle anyway, and the later answer vanished. That later text now stays on the thread.

A provider that never started could leave the thread looking alive forever. Exhausted retries now fail the run.

You settle a thread, and Home still treats it like live work. Missing snapshots keep retrying, archived threads drift back, and Waiting keeps refreshing stale rows. Settled now stays settled. A gone thread stops retrying.

You dismiss a thread error, and a later one never appears, or the old one never leaves. Dismissal lasts until a later error. Plan mode off still left the Build/Plan toggle in the composer after the v2 chat rewrite. The toggle is hidden in that case.

Why

Settle meant the useful work was over in the UI while the agent, the outbox, or a later error was still in play. Home, Waiting, and the composer should follow that same rule.

Julius's bounded thread history on current CTM stays as shipped.

UI Changes

Steered output stays in the thread. Failed starts no longer hang as if they were still starting. Home and Waiting settled state is quieter. Thread error banners dismiss until a later error. Build/Plan is hidden when plan mode is disabled.

No before/after screenshots.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Changes core orchestration paths—settlement merging, command commit guards, Claude turn lifecycle, and process-loss reconciliation—with race-sensitive behavior that can leave runs stuck or drop recovery if callers mishandle committed: false.

Overview
Threads gain settledOverrideAt so settle/keep-active pins have a stable establishment time that survives renames and is exposed on shells and mobile projections.

Settlement is tightened end-to-end: settle is blocked on queued runs, pending runtime requests, and provider background work; archived threads cannot settle/unsettle; pin/unsettle paths maintain the new timestamp; message dispatch clears overrides via a shared helper. Settled/unsettled events are merged through reduceThreadSettlementEvent in memory and in SQL so stale full-thread payloads cannot overwrite title or other metadata. Provider ingest tests document when pending thread-scoped requests should emit thread.unsettled without waking keep-active or non-activity updates.

Claude steering tracks per-turn steer counts and frame counts, detects steering handoff results before terminalizing, marks in-flight tools interrupted, and bounds silent handoffs with a timeout that fails the turn if no new frames arrive. Turn finalization is claimed up front to avoid duplicate terminals when the handoff fiber races the frame handler; new turns wait while a turn is finalizing.

Recovery adds optional updatedBefore filters on outbox cancel/reconcile and expectedThreadUpdatedAt on commitCommand so stale recovery writes no-op when the thread has already advanced.

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

Note

Add Orchestration V2 system with provider adapters, persistence, and V2 UI migration

  • Introduces the complete Orchestration V2 architecture: contracts, event store, projection store, command receipts, effect outbox/worker, runtime recovery, and database migrations 044–052
  • Adds V2 provider adapters for Claude, Codex, Cursor, Grok, OpenCode, and ACP Registry through the ProviderAdapterV2 service contract and driver registry, with replay testkits for deterministic testing
  • Implements thread lifecycle services for launch, fork, settlement, checkpoint capture/rollback, context handoff, continuation, run finalization, and provider-session management with idle timeout and shutdown
  • Migrates client-runtime, web, and mobile state from legacy orchestration types to V2 thread projections, bounded shell snapshots, runtime summaries, and progressive history paging
  • Adds MCP orchestrator and worktree toolkits, scheduled-task service with interval and fixed-time scheduling, T3 orchestration instructions, and a legacy V1 thread importer with idempotent shell and transcript hydration
  • Risk: migrations 044–052 create new schema tables and copy existing V2/project data into a shared application event log; EnvironmentThreadState in threadState.ts replaces legacy thread/page fields with OrchestrationV2ThreadProjection and ThreadHistoryMeta, removing threadHasOlderTurns; RPC authorization scopes in RpcAuthorization.ts are remapped to V2 method constants

Macroscope summarized 674b0e4.

@coderabbitai

coderabbitaiBot commented Aug 5, 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: 7726a859-eb6b-4efc-9e83-8c6c368e6e50

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 5, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One convention issue found in the new provider-ingest settlement code: a statically known tagged failure is recovered with Effect.catchTag instead of Effect.catchTags.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts
Comment threadpackages/client-runtime/src/state/shellReducer.ts Outdated
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts
@macroscopeapp

macroscopeappBot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This is a 964-file Orchestration V2 architecture and persistence migration that adds provider integrations, new workflows, protocol/schema changes, and product-default changes across server, web, and mobile. Unresolved projection and provider-lifecycle race findings add material consistency risk beyond the scope of an auto-approvable fix.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts Outdated
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from 5229063 to 6c322c7CompareAugust 6, 2026 00:13
@mwolsonmwolson changed the title fix(orchestrator): Stabilize recovery and settled-thread lifecyclefix(orchestrator): Stabilize settled-thread lifecycleAug 6, 2026
Comment threadpackages/shared/src/orchestrationV2Settled.ts
Comment threadpackages/shared/src/orchestrationV2Settled.ts
@mwolsonmwolson changed the title fix(orchestrator): Stabilize settled-thread lifecyclefix(orchestrator): Keep Home and settle honest when work endsAug 6, 2026
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 6, 2026
Port pingdotgg#5459 settled-thread lifecycle follow-ups: pin clear on settle, preserve
newer updatedAt on activity unsettle, fresher archive membership, settle runs
filter, and typed activity-unsettle prep errors.
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 25de21d to 0af2a6eCompareAugust 7, 2026 12:10
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 8, 2026
pingdotgg#5459's ProjectionSnapshotQuery.settled test must provide the same
ThreadBackgroundLiveness and ThreadPlanProgress layers the live snapshot
query now requires on CTM.
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from 0a6ccc1 to 5908b04CompareAugust 8, 2026 18:43
Comment threadpackages/shared/src/orchestrationV2Settled.ts
Comment threadapps/server/src/orchestration-v2/ProjectionStore.ts
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts Outdated
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 22bd872 to a27c1ccCompareAugust 10, 2026 17:05
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 11, 2026
Comment threadapps/server/src/orchestration-v2/ProjectionStore.ts
Comment threadapps/mobile/src/state/use-thread-selection.ts
Comment threadpackages/client-runtime/src/state/shell.ts
Comment threadpackages/contracts/src/orchestrationV2.ts
Comment threadapps/server/src/orchestration-v2/EffectOutbox.ts
Comment threadpackages/client-runtime/src/state/threads.ts

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

Effect service conventions review of the settle-lifecycle changes. The previously flagged Effect.catchTag usage in ProviderEventIngestor.ts is now Effect.catchTags({ ... }), and the new/changed service code (ProjectionStore, EventSink, EffectOutbox, ProviderRuntimeRecoveryService, ProviderEventIngestor, shared settlement helpers) follows the import, layer, and dependency-acquisition rules. Two small change-discipline findings in packages/client-runtime/src/state/threadSettled.ts.

Posted via Macroscope — Effect Service Conventions

Comment threadpackages/client-runtime/src/state/threadSettled.ts Outdated
Comment threadpackages/client-runtime/src/state/threadSettled.ts
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from dae270f to b27bee8CompareAugust 11, 2026 13:41
Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts Outdated
Comment threadpackages/client-runtime/src/state/threadSettled.ts Outdated
Comment threadpackages/client-runtime/src/state/shell.ts
Comment threadpackages/client-runtime/src/state/threadSettled.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from a186d64 to 5b1a115CompareAugust 12, 2026 23:19
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from a6e0049 to 50a57b6CompareSeptember 1, 2026 05:36
The v2 equivalents of main's pingdotgg#8984 and pingdotgg#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>
mwolson added a commit to mwolson/t3code that referenced this pull request Sep 1, 2026
OpenCode 2 now writes an occurrence timestamp when lastError is set or
cleared. The same message after a clear is a new occurrence; unchanged
text on an uncleared error keeps the original timestamp.
The optional lastErrorAt field is added to the provider-session contract
so the adapter can persist it without depending on pingdotgg#5459 settled-thread
lifecycle.
Stop retrying definitive missing thread snapshots, normalize V2 shell archive
membership, preserve provider retry metadata, and harden settled projection
ordering, activity unsettle, ingest timestamps, snapshot reads, and Waiting
lifecycle invariants.
Settlement-only merge now applies pinnedAt on explicit settle so pins still
clear, and activity unsettles no longer rewind updatedAt behind a newer
metadata bump.
Add settledOverrideAt on V2 app threads so keep-active and settled pins keep a
stable establishment clock across metadata renames. Prefer that field in the
activity-unsettle ordering guard, with settledAt/updatedAt fallbacks for
pre-field rows, and stamp it on settle, keep-active, and activity clear paths.
Pass settledOverrideAt through the client reduceThreadSettlementEvent path so
live sync matches the server pin floor, stamp a new keep-active floor when pin
promotes a settled thread, and keep existing active floors when re-pinning.
Re-apply the stranded ChatView and ChatComposer half of pingdotgg#5551 on CTM. Main
already gates the Build/Plan toggle behind planModeEnabled, but the v2 ChatView
rewrite left the beta setting and Beta panel in place while re-exposing the
toggle, /plan and /default, and Shift+Tab.
When the flag is off, force the effective interaction mode to default so a
thread with a stored plan mode cannot stay trapped after the control is hidden.
The next send persists default. Slash commands send as plain text, and the
keyboard shortcut is a no-op.
pingdotgg#6123 thread-error-banner-dismiss remembers the remount-safe banner key as
thread plus message. After that landed on CTM, dismissing one durable
failure hid later identical text for the rest of the app session.
Key the session mask with the occurrence timestamp when present so a later
run with the same lastError still shows.
The 30-minute idle reaper still tears down the live provider process. It
no longer writes lastError null on idle, manual, or server stop. Release
reads the latest projected session so an adapter-stamped error survives
process teardown.
A repeated process failure must get a new occurrence timestamp so a
dismissed banner can reappear. Idle release still preserves the prior
timestamp. The web banner key uses lastErrorAt only and no longer
falls back to updatedAt.
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from ae21654 to 57fca87CompareSeptember 1, 2026 23:52
const nextThread = reduceThreadSettlementEvent({
current: currentThread,
eventType: event.type,
settlement: {

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/ProjectionStore.ts:1261

thread.unsettled events lose event.payload.unsettledAt in the durable projection, so shell snapshots report unsettledAt as the previous value (often null) instead of when the thread was explicitly kept active. Include unsettledAt in the settlement merge.

 settlement: {
+ unsettledAt: event.payload.unsettledAt ?? null,
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/ProjectionStore.ts around line 1261:
`thread.unsettled` events lose `event.payload.unsettledAt` in the durable projection, so shell snapshots report `unsettledAt` as the previous value (often `null`) instead of when the thread was explicitly kept active. Include `unsettledAt` in the settlement merge.

// Only a definitive missing thread skips the candidate. Other read
// failures must surface so callers can retry rather than silently
// store activity without an unsettle attempt.
const projectionOption = yield* projectionStore.getThreadProjection(threadId).pipe(

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:356

Archived threads receive a thread.unsettled event for pending non-auth runtime-request.updated activity, clearing their settledOverride instead of leaving archived settlement state unchanged. getThreadProjection returns the still-present archived projection, and this path does not reject it before appending the synthetic event; restrict candidate generation to active threads.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/ProviderEventIngestor.ts around line 356:
Archived threads receive a `thread.unsettled` event for pending non-auth `runtime-request.updated` activity, clearing their `settledOverride` instead of leaving archived settlement state unchanged. `getThreadProjection` returns the still-present archived projection, and this path does not reject it before appending the synthetic event; restrict candidate generation to active threads.

Comment threadapps/server/src/orchestration-v2/ProviderSessionManager.ts
if (nextThread === projection.thread) {
return projection;
}
return { ...base, thread: nextThread };

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.

🟡 Mediumstate/orchestrationV2Projection.ts:207

A delayed thread.unsettled rewinds projection.updatedAt to event.occurredAt, even when the projection already has a newer timestamp from a concurrent metadata update. Although reduceThreadSettlementEvent preserves the newer thread.updatedAt, spreading base here still makes client ordering/activity views treat the thread as stale; preserve the later of the existing projection timestamp and event.occurredAt, as the server reducer does.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/orchestrationV2Projection.ts around line 207:
A delayed `thread.unsettled` rewinds `projection.updatedAt` to `event.occurredAt`, even when the projection already has a newer timestamp from a concurrent metadata update. Although `reduceThreadSettlementEvent` preserves the newer `thread.updatedAt`, spreading `base` here still makes client ordering/activity views treat the thread as stale; preserve the later of the existing projection timestamp and `event.occurredAt`, as the server reducer does.

@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 OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 57fca87. Configure here.

Comment threadapps/server/src/orchestration-v2/ProviderTurnStartService.ts
const next = new Set(current);
next.add(input.context.providerTurnId);
return next;
});

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.

Finalize claim races new turns

Medium Severity

finalizeActiveTurn nulls activeTurn and only then adds the id to finalizingTurnIds. startTurn checks those refs separately, so a concurrent start (the handoff timeout is forked) can observe both empty and open a second provider turn while the first is still emitting terminals.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 57fca87. Configure here.

@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 6081201 to 95bacddCompareSeptember 2, 2026 07:19
…d-letters
Wire failFromDeadLetter onto the turn-start service and compensate before
the outbox fails a start or restart that exhausted retries, matching the
v2.1 port.

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

Scope note: only apps/web/src/** TS/TSX/CSS changes were reviewed. Findings are the newly added global CSS in index.css (dead/duplicated rules) and the composer context strip's motion contract, which changed lines break while the code that depends on it is retained.

Posted via Macroscope — UI Consistency

aria-label="Run on"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The strip's compact/expand FLIP animation still keys on [data-composer-context-control] (BranchToolbar.tsx:232, consumed by useLabelsOverflow), but this PR removes that attribute from the environment selector, the env-mode selector (locked and select paths) and the branch selector. The only remaining carrier is the vertical Separator at BranchToolbar.tsx:545, so collapsing now animates a 1px separator while the actual controls jump to their new positions. Consider keeping the attribute on the toolbar-mode triggers (it is inert in panel mode), or removing the FLIP pass as well if the motion is intentionally gone.

Suggested change
aria-label="Run on"
aria-label="Run on"
data-composer-context-control

Posted via Macroscope — UI Consistency

Comment on lines +826 to +836
.glass-opacity-slider {
--glass-slider-progress: 0%;
--glass-slider-fill-offset: 0.5rem;
--glass-slider-fill-position: calc(
var(--glass-slider-progress) + var(--glass-slider-fill-offset)
);
appearance: none;
height: 1.5rem;
background: transparent;
cursor: pointer;
}

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.

This whole .glass-opacity-slider block (through the forced-colors rules) is unreferenced — the glass-opacity input in SettingsPanels.tsx:1111 uses settings-slider with --settings-slider-progress, and no literal or composed glass-opacity-slider class exists anywhere in the repo. It is also a verbatim copy of .settings-slider below with only the custom-property names changed, so it adds a second owner for the same slider treatment. Suggest dropping it (or, if a distinct slider is intended, wiring a call site and deriving it from .settings-slider rather than duplicating the vendor pseudo-element rules).

Posted via Macroscope — UI Consistency

Comment on lines +797 to +810
.alert-glass {
--alert-glass-tint: transparent;

background:
linear-gradient(
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent),
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent)
),
color-mix(in srgb, var(--background) var(--glass-opacity), transparent) !important;
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
}

.alert-glass[data-variant="error"] {

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.

alert-glass is already defined as @utility alert-glass (line 284) and is what ProviderStatusBanner consumes. This second definition in @layer components duplicates it and omits the utility's @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) opaque fallback; because !important declarations resolve in reverse layer order, this components-layer background: ... !important outranks the utility's important fallback, so browsers without backdrop-filter get a translucent, unblurred banner instead of the solid background. Suggest deleting this block and keeping the @utility as the single owner.

Posted via Macroscope — UI Consistency

Comment on lines +765 to +774
.surface-subheader {
@apply flex h-10 min-h-10 shrink-0 items-center border-b border-border/60 bg-background;
}

[data-preview-panel-mode="inline"] [data-right-panel-surface-content] [data-surface-subheader] {
height: calc(var(--spacing) * 7);
min-height: calc(var(--spacing) * 7);
margin-bottom: calc(var(--spacing) * 3);
border-bottom-color: transparent;
}

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.

.surface-subheader has no consumer: all four subheaders (DiffPanelShell, FileBrowserPanel, FilePreviewPanel, PreviewChromeRow) still carry the same geometry inline and only set data-surface-subheader. The inline-mode rule below is also redundant — every carrier already has in-data-[preview-panel-mode=inline]:h-7/min-h-7/mb-3/border-b-transparent, and those utilities sort after @layer components, so they win wherever this rule matches. Either apply surface-subheader at those call sites and drop the duplicated utility strings, or drop both rules so the shared geometry keeps one owner.

Posted via Macroscope — UI Consistency

className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The label previously animated transform/opacity on an inner data-composer-label-motion span with motion-reduce:transform-none motion-reduce:transition-opacity; this replaces it with a max-width transition and no reduced-motion guard, so reduced-motion users now get an animated layout change on every collapse. Same class appears at line 79 and in BranchToolbarEnvModeSelector/BranchToolbarBranchSelector.

Suggested change
className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0"
className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"

Posted via Macroscope — UI Consistency

"hover:!bg-black/[0.055] data-pressed:!bg-black/[0.055] dark:hover:!bg-white/[0.075] dark:data-pressed:!bg-white/[0.075]";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These row classes override Button's core contract at the call site — height (h-9 sm:h-9), radius, padding, and base/hover/pressed background, with ! needed to beat the ghost variant's own hover colors and raw black/white alpha instead of semantic tokens. Since this treatment is now shared by every panel row (select rows, link rows, split halves, icon actions), a named Button size/variant (e.g. size="panel-row" plus a muted-ghost variant) would express it in the primitive and drop the !important fights and the sm: re-statements; contextual width/color could stay here.

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $250.50, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

FileDiff SizeEstimate
apps/server/src/orchestration-v2/Orchestrator.ts280.33KB$14.02
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts272.17KB$13.61
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts238.01KB$11.90

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mwolson@t3dotgg@juliusmarminge@PixPMusic@nsxdavid@Yusuf007R@maria-rcks
, '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

fix(orchestrator): Keep Home, steers, and errors honest when work ends - #5459

Open
mwolson wants to merge 348 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orchestrator-settled-lifecycle
Open

fix(orchestrator): Keep Home, steers, and errors honest when work ends#5459
mwolson wants to merge 348 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orchestrator-settled-lifecycle

Conversation

@mwolson

@mwolsonmwolson commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What Changed

You can steer a turn while the agent is still working. The run could settle anyway, and the later answer vanished. That later text now stays on the thread.

A provider that never started could leave the thread looking alive forever. Exhausted retries now fail the run.

You settle a thread, and Home still treats it like live work. Missing snapshots keep retrying, archived threads drift back, and Waiting keeps refreshing stale rows. Settled now stays settled. A gone thread stops retrying.

You dismiss a thread error, and a later one never appears, or the old one never leaves. Dismissal lasts until a later error. Plan mode off still left the Build/Plan toggle in the composer after the v2 chat rewrite. The toggle is hidden in that case.

Why

Settle meant the useful work was over in the UI while the agent, the outbox, or a later error was still in play. Home, Waiting, and the composer should follow that same rule.

Julius's bounded thread history on current CTM stays as shipped.

UI Changes

Steered output stays in the thread. Failed starts no longer hang as if they were still starting. Home and Waiting settled state is quieter. Thread error banners dismiss until a later error. Build/Plan is hidden when plan mode is disabled.

No before/after screenshots.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Changes core orchestration paths—settlement merging, command commit guards, Claude turn lifecycle, and process-loss reconciliation—with race-sensitive behavior that can leave runs stuck or drop recovery if callers mishandle committed: false.

Overview
Threads gain settledOverrideAt so settle/keep-active pins have a stable establishment time that survives renames and is exposed on shells and mobile projections.

Settlement is tightened end-to-end: settle is blocked on queued runs, pending runtime requests, and provider background work; archived threads cannot settle/unsettle; pin/unsettle paths maintain the new timestamp; message dispatch clears overrides via a shared helper. Settled/unsettled events are merged through reduceThreadSettlementEvent in memory and in SQL so stale full-thread payloads cannot overwrite title or other metadata. Provider ingest tests document when pending thread-scoped requests should emit thread.unsettled without waking keep-active or non-activity updates.

Claude steering tracks per-turn steer counts and frame counts, detects steering handoff results before terminalizing, marks in-flight tools interrupted, and bounds silent handoffs with a timeout that fails the turn if no new frames arrive. Turn finalization is claimed up front to avoid duplicate terminals when the handoff fiber races the frame handler; new turns wait while a turn is finalizing.

Recovery adds optional updatedBefore filters on outbox cancel/reconcile and expectedThreadUpdatedAt on commitCommand so stale recovery writes no-op when the thread has already advanced.

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

Note

Add Orchestration V2 system with provider adapters, persistence, and V2 UI migration

  • Introduces the complete Orchestration V2 architecture: contracts, event store, projection store, command receipts, effect outbox/worker, runtime recovery, and database migrations 044–052
  • Adds V2 provider adapters for Claude, Codex, Cursor, Grok, OpenCode, and ACP Registry through the ProviderAdapterV2 service contract and driver registry, with replay testkits for deterministic testing
  • Implements thread lifecycle services for launch, fork, settlement, checkpoint capture/rollback, context handoff, continuation, run finalization, and provider-session management with idle timeout and shutdown
  • Migrates client-runtime, web, and mobile state from legacy orchestration types to V2 thread projections, bounded shell snapshots, runtime summaries, and progressive history paging
  • Adds MCP orchestrator and worktree toolkits, scheduled-task service with interval and fixed-time scheduling, T3 orchestration instructions, and a legacy V1 thread importer with idempotent shell and transcript hydration
  • Risk: migrations 044–052 create new schema tables and copy existing V2/project data into a shared application event log; EnvironmentThreadState in threadState.ts replaces legacy thread/page fields with OrchestrationV2ThreadProjection and ThreadHistoryMeta, removing threadHasOlderTurns; RPC authorization scopes in RpcAuthorization.ts are remapped to V2 method constants

Macroscope summarized 674b0e4.

@coderabbitai

coderabbitaiBot commented Aug 5, 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: 7726a859-eb6b-4efc-9e83-8c6c368e6e50

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 5, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One convention issue found in the new provider-ingest settlement code: a statically known tagged failure is recovered with Effect.catchTag instead of Effect.catchTags.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts
Comment threadpackages/client-runtime/src/state/shellReducer.ts Outdated
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts
@macroscopeapp

macroscopeappBot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This is a 964-file Orchestration V2 architecture and persistence migration that adds provider integrations, new workflows, protocol/schema changes, and product-default changes across server, web, and mobile. Unresolved projection and provider-lifecycle race findings add material consistency risk beyond the scope of an auto-approvable fix.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts Outdated
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from 5229063 to 6c322c7CompareAugust 6, 2026 00:13
@mwolsonmwolson changed the title fix(orchestrator): Stabilize recovery and settled-thread lifecyclefix(orchestrator): Stabilize settled-thread lifecycleAug 6, 2026
Comment threadpackages/shared/src/orchestrationV2Settled.ts
Comment threadpackages/shared/src/orchestrationV2Settled.ts
@mwolsonmwolson changed the title fix(orchestrator): Stabilize settled-thread lifecyclefix(orchestrator): Keep Home and settle honest when work endsAug 6, 2026
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 6, 2026
Port pingdotgg#5459 settled-thread lifecycle follow-ups: pin clear on settle, preserve
newer updatedAt on activity unsettle, fresher archive membership, settle runs
filter, and typed activity-unsettle prep errors.
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 25de21d to 0af2a6eCompareAugust 7, 2026 12:10
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 8, 2026
pingdotgg#5459's ProjectionSnapshotQuery.settled test must provide the same
ThreadBackgroundLiveness and ThreadPlanProgress layers the live snapshot
query now requires on CTM.
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from 0a6ccc1 to 5908b04CompareAugust 8, 2026 18:43
Comment threadpackages/shared/src/orchestrationV2Settled.ts
Comment threadapps/server/src/orchestration-v2/ProjectionStore.ts
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts Outdated
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 22bd872 to a27c1ccCompareAugust 10, 2026 17:05
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 11, 2026
Comment threadapps/server/src/orchestration-v2/ProjectionStore.ts
Comment threadapps/mobile/src/state/use-thread-selection.ts
Comment threadpackages/client-runtime/src/state/shell.ts
Comment threadpackages/contracts/src/orchestrationV2.ts
Comment threadapps/server/src/orchestration-v2/EffectOutbox.ts
Comment threadpackages/client-runtime/src/state/threads.ts

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

Effect service conventions review of the settle-lifecycle changes. The previously flagged Effect.catchTag usage in ProviderEventIngestor.ts is now Effect.catchTags({ ... }), and the new/changed service code (ProjectionStore, EventSink, EffectOutbox, ProviderRuntimeRecoveryService, ProviderEventIngestor, shared settlement helpers) follows the import, layer, and dependency-acquisition rules. Two small change-discipline findings in packages/client-runtime/src/state/threadSettled.ts.

Posted via Macroscope — Effect Service Conventions

Comment threadpackages/client-runtime/src/state/threadSettled.ts Outdated
Comment threadpackages/client-runtime/src/state/threadSettled.ts
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from dae270f to b27bee8CompareAugust 11, 2026 13:41
Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts Outdated
Comment threadpackages/client-runtime/src/state/threadSettled.ts Outdated
Comment threadpackages/client-runtime/src/state/shell.ts
Comment threadpackages/client-runtime/src/state/threadSettled.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from a186d64 to 5b1a115CompareAugust 12, 2026 23:19
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from a6e0049 to 50a57b6CompareSeptember 1, 2026 05:36
The v2 equivalents of main's pingdotgg#8984 and pingdotgg#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>
mwolson added a commit to mwolson/t3code that referenced this pull request Sep 1, 2026
OpenCode 2 now writes an occurrence timestamp when lastError is set or
cleared. The same message after a clear is a new occurrence; unchanged
text on an uncleared error keeps the original timestamp.
The optional lastErrorAt field is added to the provider-session contract
so the adapter can persist it without depending on pingdotgg#5459 settled-thread
lifecycle.
Stop retrying definitive missing thread snapshots, normalize V2 shell archive
membership, preserve provider retry metadata, and harden settled projection
ordering, activity unsettle, ingest timestamps, snapshot reads, and Waiting
lifecycle invariants.
Settlement-only merge now applies pinnedAt on explicit settle so pins still
clear, and activity unsettles no longer rewind updatedAt behind a newer
metadata bump.
Add settledOverrideAt on V2 app threads so keep-active and settled pins keep a
stable establishment clock across metadata renames. Prefer that field in the
activity-unsettle ordering guard, with settledAt/updatedAt fallbacks for
pre-field rows, and stamp it on settle, keep-active, and activity clear paths.
Pass settledOverrideAt through the client reduceThreadSettlementEvent path so
live sync matches the server pin floor, stamp a new keep-active floor when pin
promotes a settled thread, and keep existing active floors when re-pinning.
Re-apply the stranded ChatView and ChatComposer half of pingdotgg#5551 on CTM. Main
already gates the Build/Plan toggle behind planModeEnabled, but the v2 ChatView
rewrite left the beta setting and Beta panel in place while re-exposing the
toggle, /plan and /default, and Shift+Tab.
When the flag is off, force the effective interaction mode to default so a
thread with a stored plan mode cannot stay trapped after the control is hidden.
The next send persists default. Slash commands send as plain text, and the
keyboard shortcut is a no-op.
pingdotgg#6123 thread-error-banner-dismiss remembers the remount-safe banner key as
thread plus message. After that landed on CTM, dismissing one durable
failure hid later identical text for the rest of the app session.
Key the session mask with the occurrence timestamp when present so a later
run with the same lastError still shows.
The 30-minute idle reaper still tears down the live provider process. It
no longer writes lastError null on idle, manual, or server stop. Release
reads the latest projected session so an adapter-stamped error survives
process teardown.
A repeated process failure must get a new occurrence timestamp so a
dismissed banner can reappear. Idle release still preserves the prior
timestamp. The web banner key uses lastErrorAt only and no longer
falls back to updatedAt.
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from ae21654 to 57fca87CompareSeptember 1, 2026 23:52
const nextThread = reduceThreadSettlementEvent({
current: currentThread,
eventType: event.type,
settlement: {

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/ProjectionStore.ts:1261

thread.unsettled events lose event.payload.unsettledAt in the durable projection, so shell snapshots report unsettledAt as the previous value (often null) instead of when the thread was explicitly kept active. Include unsettledAt in the settlement merge.

 settlement: {
+ unsettledAt: event.payload.unsettledAt ?? null,
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/ProjectionStore.ts around line 1261:
`thread.unsettled` events lose `event.payload.unsettledAt` in the durable projection, so shell snapshots report `unsettledAt` as the previous value (often `null`) instead of when the thread was explicitly kept active. Include `unsettledAt` in the settlement merge.

// Only a definitive missing thread skips the candidate. Other read
// failures must surface so callers can retry rather than silently
// store activity without an unsettle attempt.
const projectionOption = yield* projectionStore.getThreadProjection(threadId).pipe(

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:356

Archived threads receive a thread.unsettled event for pending non-auth runtime-request.updated activity, clearing their settledOverride instead of leaving archived settlement state unchanged. getThreadProjection returns the still-present archived projection, and this path does not reject it before appending the synthetic event; restrict candidate generation to active threads.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/ProviderEventIngestor.ts around line 356:
Archived threads receive a `thread.unsettled` event for pending non-auth `runtime-request.updated` activity, clearing their `settledOverride` instead of leaving archived settlement state unchanged. `getThreadProjection` returns the still-present archived projection, and this path does not reject it before appending the synthetic event; restrict candidate generation to active threads.

Comment threadapps/server/src/orchestration-v2/ProviderSessionManager.ts
if (nextThread === projection.thread) {
return projection;
}
return { ...base, thread: nextThread };

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.

🟡 Mediumstate/orchestrationV2Projection.ts:207

A delayed thread.unsettled rewinds projection.updatedAt to event.occurredAt, even when the projection already has a newer timestamp from a concurrent metadata update. Although reduceThreadSettlementEvent preserves the newer thread.updatedAt, spreading base here still makes client ordering/activity views treat the thread as stale; preserve the later of the existing projection timestamp and event.occurredAt, as the server reducer does.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/orchestrationV2Projection.ts around line 207:
A delayed `thread.unsettled` rewinds `projection.updatedAt` to `event.occurredAt`, even when the projection already has a newer timestamp from a concurrent metadata update. Although `reduceThreadSettlementEvent` preserves the newer `thread.updatedAt`, spreading `base` here still makes client ordering/activity views treat the thread as stale; preserve the later of the existing projection timestamp and `event.occurredAt`, as the server reducer does.

@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 OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 57fca87. Configure here.

Comment threadapps/server/src/orchestration-v2/ProviderTurnStartService.ts
const next = new Set(current);
next.add(input.context.providerTurnId);
return next;
});

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.

Finalize claim races new turns

Medium Severity

finalizeActiveTurn nulls activeTurn and only then adds the id to finalizingTurnIds. startTurn checks those refs separately, so a concurrent start (the handoff timeout is forked) can observe both empty and open a second provider turn while the first is still emitting terminals.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 57fca87. Configure here.

@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 6081201 to 95bacddCompareSeptember 2, 2026 07:19
…d-letters
Wire failFromDeadLetter onto the turn-start service and compensate before
the outbox fails a start or restart that exhausted retries, matching the
v2.1 port.

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

Scope note: only apps/web/src/** TS/TSX/CSS changes were reviewed. Findings are the newly added global CSS in index.css (dead/duplicated rules) and the composer context strip's motion contract, which changed lines break while the code that depends on it is retained.

Posted via Macroscope — UI Consistency

aria-label="Run on"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The strip's compact/expand FLIP animation still keys on [data-composer-context-control] (BranchToolbar.tsx:232, consumed by useLabelsOverflow), but this PR removes that attribute from the environment selector, the env-mode selector (locked and select paths) and the branch selector. The only remaining carrier is the vertical Separator at BranchToolbar.tsx:545, so collapsing now animates a 1px separator while the actual controls jump to their new positions. Consider keeping the attribute on the toolbar-mode triggers (it is inert in panel mode), or removing the FLIP pass as well if the motion is intentionally gone.

Suggested change
aria-label="Run on"
aria-label="Run on"
data-composer-context-control

Posted via Macroscope — UI Consistency

Comment on lines +826 to +836
.glass-opacity-slider {
--glass-slider-progress: 0%;
--glass-slider-fill-offset: 0.5rem;
--glass-slider-fill-position: calc(
var(--glass-slider-progress) + var(--glass-slider-fill-offset)
);
appearance: none;
height: 1.5rem;
background: transparent;
cursor: pointer;
}

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.

This whole .glass-opacity-slider block (through the forced-colors rules) is unreferenced — the glass-opacity input in SettingsPanels.tsx:1111 uses settings-slider with --settings-slider-progress, and no literal or composed glass-opacity-slider class exists anywhere in the repo. It is also a verbatim copy of .settings-slider below with only the custom-property names changed, so it adds a second owner for the same slider treatment. Suggest dropping it (or, if a distinct slider is intended, wiring a call site and deriving it from .settings-slider rather than duplicating the vendor pseudo-element rules).

Posted via Macroscope — UI Consistency

Comment on lines +797 to +810
.alert-glass {
--alert-glass-tint: transparent;

background:
linear-gradient(
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent),
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent)
),
color-mix(in srgb, var(--background) var(--glass-opacity), transparent) !important;
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
}

.alert-glass[data-variant="error"] {

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.

alert-glass is already defined as @utility alert-glass (line 284) and is what ProviderStatusBanner consumes. This second definition in @layer components duplicates it and omits the utility's @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) opaque fallback; because !important declarations resolve in reverse layer order, this components-layer background: ... !important outranks the utility's important fallback, so browsers without backdrop-filter get a translucent, unblurred banner instead of the solid background. Suggest deleting this block and keeping the @utility as the single owner.

Posted via Macroscope — UI Consistency

Comment on lines +765 to +774
.surface-subheader {
@apply flex h-10 min-h-10 shrink-0 items-center border-b border-border/60 bg-background;
}

[data-preview-panel-mode="inline"] [data-right-panel-surface-content] [data-surface-subheader] {
height: calc(var(--spacing) * 7);
min-height: calc(var(--spacing) * 7);
margin-bottom: calc(var(--spacing) * 3);
border-bottom-color: transparent;
}

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.

.surface-subheader has no consumer: all four subheaders (DiffPanelShell, FileBrowserPanel, FilePreviewPanel, PreviewChromeRow) still carry the same geometry inline and only set data-surface-subheader. The inline-mode rule below is also redundant — every carrier already has in-data-[preview-panel-mode=inline]:h-7/min-h-7/mb-3/border-b-transparent, and those utilities sort after @layer components, so they win wherever this rule matches. Either apply surface-subheader at those call sites and drop the duplicated utility strings, or drop both rules so the shared geometry keeps one owner.

Posted via Macroscope — UI Consistency

className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The label previously animated transform/opacity on an inner data-composer-label-motion span with motion-reduce:transform-none motion-reduce:transition-opacity; this replaces it with a max-width transition and no reduced-motion guard, so reduced-motion users now get an animated layout change on every collapse. Same class appears at line 79 and in BranchToolbarEnvModeSelector/BranchToolbarBranchSelector.

Suggested change
className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0"
className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"

Posted via Macroscope — UI Consistency

"hover:!bg-black/[0.055] data-pressed:!bg-black/[0.055] dark:hover:!bg-white/[0.075] dark:data-pressed:!bg-white/[0.075]";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These row classes override Button's core contract at the call site — height (h-9 sm:h-9), radius, padding, and base/hover/pressed background, with ! needed to beat the ghost variant's own hover colors and raw black/white alpha instead of semantic tokens. Since this treatment is now shared by every panel row (select rows, link rows, split halves, icon actions), a named Button size/variant (e.g. size="panel-row" plus a muted-ghost variant) would express it in the primitive and drop the !important fights and the sm: re-statements; contextual width/color could stay here.

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $250.50, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

FileDiff SizeEstimate
apps/server/src/orchestration-v2/Orchestrator.ts280.33KB$14.02
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts272.17KB$13.61
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts238.01KB$11.90

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mwolson@t3dotgg@juliusmarminge@PixPMusic@nsxdavid@Yusuf007R@maria-rcks
, '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

fix(orchestrator): Keep Home, steers, and errors honest when work ends - #5459

Open
mwolson wants to merge 348 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orchestrator-settled-lifecycle
Open

fix(orchestrator): Keep Home, steers, and errors honest when work ends#5459
mwolson wants to merge 348 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orchestrator-settled-lifecycle

Conversation

@mwolson

@mwolsonmwolson commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What Changed

You can steer a turn while the agent is still working. The run could settle anyway, and the later answer vanished. That later text now stays on the thread.

A provider that never started could leave the thread looking alive forever. Exhausted retries now fail the run.

You settle a thread, and Home still treats it like live work. Missing snapshots keep retrying, archived threads drift back, and Waiting keeps refreshing stale rows. Settled now stays settled. A gone thread stops retrying.

You dismiss a thread error, and a later one never appears, or the old one never leaves. Dismissal lasts until a later error. Plan mode off still left the Build/Plan toggle in the composer after the v2 chat rewrite. The toggle is hidden in that case.

Why

Settle meant the useful work was over in the UI while the agent, the outbox, or a later error was still in play. Home, Waiting, and the composer should follow that same rule.

Julius's bounded thread history on current CTM stays as shipped.

UI Changes

Steered output stays in the thread. Failed starts no longer hang as if they were still starting. Home and Waiting settled state is quieter. Thread error banners dismiss until a later error. Build/Plan is hidden when plan mode is disabled.

No before/after screenshots.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Changes core orchestration paths—settlement merging, command commit guards, Claude turn lifecycle, and process-loss reconciliation—with race-sensitive behavior that can leave runs stuck or drop recovery if callers mishandle committed: false.

Overview
Threads gain settledOverrideAt so settle/keep-active pins have a stable establishment time that survives renames and is exposed on shells and mobile projections.

Settlement is tightened end-to-end: settle is blocked on queued runs, pending runtime requests, and provider background work; archived threads cannot settle/unsettle; pin/unsettle paths maintain the new timestamp; message dispatch clears overrides via a shared helper. Settled/unsettled events are merged through reduceThreadSettlementEvent in memory and in SQL so stale full-thread payloads cannot overwrite title or other metadata. Provider ingest tests document when pending thread-scoped requests should emit thread.unsettled without waking keep-active or non-activity updates.

Claude steering tracks per-turn steer counts and frame counts, detects steering handoff results before terminalizing, marks in-flight tools interrupted, and bounds silent handoffs with a timeout that fails the turn if no new frames arrive. Turn finalization is claimed up front to avoid duplicate terminals when the handoff fiber races the frame handler; new turns wait while a turn is finalizing.

Recovery adds optional updatedBefore filters on outbox cancel/reconcile and expectedThreadUpdatedAt on commitCommand so stale recovery writes no-op when the thread has already advanced.

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

Note

Add Orchestration V2 system with provider adapters, persistence, and V2 UI migration

  • Introduces the complete Orchestration V2 architecture: contracts, event store, projection store, command receipts, effect outbox/worker, runtime recovery, and database migrations 044–052
  • Adds V2 provider adapters for Claude, Codex, Cursor, Grok, OpenCode, and ACP Registry through the ProviderAdapterV2 service contract and driver registry, with replay testkits for deterministic testing
  • Implements thread lifecycle services for launch, fork, settlement, checkpoint capture/rollback, context handoff, continuation, run finalization, and provider-session management with idle timeout and shutdown
  • Migrates client-runtime, web, and mobile state from legacy orchestration types to V2 thread projections, bounded shell snapshots, runtime summaries, and progressive history paging
  • Adds MCP orchestrator and worktree toolkits, scheduled-task service with interval and fixed-time scheduling, T3 orchestration instructions, and a legacy V1 thread importer with idempotent shell and transcript hydration
  • Risk: migrations 044–052 create new schema tables and copy existing V2/project data into a shared application event log; EnvironmentThreadState in threadState.ts replaces legacy thread/page fields with OrchestrationV2ThreadProjection and ThreadHistoryMeta, removing threadHasOlderTurns; RPC authorization scopes in RpcAuthorization.ts are remapped to V2 method constants

Macroscope summarized 674b0e4.

@coderabbitai

coderabbitaiBot commented Aug 5, 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: 7726a859-eb6b-4efc-9e83-8c6c368e6e50

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 5, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One convention issue found in the new provider-ingest settlement code: a statically known tagged failure is recovered with Effect.catchTag instead of Effect.catchTags.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts
Comment threadpackages/client-runtime/src/state/shellReducer.ts Outdated
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts
@macroscopeapp

macroscopeappBot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This is a 964-file Orchestration V2 architecture and persistence migration that adds provider integrations, new workflows, protocol/schema changes, and product-default changes across server, web, and mobile. Unresolved projection and provider-lifecycle race findings add material consistency risk beyond the scope of an auto-approvable fix.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts Outdated
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from 5229063 to 6c322c7CompareAugust 6, 2026 00:13
@mwolsonmwolson changed the title fix(orchestrator): Stabilize recovery and settled-thread lifecyclefix(orchestrator): Stabilize settled-thread lifecycleAug 6, 2026
Comment threadpackages/shared/src/orchestrationV2Settled.ts
Comment threadpackages/shared/src/orchestrationV2Settled.ts
@mwolsonmwolson changed the title fix(orchestrator): Stabilize settled-thread lifecyclefix(orchestrator): Keep Home and settle honest when work endsAug 6, 2026
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 6, 2026
Port pingdotgg#5459 settled-thread lifecycle follow-ups: pin clear on settle, preserve
newer updatedAt on activity unsettle, fresher archive membership, settle runs
filter, and typed activity-unsettle prep errors.
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 25de21d to 0af2a6eCompareAugust 7, 2026 12:10
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 8, 2026
pingdotgg#5459's ProjectionSnapshotQuery.settled test must provide the same
ThreadBackgroundLiveness and ThreadPlanProgress layers the live snapshot
query now requires on CTM.
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from 0a6ccc1 to 5908b04CompareAugust 8, 2026 18:43
Comment threadpackages/shared/src/orchestrationV2Settled.ts
Comment threadapps/server/src/orchestration-v2/ProjectionStore.ts
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts Outdated
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 22bd872 to a27c1ccCompareAugust 10, 2026 17:05
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 11, 2026
Comment threadapps/server/src/orchestration-v2/ProjectionStore.ts
Comment threadapps/mobile/src/state/use-thread-selection.ts
Comment threadpackages/client-runtime/src/state/shell.ts
Comment threadpackages/contracts/src/orchestrationV2.ts
Comment threadapps/server/src/orchestration-v2/EffectOutbox.ts
Comment threadpackages/client-runtime/src/state/threads.ts

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

Effect service conventions review of the settle-lifecycle changes. The previously flagged Effect.catchTag usage in ProviderEventIngestor.ts is now Effect.catchTags({ ... }), and the new/changed service code (ProjectionStore, EventSink, EffectOutbox, ProviderRuntimeRecoveryService, ProviderEventIngestor, shared settlement helpers) follows the import, layer, and dependency-acquisition rules. Two small change-discipline findings in packages/client-runtime/src/state/threadSettled.ts.

Posted via Macroscope — Effect Service Conventions

Comment threadpackages/client-runtime/src/state/threadSettled.ts Outdated
Comment threadpackages/client-runtime/src/state/threadSettled.ts
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from dae270f to b27bee8CompareAugust 11, 2026 13:41
Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts Outdated
Comment threadpackages/client-runtime/src/state/threadSettled.ts Outdated
Comment threadpackages/client-runtime/src/state/shell.ts
Comment threadpackages/client-runtime/src/state/threadSettled.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from a186d64 to 5b1a115CompareAugust 12, 2026 23:19
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from a6e0049 to 50a57b6CompareSeptember 1, 2026 05:36
The v2 equivalents of main's pingdotgg#8984 and pingdotgg#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>
mwolson added a commit to mwolson/t3code that referenced this pull request Sep 1, 2026
OpenCode 2 now writes an occurrence timestamp when lastError is set or
cleared. The same message after a clear is a new occurrence; unchanged
text on an uncleared error keeps the original timestamp.
The optional lastErrorAt field is added to the provider-session contract
so the adapter can persist it without depending on pingdotgg#5459 settled-thread
lifecycle.
Stop retrying definitive missing thread snapshots, normalize V2 shell archive
membership, preserve provider retry metadata, and harden settled projection
ordering, activity unsettle, ingest timestamps, snapshot reads, and Waiting
lifecycle invariants.
Settlement-only merge now applies pinnedAt on explicit settle so pins still
clear, and activity unsettles no longer rewind updatedAt behind a newer
metadata bump.
Add settledOverrideAt on V2 app threads so keep-active and settled pins keep a
stable establishment clock across metadata renames. Prefer that field in the
activity-unsettle ordering guard, with settledAt/updatedAt fallbacks for
pre-field rows, and stamp it on settle, keep-active, and activity clear paths.
Pass settledOverrideAt through the client reduceThreadSettlementEvent path so
live sync matches the server pin floor, stamp a new keep-active floor when pin
promotes a settled thread, and keep existing active floors when re-pinning.
Re-apply the stranded ChatView and ChatComposer half of pingdotgg#5551 on CTM. Main
already gates the Build/Plan toggle behind planModeEnabled, but the v2 ChatView
rewrite left the beta setting and Beta panel in place while re-exposing the
toggle, /plan and /default, and Shift+Tab.
When the flag is off, force the effective interaction mode to default so a
thread with a stored plan mode cannot stay trapped after the control is hidden.
The next send persists default. Slash commands send as plain text, and the
keyboard shortcut is a no-op.
pingdotgg#6123 thread-error-banner-dismiss remembers the remount-safe banner key as
thread plus message. After that landed on CTM, dismissing one durable
failure hid later identical text for the rest of the app session.
Key the session mask with the occurrence timestamp when present so a later
run with the same lastError still shows.
The 30-minute idle reaper still tears down the live provider process. It
no longer writes lastError null on idle, manual, or server stop. Release
reads the latest projected session so an adapter-stamped error survives
process teardown.
A repeated process failure must get a new occurrence timestamp so a
dismissed banner can reappear. Idle release still preserves the prior
timestamp. The web banner key uses lastErrorAt only and no longer
falls back to updatedAt.
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from ae21654 to 57fca87CompareSeptember 1, 2026 23:52
const nextThread = reduceThreadSettlementEvent({
current: currentThread,
eventType: event.type,
settlement: {

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/ProjectionStore.ts:1261

thread.unsettled events lose event.payload.unsettledAt in the durable projection, so shell snapshots report unsettledAt as the previous value (often null) instead of when the thread was explicitly kept active. Include unsettledAt in the settlement merge.

 settlement: {
+ unsettledAt: event.payload.unsettledAt ?? null,
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/ProjectionStore.ts around line 1261:
`thread.unsettled` events lose `event.payload.unsettledAt` in the durable projection, so shell snapshots report `unsettledAt` as the previous value (often `null`) instead of when the thread was explicitly kept active. Include `unsettledAt` in the settlement merge.

// Only a definitive missing thread skips the candidate. Other read
// failures must surface so callers can retry rather than silently
// store activity without an unsettle attempt.
const projectionOption = yield* projectionStore.getThreadProjection(threadId).pipe(

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:356

Archived threads receive a thread.unsettled event for pending non-auth runtime-request.updated activity, clearing their settledOverride instead of leaving archived settlement state unchanged. getThreadProjection returns the still-present archived projection, and this path does not reject it before appending the synthetic event; restrict candidate generation to active threads.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/ProviderEventIngestor.ts around line 356:
Archived threads receive a `thread.unsettled` event for pending non-auth `runtime-request.updated` activity, clearing their `settledOverride` instead of leaving archived settlement state unchanged. `getThreadProjection` returns the still-present archived projection, and this path does not reject it before appending the synthetic event; restrict candidate generation to active threads.

Comment threadapps/server/src/orchestration-v2/ProviderSessionManager.ts
if (nextThread === projection.thread) {
return projection;
}
return { ...base, thread: nextThread };

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.

🟡 Mediumstate/orchestrationV2Projection.ts:207

A delayed thread.unsettled rewinds projection.updatedAt to event.occurredAt, even when the projection already has a newer timestamp from a concurrent metadata update. Although reduceThreadSettlementEvent preserves the newer thread.updatedAt, spreading base here still makes client ordering/activity views treat the thread as stale; preserve the later of the existing projection timestamp and event.occurredAt, as the server reducer does.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/orchestrationV2Projection.ts around line 207:
A delayed `thread.unsettled` rewinds `projection.updatedAt` to `event.occurredAt`, even when the projection already has a newer timestamp from a concurrent metadata update. Although `reduceThreadSettlementEvent` preserves the newer `thread.updatedAt`, spreading `base` here still makes client ordering/activity views treat the thread as stale; preserve the later of the existing projection timestamp and `event.occurredAt`, as the server reducer does.

@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 OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 57fca87. Configure here.

Comment threadapps/server/src/orchestration-v2/ProviderTurnStartService.ts
const next = new Set(current);
next.add(input.context.providerTurnId);
return next;
});

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.

Finalize claim races new turns

Medium Severity

finalizeActiveTurn nulls activeTurn and only then adds the id to finalizingTurnIds. startTurn checks those refs separately, so a concurrent start (the handoff timeout is forked) can observe both empty and open a second provider turn while the first is still emitting terminals.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 57fca87. Configure here.

@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 6081201 to 95bacddCompareSeptember 2, 2026 07:19
…d-letters
Wire failFromDeadLetter onto the turn-start service and compensate before
the outbox fails a start or restart that exhausted retries, matching the
v2.1 port.

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

Scope note: only apps/web/src/** TS/TSX/CSS changes were reviewed. Findings are the newly added global CSS in index.css (dead/duplicated rules) and the composer context strip's motion contract, which changed lines break while the code that depends on it is retained.

Posted via Macroscope — UI Consistency

aria-label="Run on"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The strip's compact/expand FLIP animation still keys on [data-composer-context-control] (BranchToolbar.tsx:232, consumed by useLabelsOverflow), but this PR removes that attribute from the environment selector, the env-mode selector (locked and select paths) and the branch selector. The only remaining carrier is the vertical Separator at BranchToolbar.tsx:545, so collapsing now animates a 1px separator while the actual controls jump to their new positions. Consider keeping the attribute on the toolbar-mode triggers (it is inert in panel mode), or removing the FLIP pass as well if the motion is intentionally gone.

Suggested change
aria-label="Run on"
aria-label="Run on"
data-composer-context-control

Posted via Macroscope — UI Consistency

Comment on lines +826 to +836
.glass-opacity-slider {
--glass-slider-progress: 0%;
--glass-slider-fill-offset: 0.5rem;
--glass-slider-fill-position: calc(
var(--glass-slider-progress) + var(--glass-slider-fill-offset)
);
appearance: none;
height: 1.5rem;
background: transparent;
cursor: pointer;
}

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.

This whole .glass-opacity-slider block (through the forced-colors rules) is unreferenced — the glass-opacity input in SettingsPanels.tsx:1111 uses settings-slider with --settings-slider-progress, and no literal or composed glass-opacity-slider class exists anywhere in the repo. It is also a verbatim copy of .settings-slider below with only the custom-property names changed, so it adds a second owner for the same slider treatment. Suggest dropping it (or, if a distinct slider is intended, wiring a call site and deriving it from .settings-slider rather than duplicating the vendor pseudo-element rules).

Posted via Macroscope — UI Consistency

Comment on lines +797 to +810
.alert-glass {
--alert-glass-tint: transparent;

background:
linear-gradient(
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent),
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent)
),
color-mix(in srgb, var(--background) var(--glass-opacity), transparent) !important;
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
}

.alert-glass[data-variant="error"] {

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.

alert-glass is already defined as @utility alert-glass (line 284) and is what ProviderStatusBanner consumes. This second definition in @layer components duplicates it and omits the utility's @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) opaque fallback; because !important declarations resolve in reverse layer order, this components-layer background: ... !important outranks the utility's important fallback, so browsers without backdrop-filter get a translucent, unblurred banner instead of the solid background. Suggest deleting this block and keeping the @utility as the single owner.

Posted via Macroscope — UI Consistency

Comment on lines +765 to +774
.surface-subheader {
@apply flex h-10 min-h-10 shrink-0 items-center border-b border-border/60 bg-background;
}

[data-preview-panel-mode="inline"] [data-right-panel-surface-content] [data-surface-subheader] {
height: calc(var(--spacing) * 7);
min-height: calc(var(--spacing) * 7);
margin-bottom: calc(var(--spacing) * 3);
border-bottom-color: transparent;
}

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.

.surface-subheader has no consumer: all four subheaders (DiffPanelShell, FileBrowserPanel, FilePreviewPanel, PreviewChromeRow) still carry the same geometry inline and only set data-surface-subheader. The inline-mode rule below is also redundant — every carrier already has in-data-[preview-panel-mode=inline]:h-7/min-h-7/mb-3/border-b-transparent, and those utilities sort after @layer components, so they win wherever this rule matches. Either apply surface-subheader at those call sites and drop the duplicated utility strings, or drop both rules so the shared geometry keeps one owner.

Posted via Macroscope — UI Consistency

className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The label previously animated transform/opacity on an inner data-composer-label-motion span with motion-reduce:transform-none motion-reduce:transition-opacity; this replaces it with a max-width transition and no reduced-motion guard, so reduced-motion users now get an animated layout change on every collapse. Same class appears at line 79 and in BranchToolbarEnvModeSelector/BranchToolbarBranchSelector.

Suggested change
className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0"
className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"

Posted via Macroscope — UI Consistency

"hover:!bg-black/[0.055] data-pressed:!bg-black/[0.055] dark:hover:!bg-white/[0.075] dark:data-pressed:!bg-white/[0.075]";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These row classes override Button's core contract at the call site — height (h-9 sm:h-9), radius, padding, and base/hover/pressed background, with ! needed to beat the ghost variant's own hover colors and raw black/white alpha instead of semantic tokens. Since this treatment is now shared by every panel row (select rows, link rows, split halves, icon actions), a named Button size/variant (e.g. size="panel-row" plus a muted-ghost variant) would express it in the primitive and drop the !important fights and the sm: re-statements; contextual width/color could stay here.

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $250.50, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

FileDiff SizeEstimate
apps/server/src/orchestration-v2/Orchestrator.ts280.33KB$14.02
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts272.17KB$13.61
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts238.01KB$11.90

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mwolson@t3dotgg@juliusmarminge@PixPMusic@nsxdavid@Yusuf007R@maria-rcks
, '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

fix(orchestrator): Keep Home, steers, and errors honest when work ends - #5459

Open
mwolson wants to merge 348 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orchestrator-settled-lifecycle
Open

fix(orchestrator): Keep Home, steers, and errors honest when work ends#5459
mwolson wants to merge 348 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orchestrator-settled-lifecycle

Conversation

@mwolson

@mwolsonmwolson commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What Changed

You can steer a turn while the agent is still working. The run could settle anyway, and the later answer vanished. That later text now stays on the thread.

A provider that never started could leave the thread looking alive forever. Exhausted retries now fail the run.

You settle a thread, and Home still treats it like live work. Missing snapshots keep retrying, archived threads drift back, and Waiting keeps refreshing stale rows. Settled now stays settled. A gone thread stops retrying.

You dismiss a thread error, and a later one never appears, or the old one never leaves. Dismissal lasts until a later error. Plan mode off still left the Build/Plan toggle in the composer after the v2 chat rewrite. The toggle is hidden in that case.

Why

Settle meant the useful work was over in the UI while the agent, the outbox, or a later error was still in play. Home, Waiting, and the composer should follow that same rule.

Julius's bounded thread history on current CTM stays as shipped.

UI Changes

Steered output stays in the thread. Failed starts no longer hang as if they were still starting. Home and Waiting settled state is quieter. Thread error banners dismiss until a later error. Build/Plan is hidden when plan mode is disabled.

No before/after screenshots.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Changes core orchestration paths—settlement merging, command commit guards, Claude turn lifecycle, and process-loss reconciliation—with race-sensitive behavior that can leave runs stuck or drop recovery if callers mishandle committed: false.

Overview
Threads gain settledOverrideAt so settle/keep-active pins have a stable establishment time that survives renames and is exposed on shells and mobile projections.

Settlement is tightened end-to-end: settle is blocked on queued runs, pending runtime requests, and provider background work; archived threads cannot settle/unsettle; pin/unsettle paths maintain the new timestamp; message dispatch clears overrides via a shared helper. Settled/unsettled events are merged through reduceThreadSettlementEvent in memory and in SQL so stale full-thread payloads cannot overwrite title or other metadata. Provider ingest tests document when pending thread-scoped requests should emit thread.unsettled without waking keep-active or non-activity updates.

Claude steering tracks per-turn steer counts and frame counts, detects steering handoff results before terminalizing, marks in-flight tools interrupted, and bounds silent handoffs with a timeout that fails the turn if no new frames arrive. Turn finalization is claimed up front to avoid duplicate terminals when the handoff fiber races the frame handler; new turns wait while a turn is finalizing.

Recovery adds optional updatedBefore filters on outbox cancel/reconcile and expectedThreadUpdatedAt on commitCommand so stale recovery writes no-op when the thread has already advanced.

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

Note

Add Orchestration V2 system with provider adapters, persistence, and V2 UI migration

  • Introduces the complete Orchestration V2 architecture: contracts, event store, projection store, command receipts, effect outbox/worker, runtime recovery, and database migrations 044–052
  • Adds V2 provider adapters for Claude, Codex, Cursor, Grok, OpenCode, and ACP Registry through the ProviderAdapterV2 service contract and driver registry, with replay testkits for deterministic testing
  • Implements thread lifecycle services for launch, fork, settlement, checkpoint capture/rollback, context handoff, continuation, run finalization, and provider-session management with idle timeout and shutdown
  • Migrates client-runtime, web, and mobile state from legacy orchestration types to V2 thread projections, bounded shell snapshots, runtime summaries, and progressive history paging
  • Adds MCP orchestrator and worktree toolkits, scheduled-task service with interval and fixed-time scheduling, T3 orchestration instructions, and a legacy V1 thread importer with idempotent shell and transcript hydration
  • Risk: migrations 044–052 create new schema tables and copy existing V2/project data into a shared application event log; EnvironmentThreadState in threadState.ts replaces legacy thread/page fields with OrchestrationV2ThreadProjection and ThreadHistoryMeta, removing threadHasOlderTurns; RPC authorization scopes in RpcAuthorization.ts are remapped to V2 method constants

Macroscope summarized 674b0e4.

@coderabbitai

coderabbitaiBot commented Aug 5, 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: 7726a859-eb6b-4efc-9e83-8c6c368e6e50

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 5, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One convention issue found in the new provider-ingest settlement code: a statically known tagged failure is recovered with Effect.catchTag instead of Effect.catchTags.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts
Comment threadpackages/client-runtime/src/state/shellReducer.ts Outdated
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts
@macroscopeapp

macroscopeappBot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This is a 964-file Orchestration V2 architecture and persistence migration that adds provider integrations, new workflows, protocol/schema changes, and product-default changes across server, web, and mobile. Unresolved projection and provider-lifecycle race findings add material consistency risk beyond the scope of an auto-approvable fix.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts Outdated
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from 5229063 to 6c322c7CompareAugust 6, 2026 00:13
@mwolsonmwolson changed the title fix(orchestrator): Stabilize recovery and settled-thread lifecyclefix(orchestrator): Stabilize settled-thread lifecycleAug 6, 2026
Comment threadpackages/shared/src/orchestrationV2Settled.ts
Comment threadpackages/shared/src/orchestrationV2Settled.ts
@mwolsonmwolson changed the title fix(orchestrator): Stabilize settled-thread lifecyclefix(orchestrator): Keep Home and settle honest when work endsAug 6, 2026
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 6, 2026
Port pingdotgg#5459 settled-thread lifecycle follow-ups: pin clear on settle, preserve
newer updatedAt on activity unsettle, fresher archive membership, settle runs
filter, and typed activity-unsettle prep errors.
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 25de21d to 0af2a6eCompareAugust 7, 2026 12:10
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 8, 2026
pingdotgg#5459's ProjectionSnapshotQuery.settled test must provide the same
ThreadBackgroundLiveness and ThreadPlanProgress layers the live snapshot
query now requires on CTM.
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from 0a6ccc1 to 5908b04CompareAugust 8, 2026 18:43
Comment threadpackages/shared/src/orchestrationV2Settled.ts
Comment threadapps/server/src/orchestration-v2/ProjectionStore.ts
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts Outdated
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 22bd872 to a27c1ccCompareAugust 10, 2026 17:05
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 11, 2026
Comment threadapps/server/src/orchestration-v2/ProjectionStore.ts
Comment threadapps/mobile/src/state/use-thread-selection.ts
Comment threadpackages/client-runtime/src/state/shell.ts
Comment threadpackages/contracts/src/orchestrationV2.ts
Comment threadapps/server/src/orchestration-v2/EffectOutbox.ts
Comment threadpackages/client-runtime/src/state/threads.ts

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

Effect service conventions review of the settle-lifecycle changes. The previously flagged Effect.catchTag usage in ProviderEventIngestor.ts is now Effect.catchTags({ ... }), and the new/changed service code (ProjectionStore, EventSink, EffectOutbox, ProviderRuntimeRecoveryService, ProviderEventIngestor, shared settlement helpers) follows the import, layer, and dependency-acquisition rules. Two small change-discipline findings in packages/client-runtime/src/state/threadSettled.ts.

Posted via Macroscope — Effect Service Conventions

Comment threadpackages/client-runtime/src/state/threadSettled.ts Outdated
Comment threadpackages/client-runtime/src/state/threadSettled.ts
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from dae270f to b27bee8CompareAugust 11, 2026 13:41
Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts Outdated
Comment threadpackages/client-runtime/src/state/threadSettled.ts Outdated
Comment threadpackages/client-runtime/src/state/shell.ts
Comment threadpackages/client-runtime/src/state/threadSettled.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from a186d64 to 5b1a115CompareAugust 12, 2026 23:19
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from a6e0049 to 50a57b6CompareSeptember 1, 2026 05:36
The v2 equivalents of main's pingdotgg#8984 and pingdotgg#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>
mwolson added a commit to mwolson/t3code that referenced this pull request Sep 1, 2026
OpenCode 2 now writes an occurrence timestamp when lastError is set or
cleared. The same message after a clear is a new occurrence; unchanged
text on an uncleared error keeps the original timestamp.
The optional lastErrorAt field is added to the provider-session contract
so the adapter can persist it without depending on pingdotgg#5459 settled-thread
lifecycle.
Stop retrying definitive missing thread snapshots, normalize V2 shell archive
membership, preserve provider retry metadata, and harden settled projection
ordering, activity unsettle, ingest timestamps, snapshot reads, and Waiting
lifecycle invariants.
Settlement-only merge now applies pinnedAt on explicit settle so pins still
clear, and activity unsettles no longer rewind updatedAt behind a newer
metadata bump.
Add settledOverrideAt on V2 app threads so keep-active and settled pins keep a
stable establishment clock across metadata renames. Prefer that field in the
activity-unsettle ordering guard, with settledAt/updatedAt fallbacks for
pre-field rows, and stamp it on settle, keep-active, and activity clear paths.
Pass settledOverrideAt through the client reduceThreadSettlementEvent path so
live sync matches the server pin floor, stamp a new keep-active floor when pin
promotes a settled thread, and keep existing active floors when re-pinning.
Re-apply the stranded ChatView and ChatComposer half of pingdotgg#5551 on CTM. Main
already gates the Build/Plan toggle behind planModeEnabled, but the v2 ChatView
rewrite left the beta setting and Beta panel in place while re-exposing the
toggle, /plan and /default, and Shift+Tab.
When the flag is off, force the effective interaction mode to default so a
thread with a stored plan mode cannot stay trapped after the control is hidden.
The next send persists default. Slash commands send as plain text, and the
keyboard shortcut is a no-op.
pingdotgg#6123 thread-error-banner-dismiss remembers the remount-safe banner key as
thread plus message. After that landed on CTM, dismissing one durable
failure hid later identical text for the rest of the app session.
Key the session mask with the occurrence timestamp when present so a later
run with the same lastError still shows.
The 30-minute idle reaper still tears down the live provider process. It
no longer writes lastError null on idle, manual, or server stop. Release
reads the latest projected session so an adapter-stamped error survives
process teardown.
A repeated process failure must get a new occurrence timestamp so a
dismissed banner can reappear. Idle release still preserves the prior
timestamp. The web banner key uses lastErrorAt only and no longer
falls back to updatedAt.
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from ae21654 to 57fca87CompareSeptember 1, 2026 23:52
const nextThread = reduceThreadSettlementEvent({
current: currentThread,
eventType: event.type,
settlement: {

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/ProjectionStore.ts:1261

thread.unsettled events lose event.payload.unsettledAt in the durable projection, so shell snapshots report unsettledAt as the previous value (often null) instead of when the thread was explicitly kept active. Include unsettledAt in the settlement merge.

 settlement: {
+ unsettledAt: event.payload.unsettledAt ?? null,
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/ProjectionStore.ts around line 1261:
`thread.unsettled` events lose `event.payload.unsettledAt` in the durable projection, so shell snapshots report `unsettledAt` as the previous value (often `null`) instead of when the thread was explicitly kept active. Include `unsettledAt` in the settlement merge.

// Only a definitive missing thread skips the candidate. Other read
// failures must surface so callers can retry rather than silently
// store activity without an unsettle attempt.
const projectionOption = yield* projectionStore.getThreadProjection(threadId).pipe(

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:356

Archived threads receive a thread.unsettled event for pending non-auth runtime-request.updated activity, clearing their settledOverride instead of leaving archived settlement state unchanged. getThreadProjection returns the still-present archived projection, and this path does not reject it before appending the synthetic event; restrict candidate generation to active threads.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/ProviderEventIngestor.ts around line 356:
Archived threads receive a `thread.unsettled` event for pending non-auth `runtime-request.updated` activity, clearing their `settledOverride` instead of leaving archived settlement state unchanged. `getThreadProjection` returns the still-present archived projection, and this path does not reject it before appending the synthetic event; restrict candidate generation to active threads.

Comment threadapps/server/src/orchestration-v2/ProviderSessionManager.ts
if (nextThread === projection.thread) {
return projection;
}
return { ...base, thread: nextThread };

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.

🟡 Mediumstate/orchestrationV2Projection.ts:207

A delayed thread.unsettled rewinds projection.updatedAt to event.occurredAt, even when the projection already has a newer timestamp from a concurrent metadata update. Although reduceThreadSettlementEvent preserves the newer thread.updatedAt, spreading base here still makes client ordering/activity views treat the thread as stale; preserve the later of the existing projection timestamp and event.occurredAt, as the server reducer does.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/orchestrationV2Projection.ts around line 207:
A delayed `thread.unsettled` rewinds `projection.updatedAt` to `event.occurredAt`, even when the projection already has a newer timestamp from a concurrent metadata update. Although `reduceThreadSettlementEvent` preserves the newer `thread.updatedAt`, spreading `base` here still makes client ordering/activity views treat the thread as stale; preserve the later of the existing projection timestamp and `event.occurredAt`, as the server reducer does.

@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 OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 57fca87. Configure here.

Comment threadapps/server/src/orchestration-v2/ProviderTurnStartService.ts
const next = new Set(current);
next.add(input.context.providerTurnId);
return next;
});

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.

Finalize claim races new turns

Medium Severity

finalizeActiveTurn nulls activeTurn and only then adds the id to finalizingTurnIds. startTurn checks those refs separately, so a concurrent start (the handoff timeout is forked) can observe both empty and open a second provider turn while the first is still emitting terminals.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 57fca87. Configure here.

@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 6081201 to 95bacddCompareSeptember 2, 2026 07:19
…d-letters
Wire failFromDeadLetter onto the turn-start service and compensate before
the outbox fails a start or restart that exhausted retries, matching the
v2.1 port.

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

Scope note: only apps/web/src/** TS/TSX/CSS changes were reviewed. Findings are the newly added global CSS in index.css (dead/duplicated rules) and the composer context strip's motion contract, which changed lines break while the code that depends on it is retained.

Posted via Macroscope — UI Consistency

aria-label="Run on"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The strip's compact/expand FLIP animation still keys on [data-composer-context-control] (BranchToolbar.tsx:232, consumed by useLabelsOverflow), but this PR removes that attribute from the environment selector, the env-mode selector (locked and select paths) and the branch selector. The only remaining carrier is the vertical Separator at BranchToolbar.tsx:545, so collapsing now animates a 1px separator while the actual controls jump to their new positions. Consider keeping the attribute on the toolbar-mode triggers (it is inert in panel mode), or removing the FLIP pass as well if the motion is intentionally gone.

Suggested change
aria-label="Run on"
aria-label="Run on"
data-composer-context-control

Posted via Macroscope — UI Consistency

Comment on lines +826 to +836
.glass-opacity-slider {
--glass-slider-progress: 0%;
--glass-slider-fill-offset: 0.5rem;
--glass-slider-fill-position: calc(
var(--glass-slider-progress) + var(--glass-slider-fill-offset)
);
appearance: none;
height: 1.5rem;
background: transparent;
cursor: pointer;
}

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.

This whole .glass-opacity-slider block (through the forced-colors rules) is unreferenced — the glass-opacity input in SettingsPanels.tsx:1111 uses settings-slider with --settings-slider-progress, and no literal or composed glass-opacity-slider class exists anywhere in the repo. It is also a verbatim copy of .settings-slider below with only the custom-property names changed, so it adds a second owner for the same slider treatment. Suggest dropping it (or, if a distinct slider is intended, wiring a call site and deriving it from .settings-slider rather than duplicating the vendor pseudo-element rules).

Posted via Macroscope — UI Consistency

Comment on lines +797 to +810
.alert-glass {
--alert-glass-tint: transparent;

background:
linear-gradient(
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent),
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent)
),
color-mix(in srgb, var(--background) var(--glass-opacity), transparent) !important;
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
}

.alert-glass[data-variant="error"] {

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.

alert-glass is already defined as @utility alert-glass (line 284) and is what ProviderStatusBanner consumes. This second definition in @layer components duplicates it and omits the utility's @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) opaque fallback; because !important declarations resolve in reverse layer order, this components-layer background: ... !important outranks the utility's important fallback, so browsers without backdrop-filter get a translucent, unblurred banner instead of the solid background. Suggest deleting this block and keeping the @utility as the single owner.

Posted via Macroscope — UI Consistency

Comment on lines +765 to +774
.surface-subheader {
@apply flex h-10 min-h-10 shrink-0 items-center border-b border-border/60 bg-background;
}

[data-preview-panel-mode="inline"] [data-right-panel-surface-content] [data-surface-subheader] {
height: calc(var(--spacing) * 7);
min-height: calc(var(--spacing) * 7);
margin-bottom: calc(var(--spacing) * 3);
border-bottom-color: transparent;
}

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.

.surface-subheader has no consumer: all four subheaders (DiffPanelShell, FileBrowserPanel, FilePreviewPanel, PreviewChromeRow) still carry the same geometry inline and only set data-surface-subheader. The inline-mode rule below is also redundant — every carrier already has in-data-[preview-panel-mode=inline]:h-7/min-h-7/mb-3/border-b-transparent, and those utilities sort after @layer components, so they win wherever this rule matches. Either apply surface-subheader at those call sites and drop the duplicated utility strings, or drop both rules so the shared geometry keeps one owner.

Posted via Macroscope — UI Consistency

className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The label previously animated transform/opacity on an inner data-composer-label-motion span with motion-reduce:transform-none motion-reduce:transition-opacity; this replaces it with a max-width transition and no reduced-motion guard, so reduced-motion users now get an animated layout change on every collapse. Same class appears at line 79 and in BranchToolbarEnvModeSelector/BranchToolbarBranchSelector.

Suggested change
className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0"
className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"

Posted via Macroscope — UI Consistency

"hover:!bg-black/[0.055] data-pressed:!bg-black/[0.055] dark:hover:!bg-white/[0.075] dark:data-pressed:!bg-white/[0.075]";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These row classes override Button's core contract at the call site — height (h-9 sm:h-9), radius, padding, and base/hover/pressed background, with ! needed to beat the ghost variant's own hover colors and raw black/white alpha instead of semantic tokens. Since this treatment is now shared by every panel row (select rows, link rows, split halves, icon actions), a named Button size/variant (e.g. size="panel-row" plus a muted-ghost variant) would express it in the primitive and drop the !important fights and the sm: re-statements; contextual width/color could stay here.

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $250.50, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

FileDiff SizeEstimate
apps/server/src/orchestration-v2/Orchestrator.ts280.33KB$14.02
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts272.17KB$13.61
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts238.01KB$11.90

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mwolson@t3dotgg@juliusmarminge@PixPMusic@nsxdavid@Yusuf007R@maria-rcks
, '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

fix(orchestrator): Keep Home, steers, and errors honest when work ends - #5459

Open
mwolson wants to merge 348 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orchestrator-settled-lifecycle
Open

fix(orchestrator): Keep Home, steers, and errors honest when work ends#5459
mwolson wants to merge 348 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orchestrator-settled-lifecycle

Conversation

@mwolson

@mwolsonmwolson commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What Changed

You can steer a turn while the agent is still working. The run could settle anyway, and the later answer vanished. That later text now stays on the thread.

A provider that never started could leave the thread looking alive forever. Exhausted retries now fail the run.

You settle a thread, and Home still treats it like live work. Missing snapshots keep retrying, archived threads drift back, and Waiting keeps refreshing stale rows. Settled now stays settled. A gone thread stops retrying.

You dismiss a thread error, and a later one never appears, or the old one never leaves. Dismissal lasts until a later error. Plan mode off still left the Build/Plan toggle in the composer after the v2 chat rewrite. The toggle is hidden in that case.

Why

Settle meant the useful work was over in the UI while the agent, the outbox, or a later error was still in play. Home, Waiting, and the composer should follow that same rule.

Julius's bounded thread history on current CTM stays as shipped.

UI Changes

Steered output stays in the thread. Failed starts no longer hang as if they were still starting. Home and Waiting settled state is quieter. Thread error banners dismiss until a later error. Build/Plan is hidden when plan mode is disabled.

No before/after screenshots.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Changes core orchestration paths—settlement merging, command commit guards, Claude turn lifecycle, and process-loss reconciliation—with race-sensitive behavior that can leave runs stuck or drop recovery if callers mishandle committed: false.

Overview
Threads gain settledOverrideAt so settle/keep-active pins have a stable establishment time that survives renames and is exposed on shells and mobile projections.

Settlement is tightened end-to-end: settle is blocked on queued runs, pending runtime requests, and provider background work; archived threads cannot settle/unsettle; pin/unsettle paths maintain the new timestamp; message dispatch clears overrides via a shared helper. Settled/unsettled events are merged through reduceThreadSettlementEvent in memory and in SQL so stale full-thread payloads cannot overwrite title or other metadata. Provider ingest tests document when pending thread-scoped requests should emit thread.unsettled without waking keep-active or non-activity updates.

Claude steering tracks per-turn steer counts and frame counts, detects steering handoff results before terminalizing, marks in-flight tools interrupted, and bounds silent handoffs with a timeout that fails the turn if no new frames arrive. Turn finalization is claimed up front to avoid duplicate terminals when the handoff fiber races the frame handler; new turns wait while a turn is finalizing.

Recovery adds optional updatedBefore filters on outbox cancel/reconcile and expectedThreadUpdatedAt on commitCommand so stale recovery writes no-op when the thread has already advanced.

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

Note

Add Orchestration V2 system with provider adapters, persistence, and V2 UI migration

  • Introduces the complete Orchestration V2 architecture: contracts, event store, projection store, command receipts, effect outbox/worker, runtime recovery, and database migrations 044–052
  • Adds V2 provider adapters for Claude, Codex, Cursor, Grok, OpenCode, and ACP Registry through the ProviderAdapterV2 service contract and driver registry, with replay testkits for deterministic testing
  • Implements thread lifecycle services for launch, fork, settlement, checkpoint capture/rollback, context handoff, continuation, run finalization, and provider-session management with idle timeout and shutdown
  • Migrates client-runtime, web, and mobile state from legacy orchestration types to V2 thread projections, bounded shell snapshots, runtime summaries, and progressive history paging
  • Adds MCP orchestrator and worktree toolkits, scheduled-task service with interval and fixed-time scheduling, T3 orchestration instructions, and a legacy V1 thread importer with idempotent shell and transcript hydration
  • Risk: migrations 044–052 create new schema tables and copy existing V2/project data into a shared application event log; EnvironmentThreadState in threadState.ts replaces legacy thread/page fields with OrchestrationV2ThreadProjection and ThreadHistoryMeta, removing threadHasOlderTurns; RPC authorization scopes in RpcAuthorization.ts are remapped to V2 method constants

Macroscope summarized 674b0e4.

@coderabbitai

coderabbitaiBot commented Aug 5, 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: 7726a859-eb6b-4efc-9e83-8c6c368e6e50

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 5, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One convention issue found in the new provider-ingest settlement code: a statically known tagged failure is recovered with Effect.catchTag instead of Effect.catchTags.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts
Comment threadpackages/client-runtime/src/state/shellReducer.ts Outdated
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts
@macroscopeapp

macroscopeappBot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This is a 964-file Orchestration V2 architecture and persistence migration that adds provider integrations, new workflows, protocol/schema changes, and product-default changes across server, web, and mobile. Unresolved projection and provider-lifecycle race findings add material consistency risk beyond the scope of an auto-approvable fix.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts Outdated
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from 5229063 to 6c322c7CompareAugust 6, 2026 00:13
@mwolsonmwolson changed the title fix(orchestrator): Stabilize recovery and settled-thread lifecyclefix(orchestrator): Stabilize settled-thread lifecycleAug 6, 2026
Comment threadpackages/shared/src/orchestrationV2Settled.ts
Comment threadpackages/shared/src/orchestrationV2Settled.ts
@mwolsonmwolson changed the title fix(orchestrator): Stabilize settled-thread lifecyclefix(orchestrator): Keep Home and settle honest when work endsAug 6, 2026
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 6, 2026
Port pingdotgg#5459 settled-thread lifecycle follow-ups: pin clear on settle, preserve
newer updatedAt on activity unsettle, fresher archive membership, settle runs
filter, and typed activity-unsettle prep errors.
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 25de21d to 0af2a6eCompareAugust 7, 2026 12:10
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 8, 2026
pingdotgg#5459's ProjectionSnapshotQuery.settled test must provide the same
ThreadBackgroundLiveness and ThreadPlanProgress layers the live snapshot
query now requires on CTM.
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from 0a6ccc1 to 5908b04CompareAugust 8, 2026 18:43
Comment threadpackages/shared/src/orchestrationV2Settled.ts
Comment threadapps/server/src/orchestration-v2/ProjectionStore.ts
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts Outdated
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 22bd872 to a27c1ccCompareAugust 10, 2026 17:05
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 11, 2026
Comment threadapps/server/src/orchestration-v2/ProjectionStore.ts
Comment threadapps/mobile/src/state/use-thread-selection.ts
Comment threadpackages/client-runtime/src/state/shell.ts
Comment threadpackages/contracts/src/orchestrationV2.ts
Comment threadapps/server/src/orchestration-v2/EffectOutbox.ts
Comment threadpackages/client-runtime/src/state/threads.ts

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

Effect service conventions review of the settle-lifecycle changes. The previously flagged Effect.catchTag usage in ProviderEventIngestor.ts is now Effect.catchTags({ ... }), and the new/changed service code (ProjectionStore, EventSink, EffectOutbox, ProviderRuntimeRecoveryService, ProviderEventIngestor, shared settlement helpers) follows the import, layer, and dependency-acquisition rules. Two small change-discipline findings in packages/client-runtime/src/state/threadSettled.ts.

Posted via Macroscope — Effect Service Conventions

Comment threadpackages/client-runtime/src/state/threadSettled.ts Outdated
Comment threadpackages/client-runtime/src/state/threadSettled.ts
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from dae270f to b27bee8CompareAugust 11, 2026 13:41
Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts Outdated
Comment threadpackages/client-runtime/src/state/threadSettled.ts Outdated
Comment threadpackages/client-runtime/src/state/shell.ts
Comment threadpackages/client-runtime/src/state/threadSettled.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from a186d64 to 5b1a115CompareAugust 12, 2026 23:19
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from a6e0049 to 50a57b6CompareSeptember 1, 2026 05:36
The v2 equivalents of main's pingdotgg#8984 and pingdotgg#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>
mwolson added a commit to mwolson/t3code that referenced this pull request Sep 1, 2026
OpenCode 2 now writes an occurrence timestamp when lastError is set or
cleared. The same message after a clear is a new occurrence; unchanged
text on an uncleared error keeps the original timestamp.
The optional lastErrorAt field is added to the provider-session contract
so the adapter can persist it without depending on pingdotgg#5459 settled-thread
lifecycle.
Stop retrying definitive missing thread snapshots, normalize V2 shell archive
membership, preserve provider retry metadata, and harden settled projection
ordering, activity unsettle, ingest timestamps, snapshot reads, and Waiting
lifecycle invariants.
Settlement-only merge now applies pinnedAt on explicit settle so pins still
clear, and activity unsettles no longer rewind updatedAt behind a newer
metadata bump.
Add settledOverrideAt on V2 app threads so keep-active and settled pins keep a
stable establishment clock across metadata renames. Prefer that field in the
activity-unsettle ordering guard, with settledAt/updatedAt fallbacks for
pre-field rows, and stamp it on settle, keep-active, and activity clear paths.
Pass settledOverrideAt through the client reduceThreadSettlementEvent path so
live sync matches the server pin floor, stamp a new keep-active floor when pin
promotes a settled thread, and keep existing active floors when re-pinning.
Re-apply the stranded ChatView and ChatComposer half of pingdotgg#5551 on CTM. Main
already gates the Build/Plan toggle behind planModeEnabled, but the v2 ChatView
rewrite left the beta setting and Beta panel in place while re-exposing the
toggle, /plan and /default, and Shift+Tab.
When the flag is off, force the effective interaction mode to default so a
thread with a stored plan mode cannot stay trapped after the control is hidden.
The next send persists default. Slash commands send as plain text, and the
keyboard shortcut is a no-op.
pingdotgg#6123 thread-error-banner-dismiss remembers the remount-safe banner key as
thread plus message. After that landed on CTM, dismissing one durable
failure hid later identical text for the rest of the app session.
Key the session mask with the occurrence timestamp when present so a later
run with the same lastError still shows.
The 30-minute idle reaper still tears down the live provider process. It
no longer writes lastError null on idle, manual, or server stop. Release
reads the latest projected session so an adapter-stamped error survives
process teardown.
A repeated process failure must get a new occurrence timestamp so a
dismissed banner can reappear. Idle release still preserves the prior
timestamp. The web banner key uses lastErrorAt only and no longer
falls back to updatedAt.
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from ae21654 to 57fca87CompareSeptember 1, 2026 23:52
const nextThread = reduceThreadSettlementEvent({
current: currentThread,
eventType: event.type,
settlement: {

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/ProjectionStore.ts:1261

thread.unsettled events lose event.payload.unsettledAt in the durable projection, so shell snapshots report unsettledAt as the previous value (often null) instead of when the thread was explicitly kept active. Include unsettledAt in the settlement merge.

 settlement: {
+ unsettledAt: event.payload.unsettledAt ?? null,
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/ProjectionStore.ts around line 1261:
`thread.unsettled` events lose `event.payload.unsettledAt` in the durable projection, so shell snapshots report `unsettledAt` as the previous value (often `null`) instead of when the thread was explicitly kept active. Include `unsettledAt` in the settlement merge.

// Only a definitive missing thread skips the candidate. Other read
// failures must surface so callers can retry rather than silently
// store activity without an unsettle attempt.
const projectionOption = yield* projectionStore.getThreadProjection(threadId).pipe(

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:356

Archived threads receive a thread.unsettled event for pending non-auth runtime-request.updated activity, clearing their settledOverride instead of leaving archived settlement state unchanged. getThreadProjection returns the still-present archived projection, and this path does not reject it before appending the synthetic event; restrict candidate generation to active threads.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/ProviderEventIngestor.ts around line 356:
Archived threads receive a `thread.unsettled` event for pending non-auth `runtime-request.updated` activity, clearing their `settledOverride` instead of leaving archived settlement state unchanged. `getThreadProjection` returns the still-present archived projection, and this path does not reject it before appending the synthetic event; restrict candidate generation to active threads.

Comment threadapps/server/src/orchestration-v2/ProviderSessionManager.ts
if (nextThread === projection.thread) {
return projection;
}
return { ...base, thread: nextThread };

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.

🟡 Mediumstate/orchestrationV2Projection.ts:207

A delayed thread.unsettled rewinds projection.updatedAt to event.occurredAt, even when the projection already has a newer timestamp from a concurrent metadata update. Although reduceThreadSettlementEvent preserves the newer thread.updatedAt, spreading base here still makes client ordering/activity views treat the thread as stale; preserve the later of the existing projection timestamp and event.occurredAt, as the server reducer does.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/orchestrationV2Projection.ts around line 207:
A delayed `thread.unsettled` rewinds `projection.updatedAt` to `event.occurredAt`, even when the projection already has a newer timestamp from a concurrent metadata update. Although `reduceThreadSettlementEvent` preserves the newer `thread.updatedAt`, spreading `base` here still makes client ordering/activity views treat the thread as stale; preserve the later of the existing projection timestamp and `event.occurredAt`, as the server reducer does.

@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 OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 57fca87. Configure here.

Comment threadapps/server/src/orchestration-v2/ProviderTurnStartService.ts
const next = new Set(current);
next.add(input.context.providerTurnId);
return next;
});

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.

Finalize claim races new turns

Medium Severity

finalizeActiveTurn nulls activeTurn and only then adds the id to finalizingTurnIds. startTurn checks those refs separately, so a concurrent start (the handoff timeout is forked) can observe both empty and open a second provider turn while the first is still emitting terminals.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 57fca87. Configure here.

@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 6081201 to 95bacddCompareSeptember 2, 2026 07:19
…d-letters
Wire failFromDeadLetter onto the turn-start service and compensate before
the outbox fails a start or restart that exhausted retries, matching the
v2.1 port.

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

Scope note: only apps/web/src/** TS/TSX/CSS changes were reviewed. Findings are the newly added global CSS in index.css (dead/duplicated rules) and the composer context strip's motion contract, which changed lines break while the code that depends on it is retained.

Posted via Macroscope — UI Consistency

aria-label="Run on"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The strip's compact/expand FLIP animation still keys on [data-composer-context-control] (BranchToolbar.tsx:232, consumed by useLabelsOverflow), but this PR removes that attribute from the environment selector, the env-mode selector (locked and select paths) and the branch selector. The only remaining carrier is the vertical Separator at BranchToolbar.tsx:545, so collapsing now animates a 1px separator while the actual controls jump to their new positions. Consider keeping the attribute on the toolbar-mode triggers (it is inert in panel mode), or removing the FLIP pass as well if the motion is intentionally gone.

Suggested change
aria-label="Run on"
aria-label="Run on"
data-composer-context-control

Posted via Macroscope — UI Consistency

Comment on lines +826 to +836
.glass-opacity-slider {
--glass-slider-progress: 0%;
--glass-slider-fill-offset: 0.5rem;
--glass-slider-fill-position: calc(
var(--glass-slider-progress) + var(--glass-slider-fill-offset)
);
appearance: none;
height: 1.5rem;
background: transparent;
cursor: pointer;
}

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.

This whole .glass-opacity-slider block (through the forced-colors rules) is unreferenced — the glass-opacity input in SettingsPanels.tsx:1111 uses settings-slider with --settings-slider-progress, and no literal or composed glass-opacity-slider class exists anywhere in the repo. It is also a verbatim copy of .settings-slider below with only the custom-property names changed, so it adds a second owner for the same slider treatment. Suggest dropping it (or, if a distinct slider is intended, wiring a call site and deriving it from .settings-slider rather than duplicating the vendor pseudo-element rules).

Posted via Macroscope — UI Consistency

Comment on lines +797 to +810
.alert-glass {
--alert-glass-tint: transparent;

background:
linear-gradient(
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent),
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent)
),
color-mix(in srgb, var(--background) var(--glass-opacity), transparent) !important;
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
}

.alert-glass[data-variant="error"] {

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.

alert-glass is already defined as @utility alert-glass (line 284) and is what ProviderStatusBanner consumes. This second definition in @layer components duplicates it and omits the utility's @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) opaque fallback; because !important declarations resolve in reverse layer order, this components-layer background: ... !important outranks the utility's important fallback, so browsers without backdrop-filter get a translucent, unblurred banner instead of the solid background. Suggest deleting this block and keeping the @utility as the single owner.

Posted via Macroscope — UI Consistency

Comment on lines +765 to +774
.surface-subheader {
@apply flex h-10 min-h-10 shrink-0 items-center border-b border-border/60 bg-background;
}

[data-preview-panel-mode="inline"] [data-right-panel-surface-content] [data-surface-subheader] {
height: calc(var(--spacing) * 7);
min-height: calc(var(--spacing) * 7);
margin-bottom: calc(var(--spacing) * 3);
border-bottom-color: transparent;
}

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.

.surface-subheader has no consumer: all four subheaders (DiffPanelShell, FileBrowserPanel, FilePreviewPanel, PreviewChromeRow) still carry the same geometry inline and only set data-surface-subheader. The inline-mode rule below is also redundant — every carrier already has in-data-[preview-panel-mode=inline]:h-7/min-h-7/mb-3/border-b-transparent, and those utilities sort after @layer components, so they win wherever this rule matches. Either apply surface-subheader at those call sites and drop the duplicated utility strings, or drop both rules so the shared geometry keeps one owner.

Posted via Macroscope — UI Consistency

className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The label previously animated transform/opacity on an inner data-composer-label-motion span with motion-reduce:transform-none motion-reduce:transition-opacity; this replaces it with a max-width transition and no reduced-motion guard, so reduced-motion users now get an animated layout change on every collapse. Same class appears at line 79 and in BranchToolbarEnvModeSelector/BranchToolbarBranchSelector.

Suggested change
className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0"
className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"

Posted via Macroscope — UI Consistency

"hover:!bg-black/[0.055] data-pressed:!bg-black/[0.055] dark:hover:!bg-white/[0.075] dark:data-pressed:!bg-white/[0.075]";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These row classes override Button's core contract at the call site — height (h-9 sm:h-9), radius, padding, and base/hover/pressed background, with ! needed to beat the ghost variant's own hover colors and raw black/white alpha instead of semantic tokens. Since this treatment is now shared by every panel row (select rows, link rows, split halves, icon actions), a named Button size/variant (e.g. size="panel-row" plus a muted-ghost variant) would express it in the primitive and drop the !important fights and the sm: re-statements; contextual width/color could stay here.

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $250.50, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

FileDiff SizeEstimate
apps/server/src/orchestration-v2/Orchestrator.ts280.33KB$14.02
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts272.17KB$13.61
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts238.01KB$11.90

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mwolson@t3dotgg@juliusmarminge@PixPMusic@nsxdavid@Yusuf007R@maria-rcks
, '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

fix(orchestrator): Keep Home, steers, and errors honest when work ends - #5459

Open
mwolson wants to merge 348 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orchestrator-settled-lifecycle
Open

fix(orchestrator): Keep Home, steers, and errors honest when work ends#5459
mwolson wants to merge 348 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orchestrator-settled-lifecycle

Conversation

@mwolson

@mwolsonmwolson commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What Changed

You can steer a turn while the agent is still working. The run could settle anyway, and the later answer vanished. That later text now stays on the thread.

A provider that never started could leave the thread looking alive forever. Exhausted retries now fail the run.

You settle a thread, and Home still treats it like live work. Missing snapshots keep retrying, archived threads drift back, and Waiting keeps refreshing stale rows. Settled now stays settled. A gone thread stops retrying.

You dismiss a thread error, and a later one never appears, or the old one never leaves. Dismissal lasts until a later error. Plan mode off still left the Build/Plan toggle in the composer after the v2 chat rewrite. The toggle is hidden in that case.

Why

Settle meant the useful work was over in the UI while the agent, the outbox, or a later error was still in play. Home, Waiting, and the composer should follow that same rule.

Julius's bounded thread history on current CTM stays as shipped.

UI Changes

Steered output stays in the thread. Failed starts no longer hang as if they were still starting. Home and Waiting settled state is quieter. Thread error banners dismiss until a later error. Build/Plan is hidden when plan mode is disabled.

No before/after screenshots.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Changes core orchestration paths—settlement merging, command commit guards, Claude turn lifecycle, and process-loss reconciliation—with race-sensitive behavior that can leave runs stuck or drop recovery if callers mishandle committed: false.

Overview
Threads gain settledOverrideAt so settle/keep-active pins have a stable establishment time that survives renames and is exposed on shells and mobile projections.

Settlement is tightened end-to-end: settle is blocked on queued runs, pending runtime requests, and provider background work; archived threads cannot settle/unsettle; pin/unsettle paths maintain the new timestamp; message dispatch clears overrides via a shared helper. Settled/unsettled events are merged through reduceThreadSettlementEvent in memory and in SQL so stale full-thread payloads cannot overwrite title or other metadata. Provider ingest tests document when pending thread-scoped requests should emit thread.unsettled without waking keep-active or non-activity updates.

Claude steering tracks per-turn steer counts and frame counts, detects steering handoff results before terminalizing, marks in-flight tools interrupted, and bounds silent handoffs with a timeout that fails the turn if no new frames arrive. Turn finalization is claimed up front to avoid duplicate terminals when the handoff fiber races the frame handler; new turns wait while a turn is finalizing.

Recovery adds optional updatedBefore filters on outbox cancel/reconcile and expectedThreadUpdatedAt on commitCommand so stale recovery writes no-op when the thread has already advanced.

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

Note

Add Orchestration V2 system with provider adapters, persistence, and V2 UI migration

  • Introduces the complete Orchestration V2 architecture: contracts, event store, projection store, command receipts, effect outbox/worker, runtime recovery, and database migrations 044–052
  • Adds V2 provider adapters for Claude, Codex, Cursor, Grok, OpenCode, and ACP Registry through the ProviderAdapterV2 service contract and driver registry, with replay testkits for deterministic testing
  • Implements thread lifecycle services for launch, fork, settlement, checkpoint capture/rollback, context handoff, continuation, run finalization, and provider-session management with idle timeout and shutdown
  • Migrates client-runtime, web, and mobile state from legacy orchestration types to V2 thread projections, bounded shell snapshots, runtime summaries, and progressive history paging
  • Adds MCP orchestrator and worktree toolkits, scheduled-task service with interval and fixed-time scheduling, T3 orchestration instructions, and a legacy V1 thread importer with idempotent shell and transcript hydration
  • Risk: migrations 044–052 create new schema tables and copy existing V2/project data into a shared application event log; EnvironmentThreadState in threadState.ts replaces legacy thread/page fields with OrchestrationV2ThreadProjection and ThreadHistoryMeta, removing threadHasOlderTurns; RPC authorization scopes in RpcAuthorization.ts are remapped to V2 method constants

Macroscope summarized 674b0e4.

@coderabbitai

coderabbitaiBot commented Aug 5, 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: 7726a859-eb6b-4efc-9e83-8c6c368e6e50

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 5, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One convention issue found in the new provider-ingest settlement code: a statically known tagged failure is recovered with Effect.catchTag instead of Effect.catchTags.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts
Comment threadpackages/client-runtime/src/state/shellReducer.ts Outdated
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts
@macroscopeapp

macroscopeappBot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This is a 964-file Orchestration V2 architecture and persistence migration that adds provider integrations, new workflows, protocol/schema changes, and product-default changes across server, web, and mobile. Unresolved projection and provider-lifecycle race findings add material consistency risk beyond the scope of an auto-approvable fix.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts Outdated
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from 5229063 to 6c322c7CompareAugust 6, 2026 00:13
@mwolsonmwolson changed the title fix(orchestrator): Stabilize recovery and settled-thread lifecyclefix(orchestrator): Stabilize settled-thread lifecycleAug 6, 2026
Comment threadpackages/shared/src/orchestrationV2Settled.ts
Comment threadpackages/shared/src/orchestrationV2Settled.ts
@mwolsonmwolson changed the title fix(orchestrator): Stabilize settled-thread lifecyclefix(orchestrator): Keep Home and settle honest when work endsAug 6, 2026
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 6, 2026
Port pingdotgg#5459 settled-thread lifecycle follow-ups: pin clear on settle, preserve
newer updatedAt on activity unsettle, fresher archive membership, settle runs
filter, and typed activity-unsettle prep errors.
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 25de21d to 0af2a6eCompareAugust 7, 2026 12:10
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 8, 2026
pingdotgg#5459's ProjectionSnapshotQuery.settled test must provide the same
ThreadBackgroundLiveness and ThreadPlanProgress layers the live snapshot
query now requires on CTM.
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from 0a6ccc1 to 5908b04CompareAugust 8, 2026 18:43
Comment threadpackages/shared/src/orchestrationV2Settled.ts
Comment threadapps/server/src/orchestration-v2/ProjectionStore.ts
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts Outdated
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 22bd872 to a27c1ccCompareAugust 10, 2026 17:05
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 11, 2026
Comment threadapps/server/src/orchestration-v2/ProjectionStore.ts
Comment threadapps/mobile/src/state/use-thread-selection.ts
Comment threadpackages/client-runtime/src/state/shell.ts
Comment threadpackages/contracts/src/orchestrationV2.ts
Comment threadapps/server/src/orchestration-v2/EffectOutbox.ts
Comment threadpackages/client-runtime/src/state/threads.ts

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

Effect service conventions review of the settle-lifecycle changes. The previously flagged Effect.catchTag usage in ProviderEventIngestor.ts is now Effect.catchTags({ ... }), and the new/changed service code (ProjectionStore, EventSink, EffectOutbox, ProviderRuntimeRecoveryService, ProviderEventIngestor, shared settlement helpers) follows the import, layer, and dependency-acquisition rules. Two small change-discipline findings in packages/client-runtime/src/state/threadSettled.ts.

Posted via Macroscope — Effect Service Conventions

Comment threadpackages/client-runtime/src/state/threadSettled.ts Outdated
Comment threadpackages/client-runtime/src/state/threadSettled.ts
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from dae270f to b27bee8CompareAugust 11, 2026 13:41
Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts Outdated
Comment threadpackages/client-runtime/src/state/threadSettled.ts Outdated
Comment threadpackages/client-runtime/src/state/shell.ts
Comment threadpackages/client-runtime/src/state/threadSettled.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from a186d64 to 5b1a115CompareAugust 12, 2026 23:19
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from a6e0049 to 50a57b6CompareSeptember 1, 2026 05:36
The v2 equivalents of main's pingdotgg#8984 and pingdotgg#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>
mwolson added a commit to mwolson/t3code that referenced this pull request Sep 1, 2026
OpenCode 2 now writes an occurrence timestamp when lastError is set or
cleared. The same message after a clear is a new occurrence; unchanged
text on an uncleared error keeps the original timestamp.
The optional lastErrorAt field is added to the provider-session contract
so the adapter can persist it without depending on pingdotgg#5459 settled-thread
lifecycle.
Stop retrying definitive missing thread snapshots, normalize V2 shell archive
membership, preserve provider retry metadata, and harden settled projection
ordering, activity unsettle, ingest timestamps, snapshot reads, and Waiting
lifecycle invariants.
Settlement-only merge now applies pinnedAt on explicit settle so pins still
clear, and activity unsettles no longer rewind updatedAt behind a newer
metadata bump.
Add settledOverrideAt on V2 app threads so keep-active and settled pins keep a
stable establishment clock across metadata renames. Prefer that field in the
activity-unsettle ordering guard, with settledAt/updatedAt fallbacks for
pre-field rows, and stamp it on settle, keep-active, and activity clear paths.
Pass settledOverrideAt through the client reduceThreadSettlementEvent path so
live sync matches the server pin floor, stamp a new keep-active floor when pin
promotes a settled thread, and keep existing active floors when re-pinning.
Re-apply the stranded ChatView and ChatComposer half of pingdotgg#5551 on CTM. Main
already gates the Build/Plan toggle behind planModeEnabled, but the v2 ChatView
rewrite left the beta setting and Beta panel in place while re-exposing the
toggle, /plan and /default, and Shift+Tab.
When the flag is off, force the effective interaction mode to default so a
thread with a stored plan mode cannot stay trapped after the control is hidden.
The next send persists default. Slash commands send as plain text, and the
keyboard shortcut is a no-op.
pingdotgg#6123 thread-error-banner-dismiss remembers the remount-safe banner key as
thread plus message. After that landed on CTM, dismissing one durable
failure hid later identical text for the rest of the app session.
Key the session mask with the occurrence timestamp when present so a later
run with the same lastError still shows.
The 30-minute idle reaper still tears down the live provider process. It
no longer writes lastError null on idle, manual, or server stop. Release
reads the latest projected session so an adapter-stamped error survives
process teardown.
A repeated process failure must get a new occurrence timestamp so a
dismissed banner can reappear. Idle release still preserves the prior
timestamp. The web banner key uses lastErrorAt only and no longer
falls back to updatedAt.
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from ae21654 to 57fca87CompareSeptember 1, 2026 23:52
const nextThread = reduceThreadSettlementEvent({
current: currentThread,
eventType: event.type,
settlement: {

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/ProjectionStore.ts:1261

thread.unsettled events lose event.payload.unsettledAt in the durable projection, so shell snapshots report unsettledAt as the previous value (often null) instead of when the thread was explicitly kept active. Include unsettledAt in the settlement merge.

 settlement: {
+ unsettledAt: event.payload.unsettledAt ?? null,
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/ProjectionStore.ts around line 1261:
`thread.unsettled` events lose `event.payload.unsettledAt` in the durable projection, so shell snapshots report `unsettledAt` as the previous value (often `null`) instead of when the thread was explicitly kept active. Include `unsettledAt` in the settlement merge.

// Only a definitive missing thread skips the candidate. Other read
// failures must surface so callers can retry rather than silently
// store activity without an unsettle attempt.
const projectionOption = yield* projectionStore.getThreadProjection(threadId).pipe(

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:356

Archived threads receive a thread.unsettled event for pending non-auth runtime-request.updated activity, clearing their settledOverride instead of leaving archived settlement state unchanged. getThreadProjection returns the still-present archived projection, and this path does not reject it before appending the synthetic event; restrict candidate generation to active threads.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/ProviderEventIngestor.ts around line 356:
Archived threads receive a `thread.unsettled` event for pending non-auth `runtime-request.updated` activity, clearing their `settledOverride` instead of leaving archived settlement state unchanged. `getThreadProjection` returns the still-present archived projection, and this path does not reject it before appending the synthetic event; restrict candidate generation to active threads.

Comment threadapps/server/src/orchestration-v2/ProviderSessionManager.ts
if (nextThread === projection.thread) {
return projection;
}
return { ...base, thread: nextThread };

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.

🟡 Mediumstate/orchestrationV2Projection.ts:207

A delayed thread.unsettled rewinds projection.updatedAt to event.occurredAt, even when the projection already has a newer timestamp from a concurrent metadata update. Although reduceThreadSettlementEvent preserves the newer thread.updatedAt, spreading base here still makes client ordering/activity views treat the thread as stale; preserve the later of the existing projection timestamp and event.occurredAt, as the server reducer does.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/orchestrationV2Projection.ts around line 207:
A delayed `thread.unsettled` rewinds `projection.updatedAt` to `event.occurredAt`, even when the projection already has a newer timestamp from a concurrent metadata update. Although `reduceThreadSettlementEvent` preserves the newer `thread.updatedAt`, spreading `base` here still makes client ordering/activity views treat the thread as stale; preserve the later of the existing projection timestamp and `event.occurredAt`, as the server reducer does.

@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 OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 57fca87. Configure here.

Comment threadapps/server/src/orchestration-v2/ProviderTurnStartService.ts
const next = new Set(current);
next.add(input.context.providerTurnId);
return next;
});

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.

Finalize claim races new turns

Medium Severity

finalizeActiveTurn nulls activeTurn and only then adds the id to finalizingTurnIds. startTurn checks those refs separately, so a concurrent start (the handoff timeout is forked) can observe both empty and open a second provider turn while the first is still emitting terminals.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 57fca87. Configure here.

@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 6081201 to 95bacddCompareSeptember 2, 2026 07:19
…d-letters
Wire failFromDeadLetter onto the turn-start service and compensate before
the outbox fails a start or restart that exhausted retries, matching the
v2.1 port.

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

Scope note: only apps/web/src/** TS/TSX/CSS changes were reviewed. Findings are the newly added global CSS in index.css (dead/duplicated rules) and the composer context strip's motion contract, which changed lines break while the code that depends on it is retained.

Posted via Macroscope — UI Consistency

aria-label="Run on"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The strip's compact/expand FLIP animation still keys on [data-composer-context-control] (BranchToolbar.tsx:232, consumed by useLabelsOverflow), but this PR removes that attribute from the environment selector, the env-mode selector (locked and select paths) and the branch selector. The only remaining carrier is the vertical Separator at BranchToolbar.tsx:545, so collapsing now animates a 1px separator while the actual controls jump to their new positions. Consider keeping the attribute on the toolbar-mode triggers (it is inert in panel mode), or removing the FLIP pass as well if the motion is intentionally gone.

Suggested change
aria-label="Run on"
aria-label="Run on"
data-composer-context-control

Posted via Macroscope — UI Consistency

Comment on lines +826 to +836
.glass-opacity-slider {
--glass-slider-progress: 0%;
--glass-slider-fill-offset: 0.5rem;
--glass-slider-fill-position: calc(
var(--glass-slider-progress) + var(--glass-slider-fill-offset)
);
appearance: none;
height: 1.5rem;
background: transparent;
cursor: pointer;
}

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.

This whole .glass-opacity-slider block (through the forced-colors rules) is unreferenced — the glass-opacity input in SettingsPanels.tsx:1111 uses settings-slider with --settings-slider-progress, and no literal or composed glass-opacity-slider class exists anywhere in the repo. It is also a verbatim copy of .settings-slider below with only the custom-property names changed, so it adds a second owner for the same slider treatment. Suggest dropping it (or, if a distinct slider is intended, wiring a call site and deriving it from .settings-slider rather than duplicating the vendor pseudo-element rules).

Posted via Macroscope — UI Consistency

Comment on lines +797 to +810
.alert-glass {
--alert-glass-tint: transparent;

background:
linear-gradient(
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent),
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent)
),
color-mix(in srgb, var(--background) var(--glass-opacity), transparent) !important;
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
}

.alert-glass[data-variant="error"] {

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.

alert-glass is already defined as @utility alert-glass (line 284) and is what ProviderStatusBanner consumes. This second definition in @layer components duplicates it and omits the utility's @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) opaque fallback; because !important declarations resolve in reverse layer order, this components-layer background: ... !important outranks the utility's important fallback, so browsers without backdrop-filter get a translucent, unblurred banner instead of the solid background. Suggest deleting this block and keeping the @utility as the single owner.

Posted via Macroscope — UI Consistency

Comment on lines +765 to +774
.surface-subheader {
@apply flex h-10 min-h-10 shrink-0 items-center border-b border-border/60 bg-background;
}

[data-preview-panel-mode="inline"] [data-right-panel-surface-content] [data-surface-subheader] {
height: calc(var(--spacing) * 7);
min-height: calc(var(--spacing) * 7);
margin-bottom: calc(var(--spacing) * 3);
border-bottom-color: transparent;
}

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.

.surface-subheader has no consumer: all four subheaders (DiffPanelShell, FileBrowserPanel, FilePreviewPanel, PreviewChromeRow) still carry the same geometry inline and only set data-surface-subheader. The inline-mode rule below is also redundant — every carrier already has in-data-[preview-panel-mode=inline]:h-7/min-h-7/mb-3/border-b-transparent, and those utilities sort after @layer components, so they win wherever this rule matches. Either apply surface-subheader at those call sites and drop the duplicated utility strings, or drop both rules so the shared geometry keeps one owner.

Posted via Macroscope — UI Consistency

className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The label previously animated transform/opacity on an inner data-composer-label-motion span with motion-reduce:transform-none motion-reduce:transition-opacity; this replaces it with a max-width transition and no reduced-motion guard, so reduced-motion users now get an animated layout change on every collapse. Same class appears at line 79 and in BranchToolbarEnvModeSelector/BranchToolbarBranchSelector.

Suggested change
className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0"
className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"

Posted via Macroscope — UI Consistency

"hover:!bg-black/[0.055] data-pressed:!bg-black/[0.055] dark:hover:!bg-white/[0.075] dark:data-pressed:!bg-white/[0.075]";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These row classes override Button's core contract at the call site — height (h-9 sm:h-9), radius, padding, and base/hover/pressed background, with ! needed to beat the ghost variant's own hover colors and raw black/white alpha instead of semantic tokens. Since this treatment is now shared by every panel row (select rows, link rows, split halves, icon actions), a named Button size/variant (e.g. size="panel-row" plus a muted-ghost variant) would express it in the primitive and drop the !important fights and the sm: re-statements; contextual width/color could stay here.

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $250.50, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

FileDiff SizeEstimate
apps/server/src/orchestration-v2/Orchestrator.ts280.33KB$14.02
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts272.17KB$13.61
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts238.01KB$11.90

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mwolson@t3dotgg@juliusmarminge@PixPMusic@nsxdavid@Yusuf007R@maria-rcks
, '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

fix(orchestrator): Keep Home, steers, and errors honest when work ends - #5459

Open
mwolson wants to merge 348 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orchestrator-settled-lifecycle
Open

fix(orchestrator): Keep Home, steers, and errors honest when work ends#5459
mwolson wants to merge 348 commits into
pingdotgg:t3code/codex-turn-mappingfrom
mwolson:fix/orchestrator-settled-lifecycle

Conversation

@mwolson

@mwolsonmwolson commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What Changed

You can steer a turn while the agent is still working. The run could settle anyway, and the later answer vanished. That later text now stays on the thread.

A provider that never started could leave the thread looking alive forever. Exhausted retries now fail the run.

You settle a thread, and Home still treats it like live work. Missing snapshots keep retrying, archived threads drift back, and Waiting keeps refreshing stale rows. Settled now stays settled. A gone thread stops retrying.

You dismiss a thread error, and a later one never appears, or the old one never leaves. Dismissal lasts until a later error. Plan mode off still left the Build/Plan toggle in the composer after the v2 chat rewrite. The toggle is hidden in that case.

Why

Settle meant the useful work was over in the UI while the agent, the outbox, or a later error was still in play. Home, Waiting, and the composer should follow that same rule.

Julius's bounded thread history on current CTM stays as shipped.

UI Changes

Steered output stays in the thread. Failed starts no longer hang as if they were still starting. Home and Waiting settled state is quieter. Thread error banners dismiss until a later error. Build/Plan is hidden when plan mode is disabled.

No before/after screenshots.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

High Risk
Changes core orchestration paths—settlement merging, command commit guards, Claude turn lifecycle, and process-loss reconciliation—with race-sensitive behavior that can leave runs stuck or drop recovery if callers mishandle committed: false.

Overview
Threads gain settledOverrideAt so settle/keep-active pins have a stable establishment time that survives renames and is exposed on shells and mobile projections.

Settlement is tightened end-to-end: settle is blocked on queued runs, pending runtime requests, and provider background work; archived threads cannot settle/unsettle; pin/unsettle paths maintain the new timestamp; message dispatch clears overrides via a shared helper. Settled/unsettled events are merged through reduceThreadSettlementEvent in memory and in SQL so stale full-thread payloads cannot overwrite title or other metadata. Provider ingest tests document when pending thread-scoped requests should emit thread.unsettled without waking keep-active or non-activity updates.

Claude steering tracks per-turn steer counts and frame counts, detects steering handoff results before terminalizing, marks in-flight tools interrupted, and bounds silent handoffs with a timeout that fails the turn if no new frames arrive. Turn finalization is claimed up front to avoid duplicate terminals when the handoff fiber races the frame handler; new turns wait while a turn is finalizing.

Recovery adds optional updatedBefore filters on outbox cancel/reconcile and expectedThreadUpdatedAt on commitCommand so stale recovery writes no-op when the thread has already advanced.

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

Note

Add Orchestration V2 system with provider adapters, persistence, and V2 UI migration

  • Introduces the complete Orchestration V2 architecture: contracts, event store, projection store, command receipts, effect outbox/worker, runtime recovery, and database migrations 044–052
  • Adds V2 provider adapters for Claude, Codex, Cursor, Grok, OpenCode, and ACP Registry through the ProviderAdapterV2 service contract and driver registry, with replay testkits for deterministic testing
  • Implements thread lifecycle services for launch, fork, settlement, checkpoint capture/rollback, context handoff, continuation, run finalization, and provider-session management with idle timeout and shutdown
  • Migrates client-runtime, web, and mobile state from legacy orchestration types to V2 thread projections, bounded shell snapshots, runtime summaries, and progressive history paging
  • Adds MCP orchestrator and worktree toolkits, scheduled-task service with interval and fixed-time scheduling, T3 orchestration instructions, and a legacy V1 thread importer with idempotent shell and transcript hydration
  • Risk: migrations 044–052 create new schema tables and copy existing V2/project data into a shared application event log; EnvironmentThreadState in threadState.ts replaces legacy thread/page fields with OrchestrationV2ThreadProjection and ThreadHistoryMeta, removing threadHasOlderTurns; RPC authorization scopes in RpcAuthorization.ts are remapped to V2 method constants

Macroscope summarized 674b0e4.

@coderabbitai

coderabbitaiBot commented Aug 5, 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: 7726a859-eb6b-4efc-9e83-8c6c368e6e50

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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 5, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One convention issue found in the new provider-ingest settlement code: a statically known tagged failure is recovered with Effect.catchTag instead of Effect.catchTags.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts
Comment threadpackages/client-runtime/src/state/shellReducer.ts Outdated
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts
@macroscopeapp

macroscopeappBot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This is a 964-file Orchestration V2 architecture and persistence migration that adds provider integrations, new workflows, protocol/schema changes, and product-default changes across server, web, and mobile. Unresolved projection and provider-lifecycle race findings add material consistency risk beyond the scope of an auto-approvable fix.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts Outdated
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from 5229063 to 6c322c7CompareAugust 6, 2026 00:13
@mwolsonmwolson changed the title fix(orchestrator): Stabilize recovery and settled-thread lifecyclefix(orchestrator): Stabilize settled-thread lifecycleAug 6, 2026
Comment threadpackages/shared/src/orchestrationV2Settled.ts
Comment threadpackages/shared/src/orchestrationV2Settled.ts
@mwolsonmwolson changed the title fix(orchestrator): Stabilize settled-thread lifecyclefix(orchestrator): Keep Home and settle honest when work endsAug 6, 2026
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 6, 2026
Port pingdotgg#5459 settled-thread lifecycle follow-ups: pin clear on settle, preserve
newer updatedAt on activity unsettle, fresher archive membership, settle runs
filter, and typed activity-unsettle prep errors.
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 25de21d to 0af2a6eCompareAugust 7, 2026 12:10
mwolson added a commit to mwolson/t3code that referenced this pull request Aug 8, 2026
pingdotgg#5459's ProjectionSnapshotQuery.settled test must provide the same
ThreadBackgroundLiveness and ThreadPlanProgress layers the live snapshot
query now requires on CTM.
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from 0a6ccc1 to 5908b04CompareAugust 8, 2026 18:43
Comment threadpackages/shared/src/orchestrationV2Settled.ts
Comment threadapps/server/src/orchestration-v2/ProjectionStore.ts
Comment threadapps/server/src/orchestration-v2/Orchestrator.ts Outdated
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from 22bd872 to a27c1ccCompareAugust 10, 2026 17:05
@github-actionsgithub-actionsBot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 11, 2026
Comment threadapps/server/src/orchestration-v2/ProjectionStore.ts
Comment threadapps/mobile/src/state/use-thread-selection.ts
Comment threadpackages/client-runtime/src/state/shell.ts
Comment threadpackages/contracts/src/orchestrationV2.ts
Comment threadapps/server/src/orchestration-v2/EffectOutbox.ts
Comment threadpackages/client-runtime/src/state/threads.ts

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

Effect service conventions review of the settle-lifecycle changes. The previously flagged Effect.catchTag usage in ProviderEventIngestor.ts is now Effect.catchTags({ ... }), and the new/changed service code (ProjectionStore, EventSink, EffectOutbox, ProviderRuntimeRecoveryService, ProviderEventIngestor, shared settlement helpers) follows the import, layer, and dependency-acquisition rules. Two small change-discipline findings in packages/client-runtime/src/state/threadSettled.ts.

Posted via Macroscope — Effect Service Conventions

Comment threadpackages/client-runtime/src/state/threadSettled.ts Outdated
Comment threadpackages/client-runtime/src/state/threadSettled.ts
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from dae270f to b27bee8CompareAugust 11, 2026 13:41
Comment threadapps/server/src/orchestration-v2/ProviderEventIngestor.ts Outdated
Comment threadpackages/client-runtime/src/state/threadSettled.ts Outdated
Comment threadpackages/client-runtime/src/state/shell.ts
Comment threadpackages/client-runtime/src/state/threadSettled.ts
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from a186d64 to 5b1a115CompareAugust 12, 2026 23:19
@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch from a6e0049 to 50a57b6CompareSeptember 1, 2026 05:36
The v2 equivalents of main's pingdotgg#8984 and pingdotgg#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>
mwolson added a commit to mwolson/t3code that referenced this pull request Sep 1, 2026
OpenCode 2 now writes an occurrence timestamp when lastError is set or
cleared. The same message after a clear is a new occurrence; unchanged
text on an uncleared error keeps the original timestamp.
The optional lastErrorAt field is added to the provider-session contract
so the adapter can persist it without depending on pingdotgg#5459 settled-thread
lifecycle.
Stop retrying definitive missing thread snapshots, normalize V2 shell archive
membership, preserve provider retry metadata, and harden settled projection
ordering, activity unsettle, ingest timestamps, snapshot reads, and Waiting
lifecycle invariants.
Settlement-only merge now applies pinnedAt on explicit settle so pins still
clear, and activity unsettles no longer rewind updatedAt behind a newer
metadata bump.
Add settledOverrideAt on V2 app threads so keep-active and settled pins keep a
stable establishment clock across metadata renames. Prefer that field in the
activity-unsettle ordering guard, with settledAt/updatedAt fallbacks for
pre-field rows, and stamp it on settle, keep-active, and activity clear paths.
Pass settledOverrideAt through the client reduceThreadSettlementEvent path so
live sync matches the server pin floor, stamp a new keep-active floor when pin
promotes a settled thread, and keep existing active floors when re-pinning.
Re-apply the stranded ChatView and ChatComposer half of pingdotgg#5551 on CTM. Main
already gates the Build/Plan toggle behind planModeEnabled, but the v2 ChatView
rewrite left the beta setting and Beta panel in place while re-exposing the
toggle, /plan and /default, and Shift+Tab.
When the flag is off, force the effective interaction mode to default so a
thread with a stored plan mode cannot stay trapped after the control is hidden.
The next send persists default. Slash commands send as plain text, and the
keyboard shortcut is a no-op.
pingdotgg#6123 thread-error-banner-dismiss remembers the remount-safe banner key as
thread plus message. After that landed on CTM, dismissing one durable
failure hid later identical text for the rest of the app session.
Key the session mask with the occurrence timestamp when present so a later
run with the same lastError still shows.
The 30-minute idle reaper still tears down the live provider process. It
no longer writes lastError null on idle, manual, or server stop. Release
reads the latest projected session so an adapter-stamped error survives
process teardown.
A repeated process failure must get a new occurrence timestamp so a
dismissed banner can reappear. Idle release still preserves the prior
timestamp. The web banner key uses lastErrorAt only and no longer
falls back to updatedAt.
@mwolson
mwolsonforce-pushed the fix/orchestrator-settled-lifecycle branch from ae21654 to 57fca87CompareSeptember 1, 2026 23:52
const nextThread = reduceThreadSettlementEvent({
current: currentThread,
eventType: event.type,
settlement: {

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/ProjectionStore.ts:1261

thread.unsettled events lose event.payload.unsettledAt in the durable projection, so shell snapshots report unsettledAt as the previous value (often null) instead of when the thread was explicitly kept active. Include unsettledAt in the settlement merge.

 settlement: {
+ unsettledAt: event.payload.unsettledAt ?? null,
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/ProjectionStore.ts around line 1261:
`thread.unsettled` events lose `event.payload.unsettledAt` in the durable projection, so shell snapshots report `unsettledAt` as the previous value (often `null`) instead of when the thread was explicitly kept active. Include `unsettledAt` in the settlement merge.

// Only a definitive missing thread skips the candidate. Other read
// failures must surface so callers can retry rather than silently
// store activity without an unsettle attempt.
const projectionOption = yield* projectionStore.getThreadProjection(threadId).pipe(

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:356

Archived threads receive a thread.unsettled event for pending non-auth runtime-request.updated activity, clearing their settledOverride instead of leaving archived settlement state unchanged. getThreadProjection returns the still-present archived projection, and this path does not reject it before appending the synthetic event; restrict candidate generation to active threads.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/ProviderEventIngestor.ts around line 356:
Archived threads receive a `thread.unsettled` event for pending non-auth `runtime-request.updated` activity, clearing their `settledOverride` instead of leaving archived settlement state unchanged. `getThreadProjection` returns the still-present archived projection, and this path does not reject it before appending the synthetic event; restrict candidate generation to active threads.

Comment threadapps/server/src/orchestration-v2/ProviderSessionManager.ts
if (nextThread === projection.thread) {
return projection;
}
return { ...base, thread: nextThread };

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.

🟡 Mediumstate/orchestrationV2Projection.ts:207

A delayed thread.unsettled rewinds projection.updatedAt to event.occurredAt, even when the projection already has a newer timestamp from a concurrent metadata update. Although reduceThreadSettlementEvent preserves the newer thread.updatedAt, spreading base here still makes client ordering/activity views treat the thread as stale; preserve the later of the existing projection timestamp and event.occurredAt, as the server reducer does.

🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/orchestrationV2Projection.ts around line 207:
A delayed `thread.unsettled` rewinds `projection.updatedAt` to `event.occurredAt`, even when the projection already has a newer timestamp from a concurrent metadata update. Although `reduceThreadSettlementEvent` preserves the newer `thread.updatedAt`, spreading `base` here still makes client ordering/activity views treat the thread as stale; preserve the later of the existing projection timestamp and `event.occurredAt`, as the server reducer does.

@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 OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 57fca87. Configure here.

Comment threadapps/server/src/orchestration-v2/ProviderTurnStartService.ts
const next = new Set(current);
next.add(input.context.providerTurnId);
return next;
});

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.

Finalize claim races new turns

Medium Severity

finalizeActiveTurn nulls activeTurn and only then adds the id to finalizingTurnIds. startTurn checks those refs separately, so a concurrent start (the handoff timeout is forked) can observe both empty and open a second provider turn while the first is still emitting terminals.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 57fca87. Configure here.

@juliusmarminge
juliusmarmingeforce-pushed the t3code/codex-turn-mapping branch 2 times, most recently from 6081201 to 95bacddCompareSeptember 2, 2026 07:19
…d-letters
Wire failFromDeadLetter onto the turn-start service and compensate before
the outbox fails a start or restart that exhausted retries, matching the
v2.1 port.

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

Scope note: only apps/web/src/** TS/TSX/CSS changes were reviewed. Findings are the newly added global CSS in index.css (dead/duplicated rules) and the composer context strip's motion contract, which changed lines break while the code that depends on it is retained.

Posted via Macroscope — UI Consistency

aria-label="Run on"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The strip's compact/expand FLIP animation still keys on [data-composer-context-control] (BranchToolbar.tsx:232, consumed by useLabelsOverflow), but this PR removes that attribute from the environment selector, the env-mode selector (locked and select paths) and the branch selector. The only remaining carrier is the vertical Separator at BranchToolbar.tsx:545, so collapsing now animates a 1px separator while the actual controls jump to their new positions. Consider keeping the attribute on the toolbar-mode triggers (it is inert in panel mode), or removing the FLIP pass as well if the motion is intentionally gone.

Suggested change
aria-label="Run on"
aria-label="Run on"
data-composer-context-control

Posted via Macroscope — UI Consistency

Comment on lines +826 to +836
.glass-opacity-slider {
--glass-slider-progress: 0%;
--glass-slider-fill-offset: 0.5rem;
--glass-slider-fill-position: calc(
var(--glass-slider-progress) + var(--glass-slider-fill-offset)
);
appearance: none;
height: 1.5rem;
background: transparent;
cursor: pointer;
}

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.

This whole .glass-opacity-slider block (through the forced-colors rules) is unreferenced — the glass-opacity input in SettingsPanels.tsx:1111 uses settings-slider with --settings-slider-progress, and no literal or composed glass-opacity-slider class exists anywhere in the repo. It is also a verbatim copy of .settings-slider below with only the custom-property names changed, so it adds a second owner for the same slider treatment. Suggest dropping it (or, if a distinct slider is intended, wiring a call site and deriving it from .settings-slider rather than duplicating the vendor pseudo-element rules).

Posted via Macroscope — UI Consistency

Comment on lines +797 to +810
.alert-glass {
--alert-glass-tint: transparent;

background:
linear-gradient(
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent),
color-mix(in srgb, var(--alert-glass-tint) 4%, transparent)
),
color-mix(in srgb, var(--background) var(--glass-opacity), transparent) !important;
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation));
}

.alert-glass[data-variant="error"] {

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.

alert-glass is already defined as @utility alert-glass (line 284) and is what ProviderStatusBanner consumes. This second definition in @layer components duplicates it and omits the utility's @supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) opaque fallback; because !important declarations resolve in reverse layer order, this components-layer background: ... !important outranks the utility's important fallback, so browsers without backdrop-filter get a translucent, unblurred banner instead of the solid background. Suggest deleting this block and keeping the @utility as the single owner.

Posted via Macroscope — UI Consistency

Comment on lines +765 to +774
.surface-subheader {
@apply flex h-10 min-h-10 shrink-0 items-center border-b border-border/60 bg-background;
}

[data-preview-panel-mode="inline"] [data-right-panel-surface-content] [data-surface-subheader] {
height: calc(var(--spacing) * 7);
min-height: calc(var(--spacing) * 7);
margin-bottom: calc(var(--spacing) * 3);
border-bottom-color: transparent;
}

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.

.surface-subheader has no consumer: all four subheaders (DiffPanelShell, FileBrowserPanel, FilePreviewPanel, PreviewChromeRow) still carry the same geometry inline and only set data-surface-subheader. The inline-mode rule below is also redundant — every carrier already has in-data-[preview-panel-mode=inline]:h-7/min-h-7/mb-3/border-b-transparent, and those utilities sort after @layer components, so they win wherever this rule matches. Either apply surface-subheader at those call sites and drop the duplicated utility strings, or drop both rules so the shared geometry keeps one owner.

Posted via Macroscope — UI Consistency

className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The label previously animated transform/opacity on an inner data-composer-label-motion span with motion-reduce:transform-none motion-reduce:transition-opacity; this replaces it with a max-width transition and no reduced-motion guard, so reduced-motion users now get an animated layout change on every collapse. Same class appears at line 79 and in BranchToolbarEnvModeSelector/BranchToolbarBranchSelector.

Suggested change
className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0"
className="min-w-0 max-w-[240px] truncate transition-[max-width,opacity] duration-300 ease-out group-data-[compact]/composer-context:max-w-0 group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"

Posted via Macroscope — UI Consistency

"hover:!bg-black/[0.055] data-pressed:!bg-black/[0.055] dark:hover:!bg-white/[0.075] dark:data-pressed:!bg-white/[0.075]";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These row classes override Button's core contract at the call site — height (h-9 sm:h-9), radius, padding, and base/hover/pressed background, with ! needed to beat the ghost variant's own hover colors and raw black/white alpha instead of semantic tokens. Since this treatment is now shared by every panel row (select rows, link rows, split halves, icon actions), a named Button size/variant (e.g. size="panel-row" plus a muted-ghost variant) would express it in the primitive and drop the !important fights and the sm: re-statements; contextual width/color could stay here.

Posted via Macroscope — UI Consistency

@macroscopeapp

Copy link
Copy Markdown
Contributor

Macroscope skipped reviewing this pull request. Per-review cost limit exceeded (workspace setting).

This review would cost an estimated $250.50, which exceeds your per-review limit of $10.00.

The top 3 files driving up this estimate:

FileDiff SizeEstimate
apps/server/src/orchestration-v2/Orchestrator.ts280.33KB$14.02
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.ts272.17KB$13.61
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts238.01KB$11.90

Tip

To get this pull request reviewed, you can:

  1. Comment @macroscope-app on this PR to request a manual review (monthly spend limits still apply).
  2. Exclude the file(s) above from review by adding a pattern to your .macroscope/ignore.md — note that creating this file replaces Macroscope's built-in default ignores rather than extending them.
  3. Raise your cost limit in your workspace billing settings.

Turn off this reminder going forward

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

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@mwolson@t3dotgg@juliusmarminge@PixPMusic@nsxdavid@Yusuf007R@maria-rcks