From 5f95653f25ae0f5655145fa83d94d618c2951432 Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 13 Nov 2026 14:34:42 -0800
Subject: [PATCH 001/690] fix(server): Claude context meter no longer shows an
auto-compact point when auto-compaction is off (#36417)
---
.../Adapters/ClaudeAdapterV2.test.ts | 12 ++++++++++
.../Adapters/ClaudeAdapterV2.ts | 24 +++++++++++++++++++
2 files changed, 36 insertions(+)
diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
index 2c69c18a1f12b..343675e2cfc40 100644
--- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
+++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
@@ -9109,6 +9109,18 @@ describe("ClaudeAdapterV2 background wake turns", () => {
environment: { CLAUDE_CODE_AUTO_COMPACT_WINDOW: "300000" },
threshold: 267_000,
},
+ {
+ autoCompactWindow: "",
+ model: "claude-opus-5-5",
+ userSettings: '{"autoCompactWindow":400000,"autoCompactEnabled":false}',
+ threshold: undefined,
+ },
+ {
+ autoCompactWindow: "400000",
+ model: "claude-opus-5-5",
+ environment: { DISABLE_AUTO_COMPACT: "1" },
+ threshold: undefined,
+ },
])(
"ignores stale snapshots after compaction and reports threshold $autoCompactWindow for $model",
({
diff --git a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
index 18bf66a1fdacc..f5d39bfd7d087 100644
--- a/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
+++ b/apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
@@ -270,6 +270,11 @@ function claudePromptCacheTtlMs(
// getContextUsage reports a 150,000 window as a 117,000 threshold, for every catalog model.
const CLAUDE_AUTO_COMPACT_RESERVE_TOKENS = 33_000;
+// Claude Code's isEnvTruthy.
+function isClaudeEnvTruthy(value: string | undefined): boolean {
+ return value !== undefined && ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
+}
+
function claudeAutoCompactThreshold(autoCompactWindow: string, maxTokens: number | null) {
const windowTokens = Number(autoCompactWindow);
return windowTokens > 0 && (maxTokens === null || windowTokens < maxTokens)
@@ -3260,6 +3265,7 @@ const decodeClaudeSubagentCacheSettings = Schema.decodeUnknownEffect(
const decodeClaudeCompactSettings = Schema.decodeUnknownEffect(
Schema.fromJsonString(
Schema.Struct({
+ autoCompactEnabled: Schema.optionalKey(Schema.Unknown),
autoCompactWindow: Schema.optionalKey(Schema.Unknown),
modelSettings: Schema.optionalKey(Schema.Unknown),
}),
@@ -5473,6 +5479,24 @@ export const makeClaudeAdapterV2 = Effect.fn("makeClaudeAdapterV2")(function* (
modelSelection: ModelSelection,
launchArgSettings: ClaudeSdkSettings | undefined,
) {
+ // With auto-compaction off Claude never compacts, so there is no threshold to report.
+ for (const name of ["DISABLE_COMPACT", "DISABLE_AUTO_COMPACT"]) {
+ const value = yield* resolveClaudeEnv(cwd, launchArgSettings, name);
+ if (isClaudeEnvTruthy(value)) return "";
+ }
+ const launchEnabled = launchArgSettings?.autoCompactEnabled;
+ let autoCompactEnabled = typeof launchEnabled === "boolean" ? launchEnabled : undefined;
+ for (const settingsFile of yield* settingsFilesByPrecedence(cwd)) {
+ if (autoCompactEnabled !== undefined) break;
+ const settings = yield* fileSystem.readFileString(settingsFile).pipe(
+ Effect.flatMap(decodeClaudeCompactSettings),
+ Effect.orElseSucceed(() => undefined),
+ );
+ if (typeof settings?.autoCompactEnabled === "boolean") {
+ autoCompactEnabled = settings.autoCompactEnabled;
+ }
+ }
+ if (autoCompactEnabled === false) return "";
if (adapterOptions.settings.autoCompactWindow !== "")
return adapterOptions.settings.autoCompactWindow;
const validWindow = (value: unknown) => {
From c8db8be305056bda6f82f96a43c65db178e62d36 Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 13 Nov 2026 14:45:20 -0800
Subject: [PATCH 002/690] fix(web): a run waiting on foreground subagents says
so instead of Thinking (#36334)
---
apps/mobile/src/lib/threadActivity.ts | 21 +++-
.../chat/MessagesTimeline.logic.test.ts | 107 ++++++++++++++++++
.../components/chat/MessagesTimeline.logic.ts | 32 +++++-
.../components/chat/TimelineWorkGroupRows.tsx | 2 +-
.../src/state/subagentRuntime.ts | 4 +
5 files changed, 161 insertions(+), 5 deletions(-)
diff --git a/apps/mobile/src/lib/threadActivity.ts b/apps/mobile/src/lib/threadActivity.ts
index cbc9e5e61a054..5b9f07ed77332 100644
--- a/apps/mobile/src/lib/threadActivity.ts
+++ b/apps/mobile/src/lib/threadActivity.ts
@@ -6,7 +6,10 @@ import { linkRequestTargetName } from "@t3tools/client-runtime/link-request";
import { turnItemIsWorkspacePreparation } from "@t3tools/client-runtime/state/turn-item-presentation";
import type { ThreadCheckpointSummary } from "@t3tools/client-runtime/state/thread-checkpoints";
import { formatSubagentDisplayTitle } from "@t3tools/client-runtime/state/subagent-display";
-import { isLiveSubagentTurnItem } from "@t3tools/client-runtime/state/subagentRuntime";
+import {
+ isLiveSubagentTurnItem,
+ waitingOnSubagentsLabel,
+} from "@t3tools/client-runtime/state/subagentRuntime";
import { extractToolActivityPresentation } from "@t3tools/client-runtime/work-log/tool-presentation";
import {
turnItemDetailRevision,
@@ -1817,11 +1820,12 @@ export function deriveThreadFeedPresentation(
row.activities[0]?.projectedItem.item.status === "running"),
)
) {
+ const waitingOn = activeRunId === null ? 0 : runningSubagentsAtRunTail(feed, activeRunId);
result.push(
thinkingRow(
activeWorkStartedAt,
activeRunId,
- zen ? liveAnswerPreview(feed) : null,
+ waitingOn > 0 ? waitingOnSubagentsLabel(waitingOn) : zen ? liveAnswerPreview(feed) : null,
latestRun?.providerStartingLabel,
),
);
@@ -1928,6 +1932,19 @@ function isWorkLogFeedRow(row: ThreadFeedEntry | undefined): boolean {
const LIVE_ACTIVITY_ROW_ID = "live-activity-row";
+/** Reads the feed before tool visibility filters it, so hidden newer parent work still counts. */
+function runningSubagentsAtRunTail(feed: ReadonlyArray, runId: RunId): number {
+ let running = 0;
+ for (let index = feed.length - 1; index >= 0; index -= 1) {
+ const entry = feed[index]!;
+ if (entry.type !== "activity-group" || entry.runId !== runId) break;
+ const items = entry.activities.map((activity) => activity.projectedItem.item);
+ if (items.some((item) => item.type !== "subagent")) break;
+ running += items.filter(isLiveSubagentTurnItem).length;
+ }
+ return running;
+}
+
function thinkingRow(
createdAt: string,
runId: RunId | null,
diff --git a/apps/web/src/components/chat/MessagesTimeline.logic.test.ts b/apps/web/src/components/chat/MessagesTimeline.logic.test.ts
index 1bdb26aecfd62..7bbdf9db1784b 100644
--- a/apps/web/src/components/chat/MessagesTimeline.logic.test.ts
+++ b/apps/web/src/components/chat/MessagesTimeline.logic.test.ts
@@ -4888,6 +4888,113 @@ describe("live subagents after their parent turn settles", () => {
});
});
+describe("parent run waiting on foreground subagents", () => {
+ const runId = RunId.make("parent-run");
+ const threadId = ThreadId.make("parent-thread");
+ const at = (second: number) =>
+ DateTime.makeUnsafe(`2026-10-06T10:00:${String(second).padStart(2, "0")}Z`);
+ const base = (id: string, second: number) => ({
+ id: TurnItemId.make(id),
+ threadId,
+ runId,
+ nodeId: null,
+ providerThreadId: null,
+ providerTurnId: ProviderTurnId.make("parent-provider-turn"),
+ nativeItemRef: null,
+ parentItemId: null,
+ ordinal: second,
+ title: null,
+ startedAt: at(second),
+ completedAt: at(second),
+ updatedAt: at(second),
+ });
+ const child = (id: string, second: number): OrchestrationV2TurnItem => ({
+ ...base(id, second),
+ status: "running",
+ completedAt: null,
+ type: "subagent",
+ subagentId: NodeId.make(id),
+ origin: "provider_native",
+ driver: ProviderDriverKind.make("opencode"),
+ providerInstanceId: ProviderInstanceId.make("opencode"),
+ childThreadId: null,
+ prompt: `Read ${id}`,
+ result: null,
+ });
+
+ const liveRow = (later: ReadonlyArray, toolCallVisibility?: "hideAll") =>
+ deriveMessagesTimelineRows({
+ timelineEntries: deriveTimelineEntriesFromVisibleTurnItems({
+ visibleTurnItems: (
+ [
+ {
+ ...base("user", 0),
+ status: "completed",
+ type: "user_message",
+ messageId: MessageId.make("user"),
+ createdBy: "user",
+ creationSource: "web",
+ inputIntent: "turn_start",
+ text: "Research these in parallel",
+ attachments: [],
+ },
+ {
+ ...base("assistant", 1),
+ status: "completed",
+ type: "assistant_message",
+ messageId: MessageId.make("assistant"),
+ text: "Dispatching three subagents.",
+ streaming: false,
+ },
+ child("a", 2),
+ { ...child("b", 3), status: "completed", completedAt: at(3) },
+ child("c", 4),
+ ...later,
+ ] satisfies OrchestrationV2TurnItem[]
+ ).map((item) => ({
+ position: item.ordinal,
+ visibility: "local" as const,
+ sourceThreadId: threadId,
+ sourceItemId: item.id,
+ item,
+ })),
+ optimisticMessages: [],
+ }),
+ latestRun: {
+ runId,
+ status: "running",
+ startedAt: DateTime.formatIso(at(0)),
+ completedAt: null,
+ },
+ isWorking: true,
+ activeTurnStartedAt: DateTime.formatIso(at(0)),
+ turnDiffSummaries: [],
+ supportsConversationRollback: false,
+ ...(toolCallVisibility ? { toolCallVisibility } : {}),
+ }).find((row) => row.id === "live-activity-row");
+ const thinking = {
+ kind: "thinking",
+ id: "live-activity-row",
+ createdAt: DateTime.formatIso(at(0)),
+ };
+
+ it("names the running children while the open run has done nothing since launching them", () => {
+ expect(liveRow([])).toEqual({ ...thinking, label: "Waiting on 2 subagents" });
+ expect(liveRow([], "hideAll")).toEqual({ ...thinking, label: "Waiting on 2 subagents" });
+ });
+
+ it("keeps Thinking when a hidden newer parent command follows the children", () => {
+ const command: OrchestrationV2TurnItem = {
+ ...base("command", 5),
+ status: "running",
+ completedAt: null,
+ type: "command_execution",
+ input: "sleep 5",
+ };
+ expect(liveRow([command], "hideAll")).toEqual(thinking);
+ });
+});
+
describe("subagent continued by a later turn", () => {
const threadId = ThreadId.make("parent-thread");
const run1 = RunId.make("run-1");
diff --git a/apps/web/src/components/chat/MessagesTimeline.logic.ts b/apps/web/src/components/chat/MessagesTimeline.logic.ts
index b2460d1fb43b2..9208e409ba205 100644
--- a/apps/web/src/components/chat/MessagesTimeline.logic.ts
+++ b/apps/web/src/components/chat/MessagesTimeline.logic.ts
@@ -1,7 +1,10 @@
import * as DateTime from "effect/DateTime";
import { worktreeSetupAgentStarted } from "@t3tools/client-runtime/worktree-setup";
import { resolveUserMessagePresentation } from "@t3tools/client-runtime/user-message";
-import { isLiveSubagentTurnItem } from "@t3tools/client-runtime/state/subagentRuntime";
+import {
+ isLiveSubagentTurnItem,
+ waitingOnSubagentsLabel,
+} from "@t3tools/client-runtime/state/subagentRuntime";
import * as Equal from "effect/Equal";
import { shallow } from "zustand/vanilla/shallow";
import { claudeResumeCompactionAge } from "@t3tools/shared/claudeCompaction";
@@ -532,6 +535,8 @@ type MessagesTimelineRowContent =
kind: "thinking";
id: string;
createdAt: string | null;
+ /** Replaces "Thinking" while the run has done nothing since launching children that still run. */
+ label?: string;
groupId?: string;
expanded?: boolean;
}
@@ -1451,6 +1456,19 @@ export function liveAnswerPreview(entries: ReadonlyArray): string
return null;
}
+/** Reads entries before tool visibility filters them, so hidden newer parent work still counts. */
+function runningSubagentsAtRunTail(entries: ReadonlyArray, runId: RunId): number {
+ let running = 0;
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
+ const entry = entries[index]!;
+ if (entry.kind !== "event") break;
+ const { item } = entry.projectedItem;
+ if (item.type !== "subagent" || item.runId !== runId) break;
+ if (isLiveSubagentTurnItem(item)) running += 1;
+ }
+ return running;
+}
+
function timelineRowEntries(
input: Pick<
MessagesTimelineRowsInput,
@@ -2316,10 +2334,15 @@ export function deriveMessagesTimelineRows(input: {
}
nextRows.push(...viewedImagesRows(groupId, failedGroupAnchor.createdAt, groupedEntries));
} else {
+ const waitingOn =
+ unsettledRunId === null
+ ? 0
+ : runningSubagentsAtRunTail(input.timelineEntries, unsettledRunId);
nextRows.push({
kind: "thinking",
id: LIVE_ACTIVITY_ROW_ID,
createdAt: input.activeTurnStartedAt ?? null,
+ ...(waitingOn > 0 ? { label: waitingOnSubagentsLabel(waitingOn) } : {}),
});
}
}
@@ -2679,7 +2702,12 @@ function isRowUnchanged(a: MessagesTimelineRow, b: MessagesTimelineRow): boolean
);
case "thinking": {
const bt = b as typeof a;
- return a.createdAt === bt.createdAt && a.groupId === bt.groupId && a.expanded === bt.expanded;
+ return (
+ a.createdAt === bt.createdAt &&
+ a.groupId === bt.groupId &&
+ a.expanded === bt.expanded &&
+ a.label === bt.label
+ );
}
case "worktree-setup":
return a.snapshot === (b as typeof a).snapshot;
diff --git a/apps/web/src/components/chat/TimelineWorkGroupRows.tsx b/apps/web/src/components/chat/TimelineWorkGroupRows.tsx
index 8fd7436d0e099..33608965ef313 100644
--- a/apps/web/src/components/chat/TimelineWorkGroupRows.tsx
+++ b/apps/web/src/components/chat/TimelineWorkGroupRows.tsx
@@ -435,7 +435,7 @@ export function ThinkingTimelineRow({ row }: { row: Extract
Date: Fri, 13 Nov 2026 14:50:16 -0800
Subject: [PATCH 003/690] fix(server): a follow-up to a delegated child waits
for its awaited background work (#36411)
---
apps/server/src/mcp/OrchestratorMcpService.ts | 4 +-
.../DelegatedCompletionDelivery.test.ts | 240 ++++++++++++++++++
.../src/orchestration-v2/Orchestrator.ts | 55 +++-
.../orchestration-v2/RestartBackgroundNote.ts | 27 +-
4 files changed, 312 insertions(+), 14 deletions(-)
diff --git a/apps/server/src/mcp/OrchestratorMcpService.ts b/apps/server/src/mcp/OrchestratorMcpService.ts
index e3cbb4aca9417..2e5877e9ab8ed 100644
--- a/apps/server/src/mcp/OrchestratorMcpService.ts
+++ b/apps/server/src/mcp/OrchestratorMcpService.ts
@@ -102,7 +102,7 @@ import type {
ProjectionRecordFilter,
} from "../orchestration-v2/ProjectionStore.ts";
import {
- delegatedTaskAwaitsSubagents,
+ delegatedTaskAwaitsWork,
delegatedTaskResultRun,
delegatedTaskRuns,
runOutcome,
@@ -1640,7 +1640,7 @@ const make = Effect.gen(function* () {
? progress.state
: !isTerminalTaskStatus(taskStatusForRun(taskResultRun))
? "working"
- : delegatedTaskAwaitsSubagents(followupRuns, childControls.subagents)
+ : delegatedTaskAwaitsWork(followupRuns, childControls)
? "waiting_for_children"
: "result_available";
const resultRunIds = [
diff --git a/apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts b/apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts
index b0886435d0b5d..22827388bffa0 100644
--- a/apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts
+++ b/apps/server/src/orchestration-v2/DelegatedCompletionDelivery.test.ts
@@ -3190,6 +3190,246 @@ it.layer(Layer.merge(layerTest, ProviderContinuationRequests.layer))(
}),
);
+ it.effect.each(["woken", "cancelled"] as const)(
+ "holds a follow-up open while the child's awaited background command runs (%s)",
+ (ending) =>
+ Effect.gen(function* () {
+ const orchestrator = yield* Orchestrator.OrchestratorV2;
+ const eventSink = yield* EventSink.EventSinkV2;
+ const now = yield* DateTime.now;
+ const parentThreadId = ThreadId.make(`thread:followup-command-parent-${ending}`);
+ const projectId = ProjectId.make(`project:followup-command-${ending}`);
+ const parentRunId = RunId.make(`run:followup-command-parent-${ending}`);
+ const rootNodeId = NodeId.make(`node:followup-command-parent-${ending}`);
+ yield* seedParentWithTerminalTask({
+ threadId: parentThreadId,
+ projectId,
+ runId: parentRunId,
+ rootNodeId,
+ taskId: NodeId.make(`node:followup-command-seed-${ending}`),
+ deliveryState: "acknowledged",
+ now,
+ });
+ const child = yield* seedRestartCancelledChild({
+ parentThreadId,
+ projectId,
+ parentRunId,
+ rootNodeId,
+ name: `followup-command-child-${ending}`,
+ completionWake: "always",
+ continuationPending: false,
+ runStatus: "completed",
+ now,
+ });
+ yield* orchestrator.recoverDelegatedTasks;
+
+ const followupMessageId = MessageId.make(`message:followup-command-send-${ending}`);
+ yield* orchestrator.dispatch({
+ type: "message.dispatch",
+ createdBy: "agent",
+ creationSource: "mcp",
+ commandId: CommandId.make(`command:followup-command-send-${ending}`),
+ threadId: child.childThreadId,
+ senderThreadId: parentThreadId,
+ messageId: followupMessageId,
+ text: "Start the check in the background, wait on it, and reply when it ends.",
+ attachments: [],
+ dispatchMode: { type: "queue_after_active" },
+ });
+ const followupRun = (yield* orchestrator.getThreadProjection(
+ child.childThreadId,
+ )).runs.find((run) => run.userMessageId === followupMessageId)!;
+ const followupTaskId = followupRun.delegatedTaskId!;
+ const parent = yield* orchestrator.getThreadProjection(parentThreadId);
+
+ // The child awaits a background command, replies with an interim note and ends its turn.
+ yield* eventSink.write({
+ commandId: reconcileCommandId(`followup-command-turn-ended-${ending}`),
+ events: [
+ {
+ id: EventId.make(`event:followup-command-awaited-${ending}`),
+ type: "provider-thread.updated",
+ threadId: child.childThreadId,
+ driver,
+ providerInstanceId: modelSelection.instanceId,
+ occurredAt: now,
+ payload: {
+ ...parent.providerThreads[0]!,
+ id: ProviderThreadId.make(`provider-thread:followup-command-child-${ending}`),
+ appThreadId: child.childThreadId,
+ pendingBackgroundTasks: [
+ { taskId: "sleep-90", kind: "command" as const, awaited: true as const },
+ ],
+ },
+ },
+ {
+ id: EventId.make(`event:followup-command-interim-${ending}`),
+ type: "message.updated",
+ threadId: child.childThreadId,
+ runId: followupRun.id,
+ occurredAt: now,
+ payload: {
+ id: MessageId.make(`message:followup-command-interim-${ending}`),
+ threadId: child.childThreadId,
+ runId: followupRun.id,
+ nodeId: null,
+ role: "assistant",
+ text: "INTERIM",
+ attachments: [],
+ streaming: false,
+ createdBy: "agent",
+ creationSource: "server",
+ createdAt: now,
+ updatedAt: now,
+ },
+ },
+ {
+ id: EventId.make(`event:followup-command-run-completed-${ending}`),
+ type: "run.updated",
+ threadId: child.childThreadId,
+ runId: followupRun.id,
+ occurredAt: now,
+ payload: { ...followupRun, status: "completed", startedAt: now, completedAt: now },
+ },
+ ],
+ });
+ yield* orchestrator.recoverDelegatedTasks;
+
+ const held = (yield* orchestrator.getThreadProjection(parentThreadId)).subagents.find(
+ (task) => task.id === followupTaskId,
+ );
+ assert.isNull(
+ held?.result ?? null,
+ "the follow-up closed with its interim reply while the awaited command still ran",
+ );
+ assert.equal(held?.status, "running");
+
+ if (ending === "cancelled") {
+ yield* orchestrator.cancelDelegatedTask({
+ parentThreadId,
+ taskId: followupTaskId,
+ command: {
+ type: "thread.stop",
+ commandId: CommandId.make(`command:followup-command-cancel-${ending}`),
+ threadId: child.childThreadId,
+ stopDelegatedTasks: false,
+ },
+ stopDescendants: () => Effect.void,
+ });
+ yield* eventSink.write({
+ commandId: reconcileCommandId(`followup-command-stopped-${ending}`),
+ events: [
+ {
+ id: EventId.make(`event:followup-command-stopped-${ending}`),
+ type: "provider-thread.updated",
+ threadId: child.childThreadId,
+ driver,
+ providerInstanceId: modelSelection.instanceId,
+ occurredAt: now,
+ payload: {
+ ...parent.providerThreads[0]!,
+ id: ProviderThreadId.make(`provider-thread:followup-command-child-${ending}`),
+ appThreadId: child.childThreadId,
+ pendingBackgroundTasks: [],
+ },
+ },
+ ],
+ });
+ yield* orchestrator.recoverDelegatedTasks;
+ const stopped = (yield* orchestrator.getThreadProjection(
+ parentThreadId,
+ )).subagents.find((task) => task.id === followupTaskId);
+ assert.equal(stopped?.cancelledRunId, followupRun.id);
+ assert.equal(stopped?.status, "interrupted");
+ return;
+ }
+
+ // The finished command wakes the child; that turn continues the same follow-up.
+ const wakeMessageId = MessageId.make(`message:followup-command-wake-${ending}`);
+ yield* orchestrator.dispatch({
+ type: "message.dispatch",
+ createdBy: "agent",
+ creationSource: "server",
+ commandId: CommandId.make(`command:followup-command-wake-${ending}`),
+ threadId: child.childThreadId,
+ messageId: wakeMessageId,
+ text: "Background activity updated",
+ notification: {
+ source: { kind: "command" },
+ outcome: "completed",
+ summary: 'Command "sleep 90" finished',
+ },
+ attachments: [],
+ dispatchMode: { type: "queue_after_active" },
+ });
+ const wakeRun = (yield* orchestrator.getThreadProjection(child.childThreadId)).runs.find(
+ (run) => run.userMessageId === wakeMessageId,
+ )!;
+ assert.equal(wakeRun.delegatedTaskId, followupTaskId);
+ yield* eventSink.write({
+ commandId: reconcileCommandId(`followup-command-wake-ended-${ending}`),
+ events: [
+ {
+ id: EventId.make(`event:followup-command-settled-${ending}`),
+ type: "provider-thread.updated",
+ threadId: child.childThreadId,
+ driver,
+ providerInstanceId: modelSelection.instanceId,
+ occurredAt: now,
+ payload: {
+ ...parent.providerThreads[0]!,
+ id: ProviderThreadId.make(`provider-thread:followup-command-child-${ending}`),
+ appThreadId: child.childThreadId,
+ pendingBackgroundTasks: [],
+ },
+ },
+ {
+ id: EventId.make(`event:followup-command-final-${ending}`),
+ type: "message.updated",
+ threadId: child.childThreadId,
+ runId: wakeRun.id,
+ occurredAt: now,
+ payload: {
+ id: MessageId.make(`message:followup-command-final-${ending}`),
+ threadId: child.childThreadId,
+ runId: wakeRun.id,
+ nodeId: null,
+ role: "assistant",
+ text: "FINAL CHECK-OK",
+ attachments: [],
+ streaming: false,
+ createdBy: "agent",
+ creationSource: "server",
+ createdAt: now,
+ updatedAt: now,
+ },
+ },
+ {
+ id: EventId.make(`event:followup-command-wake-completed-${ending}`),
+ type: "run.updated",
+ threadId: child.childThreadId,
+ runId: wakeRun.id,
+ occurredAt: now,
+ payload: { ...wakeRun, status: "completed", startedAt: now, completedAt: now },
+ },
+ ],
+ });
+ yield* orchestrator.recoverDelegatedTasks;
+
+ const finished = yield* orchestrator.getThreadProjection(parentThreadId);
+ const childTasks = finished.subagents.filter(
+ (task) => task.childThreadId === child.childThreadId,
+ );
+ assert.deepEqual(
+ childTasks.map((task) => task.id).toSorted(),
+ [child.taskId, followupTaskId].toSorted(),
+ );
+ const followup = childTasks.find((task) => task.id === followupTaskId);
+ assert.equal(followup?.status, "completed");
+ assert.equal(followup?.result, "FINAL CHECK-OK");
+ }),
+ );
+
it.effect("waits for a run the follow-up's child queued on its own thread", () =>
Effect.gen(function* () {
const requests = yield* ProviderContinuationRequests.ProviderContinuationRequests;
diff --git a/apps/server/src/orchestration-v2/Orchestrator.ts b/apps/server/src/orchestration-v2/Orchestrator.ts
index dfcf279a0392e..398a4c29c0989 100644
--- a/apps/server/src/orchestration-v2/Orchestrator.ts
+++ b/apps/server/src/orchestration-v2/Orchestrator.ts
@@ -128,7 +128,7 @@ import {
cancelledBeforeStart,
continuationFailedBeforePrompt,
continuationStartRetryDelayMs,
- delegatedTaskAwaitsSubagents,
+ delegatedTaskAwaitsWork,
delegatedTaskResultRun,
delegatedTaskRuns,
isRestartContinuationSource,
@@ -9489,9 +9489,21 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
(row) => row.id === message.runId,
);
if (run === undefined || run.delegatedTaskId !== undefined) return;
+ // A wake continues the latest follow-up while it is open, as it continues the original task.
+ const latestFollowupId =
+ command.notification === undefined
+ ? undefined
+ : (yield* projectionStore
+ .getThreadRecords(command.threadId, ["runs"])
+ .pipe(mapDispatchError(command))).runs
+ .filter((row) => row.id !== run.id && row.delegatedTaskId !== undefined)
+ .toSorted((a, b) => b.ordinal - a.ordinal)[0]?.delegatedTaskId;
const parent = yield* projectionStore
.getThreadRecords(parentThreadId, ["runs", "subagents", "providerTurns", "attempts"], {
- subagentIds: child.forkedFrom?.type === "node" ? [child.forkedFrom.nodeId] : [],
+ subagentIds: [
+ ...(child.forkedFrom?.type === "node" ? [child.forkedFrom.nodeId] : []),
+ ...(latestFollowupId === undefined ? [] : [latestFollowupId]),
+ ],
})
.pipe(mapDispatchError(command));
const original = parent.subagents.find(
@@ -9501,6 +9513,27 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
task.id === (child.forkedFrom?.type === "node" ? child.forkedFrom.nodeId : undefined),
);
if (original === undefined || original.result === null) return;
+ const openFollowup = parent.subagents.find(
+ (task) =>
+ task.id === latestFollowupId &&
+ task.origin === "app_owned" &&
+ task.childThreadId === child.id &&
+ task.cancelledRunId === undefined &&
+ task.result === null,
+ );
+ if (openFollowup !== undefined) {
+ yield* emit(
+ events,
+ command,
+ )({
+ type: "run.updated",
+ threadId: child.id,
+ runId: run.id,
+ occurredAt: message.createdAt,
+ payload: { ...run, delegatedTaskId: openFollowup.id },
+ });
+ return;
+ }
const owner = followUpOwnerRun(parent.runs);
if (owner === null || owner.rootNodeId === null) return;
const now = message.createdAt;
@@ -14115,8 +14148,17 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
]);
return;
}
- // A follow-up ends with the subagents its turns started, as the original task does.
- if (delegatedTaskAwaitsSubagents(taskRuns, childControls.subagents)) return;
+ // A follow-up ends with the work it awaits, as the original task does. A cancelled task
+ // does not wait for the background work its cancel is stopping.
+ if (
+ delegatedTaskAwaitsWork(
+ taskRuns,
+ options.task.cancelledRunId === undefined
+ ? childControls
+ : { subagents: childControls.subagents, providerThreads: [] },
+ )
+ )
+ return;
if (
yield* childAwaitsRestartContinuation(
taskRuns.length === 0 ? childControls.runs : taskRuns,
@@ -15691,7 +15733,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
if (
run === undefined ||
delegatedTaskTerminalStatus(run.status) === null ||
- delegatedTaskAwaitsSubagents(runs, child.subagents)
+ delegatedTaskAwaitsWork(runs, child)
)
return true;
return yield* childAwaitsRestartContinuation(runs, run);
@@ -15786,7 +15828,8 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
? child.thread.forkedFrom?.type === "node" &&
child.thread.forkedFrom.nodeId === input.taskId &&
progressAfterTurn(child).state !== "result_available"
- : delegatedTaskTerminalStatus(runOutcome(followUpRun)) === null;
+ : delegatedTaskTerminalStatus(runOutcome(followUpRun)) === null ||
+ delegatedTaskAwaitsWork(delegatedTaskRuns(child, input.taskId), child);
yield* threadDispatch.withLock(
command.threadId,
Effect.gen(function* () {
diff --git a/apps/server/src/orchestration-v2/RestartBackgroundNote.ts b/apps/server/src/orchestration-v2/RestartBackgroundNote.ts
index 3de10f4e944ad..23a95bc2bd20f 100644
--- a/apps/server/src/orchestration-v2/RestartBackgroundNote.ts
+++ b/apps/server/src/orchestration-v2/RestartBackgroundNote.ts
@@ -2,6 +2,7 @@ import type {
NodeId,
OrchestrationV2ConversationMessage,
OrchestrationV2PendingBackgroundTask,
+ OrchestrationV2ProviderThread,
OrchestrationV2ProviderTurn,
OrchestrationV2RestartCancelledBackgroundWork,
OrchestrationV2Run,
@@ -10,6 +11,7 @@ import type {
OrchestrationV2TurnItem,
} from "@t3tools/contracts";
import { subagentHoldsCompletion } from "@t3tools/provider-core/server/subagentProjection";
+import { backgroundWorkHoldsCompletion } from "@t3tools/shared/orchestrationV2PendingBackgroundWork";
import { latestRootProviderFailure, runRanAfter } from "@t3tools/shared/orchestrationV2ThreadError";
type Work = OrchestrationV2RestartCancelledBackgroundWork;
@@ -224,15 +226,28 @@ export function delegatedTaskRuns(
return projection.runs.filter((run) => owned.has(run.id));
}
-/** A follow-up task's runs still owe a subagent they started its result. */
-export function delegatedTaskAwaitsSubagents(
+/**
+ * A follow-up task's runs still owe a subagent they started its result, or the child still
+ * awaits background work, as the original task waits on both.
+ */
+export function delegatedTaskAwaitsWork(
runs: ReadonlyArray,
- subagents: ReadonlyArray<
- Pick
- >,
+ child: {
+ readonly subagents: ReadonlyArray<
+ Pick
+ >;
+ readonly providerThreads: ReadonlyArray<
+ Pick
+ >;
+ },
): boolean {
const runIds = new Set(runs.map((run) => run.id));
- return subagents.some((task) => runIds.has(task.runId) && subagentHoldsCompletion(task));
+ return (
+ child.subagents.some((task) => runIds.has(task.runId) && subagentHoldsCompletion(task)) ||
+ child.providerThreads.some((thread) =>
+ backgroundWorkHoldsCompletion(thread.pendingBackgroundTasks ?? []),
+ )
+ );
}
/** A follow-up task's latest run, passing over queued work it no longer waits on, as the original task does. */
From 08b8689a856bb282350bbb30c94e5d8cd22fec7a Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 13 Nov 2026 14:54:05 -0800
Subject: [PATCH 004/690] fix(provider-grok): Grok follow-ups queue instead of
cancelling the running tool (#36422)
---
.../orchestration-v2/CommandPolicy.test.ts | 23 +++++++
.../testkit/fixtures/index.ts | 6 --
.../message_steering/grok_transcript.ndjson | 67 -------------------
packages/provider-grok/src/server/adapter.ts | 2 +
4 files changed, 25 insertions(+), 73 deletions(-)
delete mode 100644 apps/server/src/orchestration-v2/testkit/fixtures/message_steering/grok_transcript.ndjson
diff --git a/apps/server/src/orchestration-v2/CommandPolicy.test.ts b/apps/server/src/orchestration-v2/CommandPolicy.test.ts
index 85babb8a869bf..14a803b1d1d60 100644
--- a/apps/server/src/orchestration-v2/CommandPolicy.test.ts
+++ b/apps/server/src/orchestration-v2/CommandPolicy.test.ts
@@ -15,6 +15,7 @@ import * as Effect from "effect/Effect";
import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts";
import { CursorProviderCapabilitiesV2 } from "@t3tools/provider-cursor/testing";
+import { makeGrokAcpAdapterFlavor } from "@t3tools/provider-grok/testing";
import * as CommandPolicy from "./CommandPolicy.ts";
const commandId = CommandId.make("command-policy-test");
@@ -211,6 +212,28 @@ it("queues a steer for a provider that cannot steer either way", () => {
);
});
+// regression: #27014 a Grok follow-up cancels the foreground tool the user wanted to finish
+it("does not steer a Grok follow-up by interrupting its running turn", () => {
+ const grok = makeGrokAcpAdapterFlavor(
+ { makeRuntime: () => Effect.never } as unknown as Parameters<
+ typeof makeGrokAcpAdapterFlavor
+ >[0],
+ "/home/ada",
+ ).capabilities;
+ const projection = dispatchProjection(grok);
+ for (const deliveryIntent of ["auto", "steer"] as const) {
+ const mode = CommandPolicy.resolveMessageDispatchIntent(
+ projection,
+ { type: "start_immediately" },
+ deliveryIntent,
+ );
+ assert.isFalse(
+ mode.type === "steer_active" && !grok.turns.supportsActiveSteering,
+ `${deliveryIntent} follow-up resolved to ${mode.type} by interrupt-restart`,
+ );
+ }
+});
+
it.each(["/compact", " /LOGOUT "])("queues a steer sent while %s runs", (text) => {
const projection = dispatchProjection(baseCapabilities);
const maintenance = {
diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/index.ts b/apps/server/src/orchestration-v2/testkit/fixtures/index.ts
index ce6d739b1cdd3..1eae24c5bb2b4 100644
--- a/apps/server/src/orchestration-v2/testkit/fixtures/index.ts
+++ b/apps/server/src/orchestration-v2/testkit/fixtures/index.ts
@@ -1779,12 +1779,6 @@ export const ORCHESTRATOR_REPLAY_FIXTURES: ReadonlyArray, HOME is /home/grok-replay and the recording user is grok-replay. T3-owned prompt text, MCP servers and initialize params are . Personal skills, machine identity, account settings and announcement broadcasts are removed, as are responses to Grok-internal request ids that T3's protocol drops. Timestamps are kept as recorded.","droppedFrames":5}}
-{"type":"expect_outbound","label":"request:initialize","frame":{"kind":"request","method":"initialize","params":{"protocolVersion":2,"info":"","capabilities":"","clientCapabilities":"","clientInfo":"","_meta":{"clientType":"extension"}}}}
-{"type":"emit_inbound","label":"response:initialize","frame":{"kind":"response","method":"initialize","result":{"protocolVersion":1,"agentCapabilities":{"loadSession":true,"promptCapabilities":{"image":false,"audio":false,"embeddedContext":true},"mcpCapabilities":{"http":true,"sse":true},"sessionCapabilities":{"list":{},"resume":{},"close":{}},"auth":{},"_meta":{"x.ai/fs_notify":true,"x.ai/hooks":{"blockingEvents":["pre_tool_use","stop","subagent_stop"],"decisions":["deny","block"],"stopSignals":["continue","stopReason","additionalContext"]},"x.ai/capabilities":{"toolOverrides":{"x_keyword_search":true,"x_semantic_search":true,"x_user_search":false,"x_thread_fetch":false}}}},"authMethods":[{"id":"cached_token","name":"cached_token","description":"Cached token from ~/.grok/auth.json"},{"id":"grok.com","name":"Grok","description":"Sign in with Grok"}],"_meta":{"grokShell":true,"defaultAuthMethodId":"cached_token","x.ai/mcp/sdk":true,"x.ai/pluginDirs":true,"currentWorkingDirectory":"","agentVersion":"1.0.41","agentId":"00000000-0000-4000-8000-000000000000","agentInstanceId":"00000000-0000-4000-8000-000000000000","hostname":"grok-replay","modelState":{"currentModelId":"grok-4.7","availableModels":[{"modelId":"grok-4.7","name":"Grok 4.7","description":"SpaceXAI's latest frontier model","_meta":{"totalContextTokens":500000,"agentType":"grok-build-plan","supportsReasoningEffort":true,"reasoningEffort":"high","reasoningEfforts":[{"id":"xhigh","value":"xhigh","label":"Extra High","description":"Maximum reasoning for the hardest tasks.","default":false},{"id":"high","value":"high","label":"High","description":"Thorough reasoning and quality. Recommended.","default":true},{"id":"medium","value":"medium","label":"Medium","description":"Strong quality with a faster turnaround.","default":false},{"id":"low","value":"low","label":"Low","description":"Fastest responses. Best for simple tasks.","default":false}]}},{"modelId":"grok-4.7-build-fast","name":"Grok 4.7 Fast","description":"Fast variant. 2x the price.","_meta":{"totalContextTokens":500000,"agentType":"grok-build-plan","supportsReasoningEffort":true,"reasoningEffort":"high","reasoningEfforts":[{"id":"xhigh","value":"xhigh","label":"Extra High","description":"Maximum reasoning for the hardest tasks.","default":false},{"id":"high","value":"high","label":"High","description":"Thorough reasoning and quality. Recommended.","default":true},{"id":"medium","value":"medium","label":"Medium","description":"Strong quality with a faster turnaround.","default":false},{"id":"low","value":"low","label":"Low","description":"Fastest responses. Best for simple tasks.","default":false}]}},{"modelId":"grok-4.6","name":"Grok 4.6","_meta":{"totalContextTokens":500000,"agentType":"grok-build-plan","supportsReasoningEffort":true,"reasoningEffort":"high","reasoningEfforts":[{"id":"xhigh","value":"xhigh","label":"Extra High","description":"Highest effort and reasoning level","default":false},{"id":"high","value":"high","label":"High","description":"Higher implementation quality with extensive reasoning","default":true},{"id":"medium","value":"medium","label":"Medium","description":"Balanced effort with standard implementation and testing","default":false},{"id":"low","value":"low","label":"Low","description":"Quick, fast implementations","default":false}]}},{"modelId":"grok-4.5","name":"Grok 4.5","_meta":{"totalContextTokens":500000,"agentType":"grok-build-plan","supportsReasoningEffort":true,"reasoningEffort":"high","reasoningEfforts":[{"id":"high","value":"high","label":"High","description":"Highest implementation quality with extensive reasoning","default":true},{"id":"medium","value":"medium","label":"Medium","description":"Balanced effort with standard implementation and testing","default":false},{"id":"low","value":"low","label":"Low","description":"Quick, fast implementations","default":false}]}}]},"mcpServers":[],"mcpApps":false,"metadata":null,"availableCommands":[{"name":"compact","description":"Compress conversation history to save context window","input":{"hint":"optional context about what to preserve"}},{"name":"always-approve","description":"Toggle always-approve mode (skip all permission prompts)","input":{"hint":"on|off"}},{"name":"context","description":"Show context window usage and session stats","input":null},{"name":"session-info","description":"Show session details (model, turns, context usage)","input":null},{"name":"deep-research","description":"Research with bounded parallel agents, cross-check evidence, and write a cited report","input":{"hint":""}},{"name":"workflow","description":"Launch a saved workflow, list runs, or manage a run (pause, resume, stop, save)","input":{"hint":" [--agent-budget N] [--effort LEVEL] [args] | runs | pause|resume|stop|save [name]"}},{"name":"goal","description":"Set, manage, or check an autonomous goal","input":{"hint":" [--budget ] | status | pause | resume | clear"}}],"cancelRewind":true,"sessionRecap":true,"feedbackTraceOffer":false,"voiceMode":true}}}}
-{"type":"expect_outbound","label":"request:session/new","frame":{"kind":"request","method":"session/new","params":{"cwd":"","mcpServers":""}}}
-{"type":"emit_inbound","label":"notification:_x.ai/mcp/servers_updated","frame":{"kind":"notification","method":"_x.ai/mcp/servers_updated","params":{"mcpServers":[]}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session/setup","frame":{"kind":"notification","method":"_x.ai/session/setup","params":{"method":"session/new","phase":"auth","sessionId":null}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session/setup","frame":{"kind":"notification","method":"_x.ai/session/setup","params":{"method":"session/new","phase":"resolve_workspace","sessionId":null}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session/setup","frame":{"kind":"notification","method":"_x.ai/session/setup","params":{"method":"session/new","phase":"folder_trust","sessionId":null}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session/setup","frame":{"kind":"notification","method":"_x.ai/session/setup","params":{"method":"session/new","phase":"plugin_registry","sessionId":null}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session/setup","frame":{"kind":"notification","method":"_x.ai/session/setup","params":{"method":"session/new","phase":"mcp_merge","sessionId":null}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session/setup","frame":{"kind":"notification","method":"_x.ai/session/setup","params":{"method":"session/new","phase":"persistence_init","sessionId":"00000000-0000-4000-8000-000000000001"}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session/setup","frame":{"kind":"notification","method":"_x.ai/session/setup","params":{"method":"session/new","phase":"spawn_session_actor","sessionId":"00000000-0000-4000-8000-000000000001"}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session/setup","frame":{"kind":"notification","method":"_x.ai/session/setup","params":{"method":"session/new","phase":"model_switch","sessionId":"00000000-0000-4000-8000-000000000001"}}}
-{"type":"emit_inbound","label":"notification:_x.ai/mcp_initialized","frame":{"kind":"notification","method":"_x.ai/mcp_initialized","params":{"sessionId":"00000000-0000-4000-8000-000000000001","mcpToolCount":0,"elapsedMs":0}}}
-{"type":"emit_inbound","label":"notification:session/update:available_commands_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"available_commands_update","availableCommands":[{"name":"compact","description":"Compress conversation history to save context window","input":{"hint":"optional context about what to preserve"}},{"name":"always-approve","description":"Toggle always-approve mode (skip all permission prompts)","input":{"hint":"on|off"}},{"name":"context","description":"Show context window usage and session stats","input":null},{"name":"session-info","description":"Show session details (model, turns, context usage)","input":null},{"name":"feedback","description":"Send feedback about the current session","input":{"hint":"feedback text"}},{"name":"deep-research","description":"Research with bounded parallel agents, cross-check evidence, and write a cited report","input":{"hint":""},"_meta":{"workflowSource":"builtin","workflowPath":"/home/grok-replay/.grok/bundled/workflows/deep-research.rhai"}},{"name":"workflow","description":"Launch a saved workflow, list runs, or manage a run (pause, resume, stop, save)","input":{"hint":" [--agent-budget N] [--effort LEVEL] [args] | runs | pause|resume|stop|save [name]"}},{"name":"goal","description":"Set, manage, or check an autonomous goal","input":{"hint":" [--budget ] | status | pause | resume | clear"}},{"name":"loop","description":"Run a prompt on a recurring interval","input":{"hint":"[interval] "}},{"name":"build-with-ai","description":"Build AI apps on SpaceXAI (XAI_API_KEY + api.x.ai)","input":{"hint":""},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/build-with-ai/SKILL.md","bareName":"build-with-ai","qualifiedName":"bundled:build-with-ai"}},{"name":"code-review","description":"Run an extremely strict maintainability review for abstraction quality, giant files, and spaghetti-condition growth. Use for a deep code quality audit or an especially harsh maintainability review.","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/code-review/SKILL.md","bareName":"code-review","qualifiedName":"bundled:code-review"}},{"name":"create-skill","description":"Create a new Grok skill","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/create-skill/SKILL.md","bareName":"create-skill","qualifiedName":"bundled:create-skill"}},{"name":"create-workflow","description":"Author a new multi-agent workflow","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/create-workflow/SKILL.md","bareName":"create-workflow","qualifiedName":"bundled:create-workflow"}},{"name":"execute-plan","description":"Execute a PR Plan DAG from a design document. Parses the plan, topologically sorts it, implements PRs in parallel using worktree-isolated subagents, runs mandatory orchestrator-level review, and assembles either a Graphite PR stack or a plain-git branch stack depending on tool availability.","input":{"hint":" [--effort N] [--concurrency N] [--dry-run] [--resume ] [--instructions \"...\"] [--no-graphite] [--auto-pr]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/execute-plan/SKILL.md","bareName":"execute-plan","qualifiedName":"bundled:execute-plan"}},{"name":"game-assets","description":"Engine-ready game art, 2D sprites to 3D models","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/game-assets/SKILL.md","bareName":"game-assets","qualifiedName":"bundled:game-assets"}},{"name":"bundled:imagine","description":"Prompting and workflow guidance for Imagine image and video tools","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/imagine/SKILL.md","bareName":"imagine","qualifiedName":"bundled:imagine"}},{"name":"implement","description":"Run the full implement-review-fix loop using implementer and reviewer personas. Supports effort-based multi-reviewer scaling (2–7 reviewers) with automatic specialization selection. Includes memory-based feedback loop that learns from past review patterns. Loops until all reviewers find 0 issues of any severity.","input":{"hint":"[--effort N] "},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/implement/SKILL.md","bareName":"implement","qualifiedName":"bundled:implement"}},{"name":"learn","description":"Learn from your own Grok Build sessions and tune your setup: add skills for things you keep typing, fix stale skill instructions, and retire skills, plugins, or MCP servers you never use. Use on \"/learn\", \"learn from my traces\", \"what skills do I never use\". Not for summarizing sessions into notes or memory.","input":{"hint":"[--mode step|auto|report] [--days N | --since-last | --limit N] [--per-trace] [--include-headless] [--cwd PATH] [--focus TEXT] [--resume]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/learn/SKILL.md","bareName":"learn","qualifiedName":"bundled:learn"}},{"name":"long-running-background-tasks","description":"Required reading before you start, watch, or wait on anything that keeps running after you launch it — background jobs, watchers, scheduled loops, CI, pull requests, training runs, dev servers, long builds. Read it before you launch such work and before you report on its state. Saying where a running job stands counts as working on it. Use when: about to launch, supervise, inspect, diagnose, or report on work that keeps running after it is started.","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/long-running-background-tasks/SKILL.md","bareName":"long-running-background-tasks","qualifiedName":"bundled:long-running-background-tasks"}},{"name":"pr-babysit","description":"Monitor PRs, fix CI failures, address review comments (human and bot/automated), resolve merge conflicts, and restack stacks. Supports independent PRs, Graphite stacks, and GitHub stacked PRs (gh-stack). Optional ship lands a ready PR bottom-up when every job has passed and no comments remain open.","input":{"hint":"add [--ship] | remove | list | check | ship [on|off] [number...]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/pr-babysit/SKILL.md","bareName":"pr-babysit","qualifiedName":"bundled:pr-babysit"}},{"name":"resume-claude","description":"Continue from a recent Claude Code session","input":{"hint":"[words describing the session | session id]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/resume-claude/SKILL.md","bareName":"resume-claude","qualifiedName":"bundled:resume-claude"}},{"name":"resume-codex","description":"Continue from a recent Codex session","input":{"hint":"[words describing the session | session id]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/resume-codex/SKILL.md","bareName":"resume-codex","qualifiedName":"bundled:resume-codex"}},{"name":"resume-cursor","description":"Continue from a recent Cursor session","input":{"hint":"[words describing the session | session id]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/resume-cursor/SKILL.md","bareName":"resume-cursor","qualifiedName":"bundled:resume-cursor"}},{"name":"review","description":"Run a reviewer subagent against your whole branch vs main, your uncommitted working state, a named branch, a single GitHub PR, or an entire Graphite/stacked-PR stack (one reviewer subagent per PR). A bare /review defaults to reviewing your uncommitted working state when the tree is dirty, or your whole branch vs main when the tree is clean. Main, local, and branch modes write a review file plus a summary to disk. PR and stack modes post the findings as a PENDING GitHub review for the user to inspect and submit through the UI. Use --main for an explicit clean-tree branch review, --stack for stacked PRs, --submit to publish immediately as COMMENT, or --approve to publish and approve; the default remains PENDING.","input":{"hint":"[--main | --local | --branch | --pr | --stack | ] [--approve | --submit]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/review/SKILL.md","bareName":"review","qualifiedName":"bundled:review"}},{"name":"skill-design-principles","description":"Concise, high-signal principles for writing and editing skills well. Use whenever authoring or editing a skill.","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/skill-design-principles/SKILL.md","bareName":"skill-design-principles","qualifiedName":"bundled:skill-design-principles"}},{"name":"statusline","description":"Configure the Grok Build status line.","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/statusline/SKILL.md","bareName":"statusline","qualifiedName":"bundled:statusline"}},{"name":"threejs-frame-conventions","description":"One frame for Three.js controls, camera, and facing","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/threejs-frame-conventions/SKILL.md","bareName":"threejs-frame-conventions","qualifiedName":"bundled:threejs-frame-conventions"}},{"name":"learn-traces","description":"Workflow: Map-reduce a collected set of Grok Build sessions into a curated list of harness changes: new or edited skills, plugin enables, deletes","input":{"hint":"[--agent-budget N] [--effort LEVEL] [args]"},"_meta":{"workflowSource":"bundled","workflowPath":"/home/grok-replay/.grok/bundled/workflows/learn-traces.rhai"}}],"_meta":{"tools":["run_terminal_command","read_file","search_replace","list_dir","grep","kill_command_or_subagent","todo_write","get_command_or_subagent_output","spawn_subagent","scheduler_create","scheduler_delete","scheduler_list","monitor","search_tool","use_tool","workflow","enter_plan_mode","exit_plan_mode","ask_user_question","send_feedback","web_search","web_fetch","image_gen","image_edit","image_to_video","reference_to_video","write"]}},"_meta":{"totalTokens":2263,"eventId":"00000000-0000-4000-8000-000000000001-0","agentTimestampMs":1790300235225,"updateType":"AvailableCommandsUpdate","updateParams":{"commandsCount":60}}}}}
-{"type":"emit_inbound","label":"notification:session/update:available_commands_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"available_commands_update","availableCommands":[{"name":"compact","description":"Compress conversation history to save context window","input":{"hint":"optional context about what to preserve"}},{"name":"always-approve","description":"Toggle always-approve mode (skip all permission prompts)","input":{"hint":"on|off"}},{"name":"context","description":"Show context window usage and session stats","input":null},{"name":"session-info","description":"Show session details (model, turns, context usage)","input":null},{"name":"feedback","description":"Send feedback about the current session","input":{"hint":"feedback text"}},{"name":"deep-research","description":"Research with bounded parallel agents, cross-check evidence, and write a cited report","input":{"hint":""},"_meta":{"workflowSource":"builtin","workflowPath":"/home/grok-replay/.grok/bundled/workflows/deep-research.rhai"}},{"name":"workflow","description":"Launch a saved workflow, list runs, or manage a run (pause, resume, stop, save)","input":{"hint":" [--agent-budget N] [--effort LEVEL] [args] | runs | pause|resume|stop|save [name]"}},{"name":"goal","description":"Set, manage, or check an autonomous goal","input":{"hint":" [--budget ] | status | pause | resume | clear"}},{"name":"loop","description":"Run a prompt on a recurring interval","input":{"hint":"[interval] "}},{"name":"build-with-ai","description":"Build AI apps on SpaceXAI (XAI_API_KEY + api.x.ai)","input":{"hint":""},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/build-with-ai/SKILL.md","bareName":"build-with-ai","qualifiedName":"bundled:build-with-ai"}},{"name":"code-review","description":"Run an extremely strict maintainability review for abstraction quality, giant files, and spaghetti-condition growth. Use for a deep code quality audit or an especially harsh maintainability review.","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/code-review/SKILL.md","bareName":"code-review","qualifiedName":"bundled:code-review"}},{"name":"create-skill","description":"Create a new Grok skill","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/create-skill/SKILL.md","bareName":"create-skill","qualifiedName":"bundled:create-skill"}},{"name":"create-workflow","description":"Author a new multi-agent workflow","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/create-workflow/SKILL.md","bareName":"create-workflow","qualifiedName":"bundled:create-workflow"}},{"name":"execute-plan","description":"Execute a PR Plan DAG from a design document. Parses the plan, topologically sorts it, implements PRs in parallel using worktree-isolated subagents, runs mandatory orchestrator-level review, and assembles either a Graphite PR stack or a plain-git branch stack depending on tool availability.","input":{"hint":" [--effort N] [--concurrency N] [--dry-run] [--resume ] [--instructions \"...\"] [--no-graphite] [--auto-pr]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/execute-plan/SKILL.md","bareName":"execute-plan","qualifiedName":"bundled:execute-plan"}},{"name":"game-assets","description":"Engine-ready game art, 2D sprites to 3D models","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/game-assets/SKILL.md","bareName":"game-assets","qualifiedName":"bundled:game-assets"}},{"name":"bundled:imagine","description":"Prompting and workflow guidance for Imagine image and video tools","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/imagine/SKILL.md","bareName":"imagine","qualifiedName":"bundled:imagine"}},{"name":"implement","description":"Run the full implement-review-fix loop using implementer and reviewer personas. Supports effort-based multi-reviewer scaling (2–7 reviewers) with automatic specialization selection. Includes memory-based feedback loop that learns from past review patterns. Loops until all reviewers find 0 issues of any severity.","input":{"hint":"[--effort N] "},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/implement/SKILL.md","bareName":"implement","qualifiedName":"bundled:implement"}},{"name":"learn","description":"Learn from your own Grok Build sessions and tune your setup: add skills for things you keep typing, fix stale skill instructions, and retire skills, plugins, or MCP servers you never use. Use on \"/learn\", \"learn from my traces\", \"what skills do I never use\". Not for summarizing sessions into notes or memory.","input":{"hint":"[--mode step|auto|report] [--days N | --since-last | --limit N] [--per-trace] [--include-headless] [--cwd PATH] [--focus TEXT] [--resume]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/learn/SKILL.md","bareName":"learn","qualifiedName":"bundled:learn"}},{"name":"long-running-background-tasks","description":"Required reading before you start, watch, or wait on anything that keeps running after you launch it — background jobs, watchers, scheduled loops, CI, pull requests, training runs, dev servers, long builds. Read it before you launch such work and before you report on its state. Saying where a running job stands counts as working on it. Use when: about to launch, supervise, inspect, diagnose, or report on work that keeps running after it is started.","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/long-running-background-tasks/SKILL.md","bareName":"long-running-background-tasks","qualifiedName":"bundled:long-running-background-tasks"}},{"name":"pr-babysit","description":"Monitor PRs, fix CI failures, address review comments (human and bot/automated), resolve merge conflicts, and restack stacks. Supports independent PRs, Graphite stacks, and GitHub stacked PRs (gh-stack). Optional ship lands a ready PR bottom-up when every job has passed and no comments remain open.","input":{"hint":"add [--ship] | remove | list | check | ship [on|off] [number...]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/pr-babysit/SKILL.md","bareName":"pr-babysit","qualifiedName":"bundled:pr-babysit"}},{"name":"resume-claude","description":"Continue from a recent Claude Code session","input":{"hint":"[words describing the session | session id]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/resume-claude/SKILL.md","bareName":"resume-claude","qualifiedName":"bundled:resume-claude"}},{"name":"resume-codex","description":"Continue from a recent Codex session","input":{"hint":"[words describing the session | session id]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/resume-codex/SKILL.md","bareName":"resume-codex","qualifiedName":"bundled:resume-codex"}},{"name":"resume-cursor","description":"Continue from a recent Cursor session","input":{"hint":"[words describing the session | session id]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/resume-cursor/SKILL.md","bareName":"resume-cursor","qualifiedName":"bundled:resume-cursor"}},{"name":"review","description":"Run a reviewer subagent against your whole branch vs main, your uncommitted working state, a named branch, a single GitHub PR, or an entire Graphite/stacked-PR stack (one reviewer subagent per PR). A bare /review defaults to reviewing your uncommitted working state when the tree is dirty, or your whole branch vs main when the tree is clean. Main, local, and branch modes write a review file plus a summary to disk. PR and stack modes post the findings as a PENDING GitHub review for the user to inspect and submit through the UI. Use --main for an explicit clean-tree branch review, --stack for stacked PRs, --submit to publish immediately as COMMENT, or --approve to publish and approve; the default remains PENDING.","input":{"hint":"[--main | --local | --branch | --pr | --stack | ] [--approve | --submit]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/review/SKILL.md","bareName":"review","qualifiedName":"bundled:review"}},{"name":"skill-design-principles","description":"Concise, high-signal principles for writing and editing skills well. Use whenever authoring or editing a skill.","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/skill-design-principles/SKILL.md","bareName":"skill-design-principles","qualifiedName":"bundled:skill-design-principles"}},{"name":"statusline","description":"Configure the Grok Build status line.","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/statusline/SKILL.md","bareName":"statusline","qualifiedName":"bundled:statusline"}},{"name":"threejs-frame-conventions","description":"One frame for Three.js controls, camera, and facing","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/threejs-frame-conventions/SKILL.md","bareName":"threejs-frame-conventions","qualifiedName":"bundled:threejs-frame-conventions"}},{"name":"learn-traces","description":"Workflow: Map-reduce a collected set of Grok Build sessions into a curated list of harness changes: new or edited skills, plugin enables, deletes","input":{"hint":"[--agent-budget N] [--effort LEVEL] [args]"},"_meta":{"workflowSource":"bundled","workflowPath":"/home/grok-replay/.grok/bundled/workflows/learn-traces.rhai"}}],"_meta":{"tools":["run_terminal_command","read_file","search_replace","list_dir","grep","kill_command_or_subagent","todo_write","get_command_or_subagent_output","spawn_subagent","scheduler_create","scheduler_delete","scheduler_list","monitor","search_tool","use_tool","workflow","enter_plan_mode","exit_plan_mode","ask_user_question","send_feedback","web_search","web_fetch","image_gen","image_edit","image_to_video","reference_to_video","write"]}},"_meta":{"totalTokens":2263,"eventId":"00000000-0000-4000-8000-000000000001-1","agentTimestampMs":1790300235246,"updateType":"AvailableCommandsUpdate","updateParams":{"commandsCount":60}}}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session_notification:model_changed","frame":{"kind":"notification","method":"_x.ai/session_notification","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"model_changed","model_id":"grok-4.7","reasoning_effort":"high"}}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session/setup","frame":{"kind":"notification","method":"_x.ai/session/setup","params":{"method":"session/new","phase":"git_discovery","sessionId":"00000000-0000-4000-8000-000000000001"}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session/setup","frame":{"kind":"notification","method":"_x.ai/session/setup","params":{"method":"session/new","phase":"finalize_response","sessionId":"00000000-0000-4000-8000-000000000001"}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session/setup","frame":{"kind":"notification","method":"_x.ai/session/setup","params":{"method":"session/new","phase":"tool_overrides","sessionId":"00000000-0000-4000-8000-000000000001"}}}
-{"type":"emit_inbound","label":"response:session/new","frame":{"kind":"response","method":"session/new","result":{"sessionId":"00000000-0000-4000-8000-000000000001","models":{"currentModelId":"grok-4.7","availableModels":[{"modelId":"grok-4.7","name":"Grok 4.7","description":"SpaceXAI's latest frontier model","_meta":{"totalContextTokens":500000,"agentType":"grok-build-plan","supportsReasoningEffort":true,"reasoningEffort":"high","reasoningEfforts":[{"id":"xhigh","value":"xhigh","label":"Extra High","description":"Maximum reasoning for the hardest tasks.","default":false},{"id":"high","value":"high","label":"High","description":"Thorough reasoning and quality. Recommended.","default":true},{"id":"medium","value":"medium","label":"Medium","description":"Strong quality with a faster turnaround.","default":false},{"id":"low","value":"low","label":"Low","description":"Fastest responses. Best for simple tasks.","default":false}]}},{"modelId":"grok-4.7-build-fast","name":"Grok 4.7 Fast","description":"Fast variant. 2x the price.","_meta":{"totalContextTokens":500000,"agentType":"grok-build-plan","supportsReasoningEffort":true,"reasoningEffort":"high","reasoningEfforts":[{"id":"xhigh","value":"xhigh","label":"Extra High","description":"Maximum reasoning for the hardest tasks.","default":false},{"id":"high","value":"high","label":"High","description":"Thorough reasoning and quality. Recommended.","default":true},{"id":"medium","value":"medium","label":"Medium","description":"Strong quality with a faster turnaround.","default":false},{"id":"low","value":"low","label":"Low","description":"Fastest responses. Best for simple tasks.","default":false}]}},{"modelId":"grok-4.6","name":"Grok 4.6","_meta":{"totalContextTokens":500000,"agentType":"grok-build-plan","supportsReasoningEffort":true,"reasoningEffort":"high","reasoningEfforts":[{"id":"xhigh","value":"xhigh","label":"Extra High","description":"Highest effort and reasoning level","default":false},{"id":"high","value":"high","label":"High","description":"Higher implementation quality with extensive reasoning","default":true},{"id":"medium","value":"medium","label":"Medium","description":"Balanced effort with standard implementation and testing","default":false},{"id":"low","value":"low","label":"Low","description":"Quick, fast implementations","default":false}]}},{"modelId":"grok-4.5","name":"Grok 4.5","_meta":{"totalContextTokens":500000,"agentType":"grok-build-plan","supportsReasoningEffort":true,"reasoningEffort":"high","reasoningEfforts":[{"id":"high","value":"high","label":"High","description":"Highest implementation quality with extensive reasoning","default":true},{"id":"medium","value":"medium","label":"Medium","description":"Balanced effort with standard implementation and testing","default":false},{"id":"low","value":"low","label":"Low","description":"Quick, fast implementations","default":false}]}}]},"configOptions":[{"id":"model","name":"Model","category":"model","type":"select","currentValue":"grok-4.7","options":[{"value":"grok-4.7","name":"Grok 4.7"},{"value":"grok-4.7-build-fast","name":"Grok 4.7 Fast"},{"value":"grok-4.6","name":"Grok 4.6"},{"value":"grok-4.5","name":"Grok 4.5"}]},{"id":"reasoning_effort","name":"Reasoning Effort","category":"thought_level","type":"select","currentValue":"high","options":[{"value":"xhigh","name":"Extra High","description":"Maximum reasoning for the hardest tasks."},{"value":"high","name":"High","description":"Thorough reasoning and quality. Recommended."},{"value":"medium","name":"Medium","description":"Strong quality with a faster turnaround."},{"value":"low","name":"Low","description":"Fastest responses. Best for simple tasks."}]}],"_meta":{"currentWorkingDirectory":"","codebaseIndexed":[],"isGitRepo":true,"gitRoot":"","showNonGitWarning":false,"feedbackEnabled":true,"x.ai/sessionConfig":{"options":[{"id":"grok-4.7","category":"model","label":"Grok 4.7","selected":true},{"id":"grok-4.7-build-fast","category":"model","label":"Grok 4.7 Fast","selected":false},{"id":"grok-4.6","category":"model","label":"Grok 4.6","selected":false},{"id":"grok-4.5","category":"model","label":"Grok 4.5","selected":false},{"id":"xhigh","category":"mode","label":"Extra High","description":"Maximum reasoning for the hardest tasks.","selected":false},{"id":"high","category":"mode","label":"High","description":"Thorough reasoning and quality. Recommended.","selected":true},{"id":"medium","category":"mode","label":"Medium","description":"Strong quality with a faster turnaround.","selected":false},{"id":"low","category":"mode","label":"Low","description":"Fastest responses. Best for simple tasks.","selected":false}]},"x.ai/sessionDetail":{"sessionId":"00000000-0000-4000-8000-000000000001","kind":"build","cwd":"","currentModelId":"grok-4.7"},"x.ai/memoryMode":"v2"}}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session/setup","frame":{"kind":"notification","method":"_x.ai/session/setup","params":{"method":"session/new","phase":"response_ready","sessionId":"00000000-0000-4000-8000-000000000001"}}}
-{"type":"emit_inbound","label":"notification:_x.ai/models/update","frame":{"kind":"notification","method":"_x.ai/models/update","params":{"currentModelId":"grok-4.7","availableModels":[{"modelId":"grok-4.7","name":"Grok 4.7","description":"SpaceXAI's latest frontier model","_meta":{"totalContextTokens":500000,"agentType":"grok-build-plan","supportsReasoningEffort":true,"reasoningEffort":"high","reasoningEfforts":[{"id":"xhigh","value":"xhigh","label":"Extra High","description":"Maximum reasoning for the hardest tasks.","default":false},{"id":"high","value":"high","label":"High","description":"Thorough reasoning and quality. Recommended.","default":true},{"id":"medium","value":"medium","label":"Medium","description":"Strong quality with a faster turnaround.","default":false},{"id":"low","value":"low","label":"Low","description":"Fastest responses. Best for simple tasks.","default":false}]}},{"modelId":"grok-4.7-build-fast","name":"Grok 4.7 Fast","description":"Fast variant. 2x the price.","_meta":{"totalContextTokens":500000,"agentType":"grok-build-plan","supportsReasoningEffort":true,"reasoningEffort":"high","reasoningEfforts":[{"id":"xhigh","value":"xhigh","label":"Extra High","description":"Maximum reasoning for the hardest tasks.","default":false},{"id":"high","value":"high","label":"High","description":"Thorough reasoning and quality. Recommended.","default":true},{"id":"medium","value":"medium","label":"Medium","description":"Strong quality with a faster turnaround.","default":false},{"id":"low","value":"low","label":"Low","description":"Fastest responses. Best for simple tasks.","default":false}]}},{"modelId":"grok-4.6","name":"Grok 4.6","_meta":{"totalContextTokens":500000,"agentType":"grok-build-plan","supportsReasoningEffort":true,"reasoningEffort":"high","reasoningEfforts":[{"id":"xhigh","value":"xhigh","label":"Extra High","description":"Highest effort and reasoning level","default":false},{"id":"high","value":"high","label":"High","description":"Higher implementation quality with extensive reasoning","default":true},{"id":"medium","value":"medium","label":"Medium","description":"Balanced effort with standard implementation and testing","default":false},{"id":"low","value":"low","label":"Low","description":"Quick, fast implementations","default":false}]}},{"modelId":"grok-4.5","name":"Grok 4.5","_meta":{"totalContextTokens":500000,"agentType":"grok-build-plan","supportsReasoningEffort":true,"reasoningEffort":"high","reasoningEfforts":[{"id":"high","value":"high","label":"High","description":"Highest implementation quality with extensive reasoning","default":true},{"id":"medium","value":"medium","label":"Medium","description":"Balanced effort with standard implementation and testing","default":false},{"id":"low","value":"low","label":"Low","description":"Quick, fast implementations","default":false}]}}]}}}
-{"type":"expect_outbound","label":"request:session/prompt","frame":{"kind":"request","method":"session/prompt","params":{"sessionId":"00000000-0000-4000-8000-000000000001","prompt":[{"type":"text","text":"\n\n\n\n\nRespond with exactly: steering fixture initial response\n"}],"_meta":{"promptId":"t3-xai-prompt-1","requestId":"t3-xai-prompt-1"}}}}
-{"type":"emit_inbound","label":"notification:_x.ai/sessions/changed","frame":{"kind":"notification","method":"_x.ai/sessions/changed","params":{"upserted":[{"sessionId":"00000000-0000-4000-8000-000000000001","title":null,"cwd":"","isWorktree":false,"modelId":"grok-4.7","reasoningEffort":"high","yolo":false,"activity":"working","resident":true,"lastChangeUnixMs":1790300235622,"origin":{"kind":"local"}}],"removed":[]}}}
-{"type":"emit_inbound","label":"notification:_x.ai/queue/changed","frame":{"kind":"notification","method":"_x.ai/queue/changed","params":{"sessionId":"00000000-0000-4000-8000-000000000001","entries":[{"id":"t3-xai-prompt-1","version":0,"kind":"prompt","text":"\n## T3 Code interaction mode: Default\n\nPrefer making reasonable assumptions and carrying out the user's request. Ask a concise question only when a missing user decision would materially change the result. Treat this mode as active until T3 Code supplies a different interaction-mode instruction.\n\n\n\nRespond with exactly: steering fixture initial response\n\n\nIn case you're asked: you are running in T3 Code through the Grok harness, as grok-build. No need to mention this otherwise. You can embed images and videos in your response using Markdown with absolute file paths.\n\n\nWhen the t3-code MCP server exposes link_pull_request, you must use it to register every pull request you create or work on for this thread. Call link_pull_request with the full PR URL immediately after creating a PR or starting work on an existing PR. For a stack, call it for every layer, not just the current branch or the top PR. This applies when creating or updating PRs through your host's CLI (including its stack commands) or its API: those operations do not register the PRs with this thread. Linking an already-linked PR is safe. Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing. Do not link unrelated PRs mentioned only as background. If a linking call fails, report that failure instead of claiming the PR is linked.\n","position":0}]}}}
-{"type":"emit_inbound","label":"notification:_x.ai/queue/changed","frame":{"kind":"notification","method":"_x.ai/queue/changed","params":{"sessionId":"00000000-0000-4000-8000-000000000001","entries":[],"runningPromptId":"t3-xai-prompt-1","runningText":"\n## T3 Code interaction mode: Default\n\nPrefer making reasonable assumptions and carrying out the user's request. Ask a concise question only when a missing user decision would materially change the result. Treat this mode as active until T3 Code supplies a different interaction-mode instruction.\n\n\n\nRespond with exactly: steering fixture initial response\n\n\nIn case you're asked: you are running in T3 Code through the Grok harness, as grok-build. No need to mention this otherwise. You can embed images and videos in your response using Markdown with absolute file paths.\n\n\nWhen the t3-code MCP server exposes link_pull_request, you must use it to register every pull request you create or work on for this thread. Call link_pull_request with the full PR URL immediately after creating a PR or starting work on an existing PR. For a stack, call it for every layer, not just the current branch or the top PR. This applies when creating or updating PRs through your host's CLI (including its stack commands) or its API: those operations do not register the PRs with this thread. Linking an already-linked PR is safe. Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing. Do not link unrelated PRs mentioned only as background. If a linking call fails, report that failure instead of claiming the PR is linked.\n","runningKind":"prompt"}}}
-{"type":"expect_outbound","label":"notification:session/cancel","frame":{"kind":"notification","method":"session/cancel","params":{"sessionId":"00000000-0000-4000-8000-000000000001","_meta":{"cancelTrigger":"ctrl_c"}}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session_notification:turn_completed","frame":{"kind":"notification","method":"_x.ai/session_notification","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"turn_completed","prompt_id":"t3-xai-prompt-1","stop_reason":"cancelled","elapsed_ms":22},"_meta":{"eventId":"00000000-0000-4000-8000-000000000001-4","agentTimestampMs":1790300235647,"cancelTrigger":"ctrl_c","cancellationCategory":"MidTurnAbort"}}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session/prompt_complete","frame":{"kind":"notification","method":"_x.ai/session/prompt_complete","params":{"sessionId":"00000000-0000-4000-8000-000000000001","promptId":"t3-xai-prompt-1","stopReason":"cancelled","agentResult":null,"cancelTrigger":"ctrl_c","cancellationCategory":"MidTurnAbort","cancellationContext":{"trigger":"ctrl_c"}}}}
-{"type":"emit_inbound","label":"notification:_x.ai/sessions/changed","frame":{"kind":"notification","method":"_x.ai/sessions/changed","params":{"upserted":[{"sessionId":"00000000-0000-4000-8000-000000000001","title":null,"cwd":"","isWorktree":false,"modelId":"grok-4.7","reasoningEffort":"high","yolo":false,"activity":"idle","resident":true,"lastChangeUnixMs":1790300235649,"origin":{"kind":"local"}}],"removed":[]}}}
-{"type":"emit_inbound","label":"response:session/prompt","frame":{"kind":"response","method":"session/prompt","result":{"stopReason":"cancelled","_meta":{"sessionId":"00000000-0000-4000-8000-000000000001","requestId":"t3-xai-prompt-1","promptId":"t3-xai-prompt-1","totalTokens":2263,"modelId":"grok-4.7","cancellationCategory":"MidTurnAbort","cancellationContext":{"trigger":"ctrl_c"},"cancelTrigger":"ctrl_c"}}}}
-{"type":"expect_outbound","label":"request:session/prompt","frame":{"kind":"request","method":"session/prompt","params":{"sessionId":"00000000-0000-4000-8000-000000000001","prompt":[{"type":"text","text":"\n\n\n\n\nActually, respond with exactly: steering fixture observed\n"},{"type":"text","text":""}],"_meta":{"promptId":"t3-xai-prompt-2","requestId":"t3-xai-prompt-2"}}}}
-{"type":"emit_inbound","label":"notification:_x.ai/sessions/changed","frame":{"kind":"notification","method":"_x.ai/sessions/changed","params":{"upserted":[{"sessionId":"00000000-0000-4000-8000-000000000001","title":null,"cwd":"","isWorktree":false,"modelId":"grok-4.7","reasoningEffort":"high","yolo":false,"activity":"working","resident":true,"lastChangeUnixMs":1790300235729,"origin":{"kind":"local"}}],"removed":[]}}}
-{"type":"emit_inbound","label":"notification:_x.ai/queue/changed","frame":{"kind":"notification","method":"_x.ai/queue/changed","params":{"sessionId":"00000000-0000-4000-8000-000000000001","entries":[{"id":"t3-xai-prompt-2","version":0,"kind":"prompt","text":"Actually, respond with exactly: steering fixture observed\n\nIn case you're asked: you are running in T3 Code through the Grok harness, as grok-build. No need to mention this otherwise. You can embed images and videos in your response using Markdown with absolute file paths.\n\n\nWhen the t3-code MCP server exposes link_pull_request, you must use it to register every pull request you create or work on for this thread. Call link_pull_request with the full PR URL immediately after creating a PR or starting work on an existing PR. For a stack, call it for every layer, not just the current branch or the top PR. This applies when creating or updating PRs through your host's CLI (including its stack commands) or its API: those operations do not register the PRs with this thread. Linking an already-linked PR is safe. Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing. Do not link unrelated PRs mentioned only as background. If a linking call fails, report that failure instead of claiming the PR is linked.\n","position":0}]}}}
-{"type":"emit_inbound","label":"notification:_x.ai/queue/changed","frame":{"kind":"notification","method":"_x.ai/queue/changed","params":{"sessionId":"00000000-0000-4000-8000-000000000001","entries":[],"runningPromptId":"t3-xai-prompt-2","runningText":"Actually, respond with exactly: steering fixture observed\n\nIn case you're asked: you are running in T3 Code through the Grok harness, as grok-build. No need to mention this otherwise. You can embed images and videos in your response using Markdown with absolute file paths.\n\n\nWhen the t3-code MCP server exposes link_pull_request, you must use it to register every pull request you create or work on for this thread. Call link_pull_request with the full PR URL immediately after creating a PR or starting work on an existing PR. For a stack, call it for every layer, not just the current branch or the top PR. This applies when creating or updating PRs through your host's CLI (including its stack commands) or its API: those operations do not register the PRs with this thread. Linking an already-linked PR is safe. Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing. Do not link unrelated PRs mentioned only as background. If a linking call fails, report that failure instead of claiming the PR is linked.\n","runningKind":"prompt"}}}
-{"type":"emit_inbound","label":"notification:_x.ai/models/update","frame":{"kind":"notification","method":"_x.ai/models/update","params":{"currentModelId":"grok-4.7","availableModels":[{"modelId":"grok-4.7","name":"Grok 4.7","description":"SpaceXAI's latest frontier model","_meta":{"totalContextTokens":500000,"agentType":"grok-build-plan","supportsReasoningEffort":true,"reasoningEffort":"high","reasoningEfforts":[{"id":"xhigh","value":"xhigh","label":"Extra High","description":"Maximum reasoning for the hardest tasks.","default":false},{"id":"high","value":"high","label":"High","description":"Thorough reasoning and quality. Recommended.","default":true},{"id":"medium","value":"medium","label":"Medium","description":"Strong quality with a faster turnaround.","default":false},{"id":"low","value":"low","label":"Low","description":"Fastest responses. Best for simple tasks.","default":false}]}},{"modelId":"grok-4.7-build-fast","name":"Grok 4.7 Fast","description":"Fast variant. 2x the price.","_meta":{"totalContextTokens":500000,"agentType":"grok-build-plan","supportsReasoningEffort":true,"reasoningEffort":"high","reasoningEfforts":[{"id":"xhigh","value":"xhigh","label":"Extra High","description":"Maximum reasoning for the hardest tasks.","default":false},{"id":"high","value":"high","label":"High","description":"Thorough reasoning and quality. Recommended.","default":true},{"id":"medium","value":"medium","label":"Medium","description":"Strong quality with a faster turnaround.","default":false},{"id":"low","value":"low","label":"Low","description":"Fastest responses. Best for simple tasks.","default":false}]}},{"modelId":"grok-4.6","name":"Grok 4.6","_meta":{"totalContextTokens":500000,"agentType":"grok-build-plan","supportsReasoningEffort":true,"reasoningEffort":"high","reasoningEfforts":[{"id":"xhigh","value":"xhigh","label":"Extra High","description":"Highest effort and reasoning level","default":false},{"id":"high","value":"high","label":"High","description":"Higher implementation quality with extensive reasoning","default":true},{"id":"medium","value":"medium","label":"Medium","description":"Balanced effort with standard implementation and testing","default":false},{"id":"low","value":"low","label":"Low","description":"Quick, fast implementations","default":false}]}},{"modelId":"grok-4.5","name":"Grok 4.5","_meta":{"totalContextTokens":500000,"agentType":"grok-build-plan","supportsReasoningEffort":true,"reasoningEffort":"high","reasoningEfforts":[{"id":"high","value":"high","label":"High","description":"Highest implementation quality with extensive reasoning","default":true},{"id":"medium","value":"medium","label":"Medium","description":"Balanced effort with standard implementation and testing","default":false},{"id":"low","value":"low","label":"Low","description":"Quick, fast implementations","default":false}]}}]}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":"The"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-8","agentTimestampMs":1790300237347,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentThoughtChunk","chunkId":1}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":" user"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-9","agentTimestampMs":1790300237347,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentThoughtChunk","chunkId":2}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":" wants"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-10","agentTimestampMs":1790300237347,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentThoughtChunk","chunkId":3}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":" me"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-11","agentTimestampMs":1790300237347,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentThoughtChunk","chunkId":4}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":" to"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-12","agentTimestampMs":1790300237347,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentThoughtChunk","chunkId":5}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":" respond"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-13","agentTimestampMs":1790300237347,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentThoughtChunk","chunkId":6}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":" with"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-14","agentTimestampMs":1790300237347,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentThoughtChunk","chunkId":7}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":" exactly"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-15","agentTimestampMs":1790300237347,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentThoughtChunk","chunkId":8}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":":"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-16","agentTimestampMs":1790300237347,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentThoughtChunk","chunkId":9}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":" \""}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-17","agentTimestampMs":1790300237347,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentThoughtChunk","chunkId":10}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":"ste"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-18","agentTimestampMs":1790300237347,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentThoughtChunk","chunkId":11}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":"ering"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-19","agentTimestampMs":1790300237347,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentThoughtChunk","chunkId":12}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":" fixture"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-20","agentTimestampMs":1790300237347,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentThoughtChunk","chunkId":13}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":" observed"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-21","agentTimestampMs":1790300237347,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentThoughtChunk","chunkId":14}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_thought_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_thought_chunk","content":{"type":"text","text":"\"\n"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-22","agentTimestampMs":1790300237347,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentThoughtChunk","chunkId":15}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ste"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-23","agentTimestampMs":1790300237880,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentMessageChunk","chunkId":16}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"ering"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-24","agentTimestampMs":1790300237881,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentMessageChunk","chunkId":17}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" fixture"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-25","agentTimestampMs":1790300237940,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentMessageChunk","chunkId":18}}}}
-{"type":"emit_inbound","label":"notification:session/update:agent_message_chunk","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":" observed"}},"_meta":{"totalTokens":2928,"eventId":"00000000-0000-4000-8000-000000000001-26","agentTimestampMs":1790300237940,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AgentMessageChunk","chunkId":19}}}}
-{"type":"emit_inbound","label":"notification:session/update:available_commands_update","frame":{"kind":"notification","method":"session/update","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"available_commands_update","availableCommands":[{"name":"compact","description":"Compress conversation history to save context window","input":{"hint":"optional context about what to preserve"}},{"name":"always-approve","description":"Toggle always-approve mode (skip all permission prompts)","input":{"hint":"on|off"}},{"name":"context","description":"Show context window usage and session stats","input":null},{"name":"session-info","description":"Show session details (model, turns, context usage)","input":null},{"name":"feedback","description":"Send feedback about the current session","input":{"hint":"feedback text"}},{"name":"deep-research","description":"Research with bounded parallel agents, cross-check evidence, and write a cited report","input":{"hint":""},"_meta":{"workflowSource":"builtin","workflowPath":"/home/grok-replay/.grok/bundled/workflows/deep-research.rhai"}},{"name":"workflow","description":"Launch a saved workflow, list runs, or manage a run (pause, resume, stop, save)","input":{"hint":" [--agent-budget N] [--effort LEVEL] [args] | runs | pause|resume|stop|save [name]"}},{"name":"goal","description":"Set, manage, or check an autonomous goal","input":{"hint":" [--budget ] | status | pause | resume | clear"}},{"name":"loop","description":"Run a prompt on a recurring interval","input":{"hint":"[interval] "}},{"name":"build-with-ai","description":"Build AI apps on SpaceXAI (XAI_API_KEY + api.x.ai)","input":{"hint":""},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/build-with-ai/SKILL.md","bareName":"build-with-ai","qualifiedName":"bundled:build-with-ai"}},{"name":"code-review","description":"Run an extremely strict maintainability review for abstraction quality, giant files, and spaghetti-condition growth. Use for a deep code quality audit or an especially harsh maintainability review.","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/code-review/SKILL.md","bareName":"code-review","qualifiedName":"bundled:code-review"}},{"name":"create-skill","description":"Create a new Grok skill","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/create-skill/SKILL.md","bareName":"create-skill","qualifiedName":"bundled:create-skill"}},{"name":"create-workflow","description":"Author a new multi-agent workflow","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/create-workflow/SKILL.md","bareName":"create-workflow","qualifiedName":"bundled:create-workflow"}},{"name":"execute-plan","description":"Execute a PR Plan DAG from a design document. Parses the plan, topologically sorts it, implements PRs in parallel using worktree-isolated subagents, runs mandatory orchestrator-level review, and assembles either a Graphite PR stack or a plain-git branch stack depending on tool availability.","input":{"hint":" [--effort N] [--concurrency N] [--dry-run] [--resume ] [--instructions \"...\"] [--no-graphite] [--auto-pr]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/execute-plan/SKILL.md","bareName":"execute-plan","qualifiedName":"bundled:execute-plan"}},{"name":"game-assets","description":"Engine-ready game art, 2D sprites to 3D models","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/game-assets/SKILL.md","bareName":"game-assets","qualifiedName":"bundled:game-assets"}},{"name":"bundled:imagine","description":"Prompting and workflow guidance for Imagine image and video tools","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/imagine/SKILL.md","bareName":"imagine","qualifiedName":"bundled:imagine"}},{"name":"implement","description":"Run the full implement-review-fix loop using implementer and reviewer personas. Supports effort-based multi-reviewer scaling (2–7 reviewers) with automatic specialization selection. Includes memory-based feedback loop that learns from past review patterns. Loops until all reviewers find 0 issues of any severity.","input":{"hint":"[--effort N] "},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/implement/SKILL.md","bareName":"implement","qualifiedName":"bundled:implement"}},{"name":"learn","description":"Learn from your own Grok Build sessions and tune your setup: add skills for things you keep typing, fix stale skill instructions, and retire skills, plugins, or MCP servers you never use. Use on \"/learn\", \"learn from my traces\", \"what skills do I never use\". Not for summarizing sessions into notes or memory.","input":{"hint":"[--mode step|auto|report] [--days N | --since-last | --limit N] [--per-trace] [--include-headless] [--cwd PATH] [--focus TEXT] [--resume]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/learn/SKILL.md","bareName":"learn","qualifiedName":"bundled:learn"}},{"name":"long-running-background-tasks","description":"Required reading before you start, watch, or wait on anything that keeps running after you launch it — background jobs, watchers, scheduled loops, CI, pull requests, training runs, dev servers, long builds. Read it before you launch such work and before you report on its state. Saying where a running job stands counts as working on it. Use when: about to launch, supervise, inspect, diagnose, or report on work that keeps running after it is started.","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/long-running-background-tasks/SKILL.md","bareName":"long-running-background-tasks","qualifiedName":"bundled:long-running-background-tasks"}},{"name":"pr-babysit","description":"Monitor PRs, fix CI failures, address review comments (human and bot/automated), resolve merge conflicts, and restack stacks. Supports independent PRs, Graphite stacks, and GitHub stacked PRs (gh-stack). Optional ship lands a ready PR bottom-up when every job has passed and no comments remain open.","input":{"hint":"add [--ship] | remove | list | check | ship [on|off] [number...]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/pr-babysit/SKILL.md","bareName":"pr-babysit","qualifiedName":"bundled:pr-babysit"}},{"name":"resume-claude","description":"Continue from a recent Claude Code session","input":{"hint":"[words describing the session | session id]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/resume-claude/SKILL.md","bareName":"resume-claude","qualifiedName":"bundled:resume-claude"}},{"name":"resume-codex","description":"Continue from a recent Codex session","input":{"hint":"[words describing the session | session id]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/resume-codex/SKILL.md","bareName":"resume-codex","qualifiedName":"bundled:resume-codex"}},{"name":"resume-cursor","description":"Continue from a recent Cursor session","input":{"hint":"[words describing the session | session id]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/resume-cursor/SKILL.md","bareName":"resume-cursor","qualifiedName":"bundled:resume-cursor"}},{"name":"review","description":"Run a reviewer subagent against your whole branch vs main, your uncommitted working state, a named branch, a single GitHub PR, or an entire Graphite/stacked-PR stack (one reviewer subagent per PR). A bare /review defaults to reviewing your uncommitted working state when the tree is dirty, or your whole branch vs main when the tree is clean. Main, local, and branch modes write a review file plus a summary to disk. PR and stack modes post the findings as a PENDING GitHub review for the user to inspect and submit through the UI. Use --main for an explicit clean-tree branch review, --stack for stacked PRs, --submit to publish immediately as COMMENT, or --approve to publish and approve; the default remains PENDING.","input":{"hint":"[--main | --local | --branch | --pr | --stack | ] [--approve | --submit]"},"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/review/SKILL.md","bareName":"review","qualifiedName":"bundled:review"}},{"name":"skill-design-principles","description":"Concise, high-signal principles for writing and editing skills well. Use whenever authoring or editing a skill.","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/skill-design-principles/SKILL.md","bareName":"skill-design-principles","qualifiedName":"bundled:skill-design-principles"}},{"name":"statusline","description":"Configure the Grok Build status line.","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/statusline/SKILL.md","bareName":"statusline","qualifiedName":"bundled:statusline"}},{"name":"threejs-frame-conventions","description":"One frame for Three.js controls, camera, and facing","input":null,"_meta":{"scope":"bundled","path":"/home/grok-replay/.grok/bundled/skills/threejs-frame-conventions/SKILL.md","bareName":"threejs-frame-conventions","qualifiedName":"bundled:threejs-frame-conventions"}},{"name":"learn-traces","description":"Workflow: Map-reduce a collected set of Grok Build sessions into a curated list of harness changes: new or edited skills, plugin enables, deletes","input":{"hint":"[--agent-budget N] [--effort LEVEL] [args]"},"_meta":{"workflowSource":"bundled","workflowPath":"/home/grok-replay/.grok/bundled/workflows/learn-traces.rhai"}}],"_meta":{"tools":["run_terminal_command","read_file","search_replace","list_dir","grep","kill_command_or_subagent","todo_write","get_command_or_subagent_output","spawn_subagent","scheduler_create","scheduler_delete","scheduler_list","monitor","search_tool","use_tool","workflow","enter_plan_mode","exit_plan_mode","ask_user_question","send_feedback","web_search","web_fetch","image_gen","image_edit","image_to_video","reference_to_video","write"]}},"_meta":{"totalTokens":22482,"eventId":"00000000-0000-4000-8000-000000000001-27","agentTimestampMs":1790300238036,"promptId":"t3-xai-prompt-2","streamStartMs":1790300236305,"turnStartMs":1790300235780,"updateType":"AvailableCommandsUpdate","updateParams":{"commandsCount":60}}}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session_notification:response_completed","frame":{"kind":"notification","method":"_x.ai/session_notification","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"response_completed","usage":{"input_tokens":20630,"output_tokens":52,"cache_read_input_tokens":1792,"cache_creation_input_tokens":0,"reasoning_tokens":48},"signature":"cNT0TTliyhDVuYf3s5VZfOfJbZ45UY6L5HBO+Xb/gBknGuT6PJ2VPHHFehMK0YlGQp0HdgesFPfH+4r5wgsonXfDZrP6QRgdIc6uzdd/tZRCuYdQaXYeuQok7T7Zy7IJip8ts9EFpP6IESqUwU/SDd2eUnc/trneRbDUTjoksGYLl3d3YCGz+1sK6fSy00huVXUpqABRPOcMn/gK6qtQVneVkGMYW/wD5Wvxp3OGC/LCwu9PT4WUYQtoPOi5ObKcFdKPj5U9y6HssWazr6kSfqt1ox2f7z5Dq/Iu57NxLuJEDZt99A/1Qo1i3DbUuUet6jEL7JkCcBwsZrBKcAu5d8wk7xV8D6rUNtpXpr0XPCZMUQLFUPL2tg"}}}}
-{"type":"emit_inbound","label":"notification:_x.ai/queue/changed","frame":{"kind":"notification","method":"_x.ai/queue/changed","params":{"sessionId":"00000000-0000-4000-8000-000000000001","entries":[]}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session_notification:turn_completed","frame":{"kind":"notification","method":"_x.ai/session_notification","params":{"sessionId":"00000000-0000-4000-8000-000000000001","update":{"sessionUpdate":"turn_completed","prompt_id":"t3-xai-prompt-2","stop_reason":"end_turn","usage":{"inputTokens":22422,"outputTokens":52,"totalTokens":22474,"cachedReadTokens":1792,"cacheCreationTokens":0,"reasoningTokens":48,"modelCalls":1,"apiDurationMs":2245,"costUsdTicks":144391200,"modelUsage":{"grok-4.7-build":{"inputTokens":22422,"outputTokens":52,"totalTokens":22474,"cachedReadTokens":1792,"cacheCreationTokens":0,"reasoningTokens":48,"modelCalls":1,"apiDurationMs":2245,"costUsdTicks":144391200}},"numTurns":1},"elapsed_ms":2358},"_meta":{"eventId":"00000000-0000-4000-8000-000000000001-30","agentTimestampMs":1790300238089}}}}
-{"type":"emit_inbound","label":"notification:_x.ai/session/prompt_complete","frame":{"kind":"notification","method":"_x.ai/session/prompt_complete","params":{"sessionId":"00000000-0000-4000-8000-000000000001","promptId":"t3-xai-prompt-2","stopReason":"end_turn","agentResult":null}}}
-{"type":"emit_inbound","label":"notification:_x.ai/sessions/changed","frame":{"kind":"notification","method":"_x.ai/sessions/changed","params":{"upserted":[{"sessionId":"00000000-0000-4000-8000-000000000001","title":null,"cwd":"","isWorktree":false,"modelId":"grok-4.7","reasoningEffort":"high","yolo":false,"activity":"idle","resident":true,"lastChangeUnixMs":1790300238090,"origin":{"kind":"local"}}],"removed":[]}}}
-{"type":"emit_inbound","label":"response:session/prompt","frame":{"kind":"response","method":"session/prompt","result":{"stopReason":"end_turn","_meta":{"sessionId":"00000000-0000-4000-8000-000000000001","requestId":"t3-xai-prompt-2","promptId":"t3-xai-prompt-2","totalTokens":22482,"modelId":"grok-4.7","inputTokens":22422,"outputTokens":52,"cachedReadTokens":1792,"reasoningTokens":48,"usage":{"inputTokens":22422,"outputTokens":52,"totalTokens":22474,"cachedReadTokens":1792,"cacheCreationTokens":0,"reasoningTokens":48,"modelCalls":1,"apiDurationMs":2245,"costUsdTicks":144391200,"modelUsage":{"grok-4.7-build":{"inputTokens":22422,"outputTokens":52,"totalTokens":22474,"cachedReadTokens":1792,"cacheCreationTokens":0,"reasoningTokens":48,"modelCalls":1,"apiDurationMs":2245,"costUsdTicks":144391200}},"numTurns":1}}}}}
-{"type":"expect_outbound","label":"request:session/close","frame":{"kind":"request","method":"session/close","params":{"sessionId":"00000000-0000-4000-8000-000000000001"}}}
-{"type":"emit_inbound","label":"notification:_x.ai/sessions/changed","frame":{"kind":"notification","method":"_x.ai/sessions/changed","params":{"upserted":[],"removed":["00000000-0000-4000-8000-000000000001"]}}}
-{"type":"emit_inbound","label":"response:session/close","frame":{"kind":"response","method":"session/close","result":{"_meta":{"x.ai/closeOutcome":"closed"}}}}
-{"type":"runtime_exit","status":"success"}
diff --git a/packages/provider-grok/src/server/adapter.ts b/packages/provider-grok/src/server/adapter.ts
index 7340ad213bed1..0e8d9b6e04714 100644
--- a/packages/provider-grok/src/server/adapter.ts
+++ b/packages/provider-grok/src/server/adapter.ts
@@ -117,6 +117,8 @@ const GrokProviderCapabilitiesV2 = {
...AcpProviderCapabilitiesV2.checkpointing,
providerCanReadConversationSnapshot: true,
},
+ // Cancelling a Grok prompt kills its running foreground tool.
+ turns: { ...AcpProviderCapabilitiesV2.turns, supportsSteeringByInterruptRestart: false },
} satisfies OrchestrationV2ProviderCapabilities;
export interface GrokAdapterV2Options {
From b5379160993f49edbc4458e091356da3bcd47c4c Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 13 Nov 2026 15:13:38 -0800
Subject: [PATCH 005/690] fix(server): thread list builds metadata only for the
page it returns (#36434)
---
.../src/mcp/OrchestratorMcpService.test.ts | 24 +++++
apps/server/src/mcp/OrchestratorMcpService.ts | 51 ++++++++---
apps/server/src/mcp/toolkits/core.test.ts | 1 +
.../src/orchestration-v2/Orchestrator.ts | 10 ++
.../src/orchestration-v2/ProjectionStore.ts | 91 +++++++++++++++++--
.../ProviderTurnControlService.test.ts | 1 +
.../ThreadManagementService.ts | 53 +++++------
apps/server/src/peer/PeerForwarding.test.ts | 1 +
8 files changed, 181 insertions(+), 51 deletions(-)
diff --git a/apps/server/src/mcp/OrchestratorMcpService.test.ts b/apps/server/src/mcp/OrchestratorMcpService.test.ts
index 03810fbefe599..a017590b1f30a 100644
--- a/apps/server/src/mcp/OrchestratorMcpService.test.ts
+++ b/apps/server/src/mcp/OrchestratorMcpService.test.ts
@@ -3975,6 +3975,7 @@ describe("OrchestratorMcpService thread list", () => {
],
},
];
+ const shellRequests: Array> = [];
const service = layerOrchestratorService.pipe(
Layer.provide(
Layer.mergeAll(
@@ -3987,6 +3988,13 @@ describe("OrchestratorMcpService thread list", () => {
(thread) => input.projectId === undefined || thread.projectId === input.projectId,
),
),
+ getShellSnapshot: (options) => {
+ const threadIds = options?.threadIds ?? shells.map((thread) => thread.id);
+ shellRequests.push(threadIds);
+ return Effect.succeed({
+ threads: shells.filter((thread) => threadIds.includes(thread.id)),
+ } as never);
+ },
}),
ServerSettings.layerTest(),
Layer.mock(ProviderRegistry.ProviderRegistry)({ getProviders: Effect.succeed([]) }),
@@ -4080,6 +4088,22 @@ describe("OrchestratorMcpService thread list", () => {
}).pipe(Effect.provide(service)),
);
+ it.effect("builds shells only for the threads on the page", () =>
+ Effect.gen(function* () {
+ const mcp = yield* OrchestratorMcpService.OrchestratorMcpService;
+ shellRequests.length = 0;
+ const listed = yield* mcp.listThreads(client, {
+ projectId: ProjectId.make("project:a"),
+ limit: 1,
+ });
+ assert.deepStrictEqual(
+ [listed.threads.map((thread) => thread.threadId), listed.total, listed.nextCursor],
+ [["thread:a-snoozed"], 3, 1],
+ );
+ assert.deepStrictEqual(shellRequests, [[ThreadId.make("thread:a-snoozed")]]);
+ }).pipe(Effect.provide(service)),
+ );
+
it.effect("points a caller outside any thread at allProjects when it omits projectId", () =>
Effect.gen(function* () {
const mcp = yield* OrchestratorMcpService.OrchestratorMcpService;
diff --git a/apps/server/src/mcp/OrchestratorMcpService.ts b/apps/server/src/mcp/OrchestratorMcpService.ts
index 2e5877e9ab8ed..fc741bcac6ab5 100644
--- a/apps/server/src/mcp/OrchestratorMcpService.ts
+++ b/apps/server/src/mcp/OrchestratorMcpService.ts
@@ -3104,16 +3104,14 @@ const make = Effect.gen(function* () {
if (!input.allProjects && input.projectId !== undefined) {
yield* requireProject(input.projectId);
}
+ const listFailure = (error: unknown) =>
+ failure("orchestration_error", `Unable to list threads: ${errorMessage(error)}`);
const projectThreads = yield* threadManagement
.listProjectThreads({
projectId: projectId ?? undefined,
includeSubagents: input.includeSubagents !== false,
})
- .pipe(
- Effect.mapError((error) =>
- failure("orchestration_error", `Unable to list threads: ${errorMessage(error)}`),
- ),
- );
+ .pipe(Effect.mapError(listFailure));
const nowMs = yield* Clock.currentTimeMillis;
// An empty list is no filter: clients that fill every optional field send `[]`.
const statuses =
@@ -3121,29 +3119,58 @@ const make = Effect.gen(function* () {
? null
: new Set(input.statuses);
const titleContains = input.titleContains?.toLocaleLowerCase();
- const filtered = projectThreads
- .filter((thread) => statuses === null || statuses.has(listedThreadStatus(thread)))
+ const candidates = projectThreads
.filter(
(thread) =>
input.settled === undefined || threadSettlement(thread).settled === input.settled,
)
.filter(
- (thread) => input.snoozed === undefined || isSnoozed(thread, nowMs) === input.snoozed,
+ (thread) =>
+ titleContains === undefined || thread.title.toLocaleLowerCase().includes(titleContains),
)
+ // Only a snooze that ends later can hold, so the rest skip building a shell.
.filter(
(thread) =>
- titleContains === undefined || thread.title.toLocaleLowerCase().includes(titleContains),
+ input.snoozed !== true ||
+ (thread.snoozedUntil != null && DateTime.toEpochMillis(thread.snoozedUntil) > nowMs),
);
+ // Shells are the costly part, so build them only for the threads the page needs.
+ const shellsOf = (threads: ReadonlyArray<{ readonly id: ThreadId }>) =>
+ threads.length === 0
+ ? Effect.succeed([])
+ : threadManagement
+ .getShellSnapshot({ location: "active", threadIds: threads.map(({ id }) => id) })
+ .pipe(
+ Effect.map((snapshot) => {
+ const shellsById = new Map(snapshot.threads.map((shell) => [shell.id, shell]));
+ return threads.flatMap(({ id }) => shellsById.get(id) ?? []);
+ }),
+ Effect.mapError(listFailure),
+ );
const cursor = input.cursor ?? 0;
const limit = input.limit ?? DEFAULT_THREAD_LIST_LIMIT;
- const page = filtered.slice(cursor, cursor + limit);
- const nextCursor = cursor + page.length < filtered.length ? cursor + page.length : null;
+ let page: ReadonlyArray;
+ let total: number;
+ if (statuses === null && input.snoozed === undefined) {
+ total = candidates.length;
+ page = yield* shellsOf(candidates.slice(cursor, cursor + limit));
+ } else {
+ // Status and snooze state are derived from the shell.
+ const filtered = (yield* shellsOf(candidates))
+ .filter((thread) => statuses === null || statuses.has(listedThreadStatus(thread)))
+ .filter(
+ (thread) => input.snoozed === undefined || isSnoozed(thread, nowMs) === input.snoozed,
+ );
+ total = filtered.length;
+ page = filtered.slice(cursor, cursor + limit);
+ }
+ const nextCursor = cursor + limit < total ? cursor + limit : null;
return {
projectId,
currentThreadId: parent?.thread.id ?? null,
threads: page.map((shell) => listItemFromShell(scope, shell, nowMs)),
nextCursor,
- total: filtered.length,
+ total,
} satisfies OrchestratorMcpThreadListResult;
}),
readThread: Effect.fn("OrchestratorMcpService.readThread")(function* (scope, input) {
diff --git a/apps/server/src/mcp/toolkits/core.test.ts b/apps/server/src/mcp/toolkits/core.test.ts
index b2aaed70322cf..35a18f5f0b460 100644
--- a/apps/server/src/mcp/toolkits/core.test.ts
+++ b/apps/server/src/mcp/toolkits/core.test.ts
@@ -1328,6 +1328,7 @@ it.effect(
Layer.provide(
Layer.mock(ThreadManagement.ThreadManagementService)({
listProjectThreads: () => Effect.succeed([unsettled, settled]),
+ getShellSnapshot: () => Effect.succeed({ threads: [unsettled, settled] } as never),
}),
),
Layer.provide(ServerSettings.layerTest()),
diff --git a/apps/server/src/orchestration-v2/Orchestrator.ts b/apps/server/src/orchestration-v2/Orchestrator.ts
index 398a4c29c0989..c732e0ca67ccf 100644
--- a/apps/server/src/orchestration-v2/Orchestrator.ts
+++ b/apps/server/src/orchestration-v2/Orchestrator.ts
@@ -4,6 +4,7 @@ import type {
OrchestrationV2SearchThreadInput,
OrchestrationV2SearchThreadResult,
OrchestrationV2ThreadHistoryPage,
+ ProjectId,
} from "@t3tools/contracts";
import {
latestExecutedRun,
@@ -173,6 +174,7 @@ import {
type ProjectionCheckpointContext,
type OlderSettledThreadsListing,
type ActiveShellIndexEntry,
+ type ActiveThreadListEntry,
type OlderSettledThreadsQuery,
type ShellSnapshotOptions,
} from "./ProjectionStore.ts";
@@ -504,6 +506,9 @@ export interface OrchestratorV2Shape {
ReadonlyArray,
OrchestratorV2Error
>;
+ readonly listActiveThreads: (
+ projectId?: ProjectId,
+ ) => Effect.Effect, OrchestratorV2Error>;
readonly listOlderSettledThreads: (
query: OlderSettledThreadsQuery,
) => Effect.Effect;
@@ -16040,6 +16045,11 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
listActiveShellIndex: Effect.fn("OrchestratorV2.listActiveShellIndex")(function () {
return projectionStore.listActiveShellIndex().pipe(Effect.mapError(shellProjectionError));
}),
+ listActiveThreads: Effect.fn("OrchestratorV2.listActiveThreads")(function (projectId) {
+ return projectionStore
+ .listActiveThreads(projectId)
+ .pipe(Effect.mapError(shellProjectionError));
+ }),
listOlderSettledThreads: Effect.fn("OrchestratorV2.listOlderSettledThreads")(function (query) {
return projectionStore
.listOlderSettledThreads(query)
diff --git a/apps/server/src/orchestration-v2/ProjectionStore.ts b/apps/server/src/orchestration-v2/ProjectionStore.ts
index 78422b56535af..e1306f58273e6 100644
--- a/apps/server/src/orchestration-v2/ProjectionStore.ts
+++ b/apps/server/src/orchestration-v2/ProjectionStore.ts
@@ -467,6 +467,19 @@ export interface ActiveShellIndexEntry extends Pick<
readonly hasRemoteTasks: boolean;
}
+/** What thread listings filter and order an active thread by, without building its shell. */
+export type ActiveThreadListEntry = Pick<
+ OrchestrationV2ThreadShell,
+ | "id"
+ | "projectId"
+ | "title"
+ | "lineage"
+ | "settledOverride"
+ | "settledAt"
+ | "snoozedUntil"
+ | "updatedAt"
+>;
+
export interface OlderSettledThreadsQuery {
readonly before: OrchestrationV2OlderSettledCursor;
readonly projectIds?: ReadonlyArray | undefined;
@@ -567,6 +580,10 @@ export interface ProjectionStoreV2Shape {
ReadonlyArray,
ProjectionStoreV2Error
>;
+ /** Every active thread, or one project's. */
+ readonly listActiveThreads: (
+ projectId?: ProjectId,
+ ) => Effect.Effect, ProjectionStoreV2Error>;
/** Older settled thread ids below a cursor, newest settle first, and per-project counts. */
readonly listOlderSettledThreads: (
query: OlderSettledThreadsQuery,
@@ -1150,6 +1167,21 @@ const encodeTimelineSources = Schema.encodeEffect(
Schema.Array(Schema.Struct({ threadId: Schema.String, id: Schema.NullOr(Schema.String) })),
),
);
+const shellIndexLineage = (row: {
+ readonly relationship_to_parent: string | null;
+ readonly parent_thread_id: string | null;
+ readonly root_thread_id: string;
+ readonly side_chat: number | null;
+}): OrchestrationV2ThreadShell["lineage"] => ({
+ parentThreadId: row.parent_thread_id === null ? null : ThreadId.make(row.parent_thread_id),
+ relationshipToParent:
+ row.relationship_to_parent === "fork" || row.relationship_to_parent === "subagent"
+ ? row.relationship_to_parent
+ : null,
+ rootThreadId: ThreadId.make(row.root_thread_id),
+ ...(row.side_chat === null ? {} : { sideChat: row.side_chat === 1 }),
+});
+
const encodeIdList = Schema.encodeSync(Schema.fromJsonString(Schema.Array(Schema.String)));
// Empty filters still scan SQLite rows; short-circuit them, including turnItemTypes: [].
@@ -8770,16 +8802,7 @@ export const layer: Layer.Layer =
return rows.map((row): ActiveShellIndexEntry => ({
id: ThreadId.make(row.thread_id),
projectId: ProjectId.make(row.project_id),
- lineage: {
- parentThreadId:
- row.parent_thread_id === null ? null : ThreadId.make(row.parent_thread_id),
- relationshipToParent:
- row.relationship_to_parent === "fork" || row.relationship_to_parent === "subagent"
- ? row.relationship_to_parent
- : null,
- rootThreadId: ThreadId.make(row.root_thread_id),
- ...(row.side_chat === null ? {} : { sideChat: row.side_chat === 1 }),
- },
+ lineage: shellIndexLineage(row),
settledOverride:
row.settled_override === "settled" || row.settled_override === "active"
? row.settled_override
@@ -8792,6 +8815,53 @@ export const layer: Layer.Layer =
}));
}, Effect.mapError(shellSnapshotReadError));
+ const listActiveThreads = Effect.fn("ProjectionStore.listActiveThreads")(function* (
+ projectId?: ProjectId,
+ ) {
+ const rows = yield* sql<{
+ readonly thread_id: string;
+ readonly project_id: string;
+ readonly title: string;
+ readonly updated_at: string;
+ readonly relationship_to_parent: string | null;
+ readonly parent_thread_id: string | null;
+ readonly root_thread_id: string;
+ readonly side_chat: number | null;
+ readonly settled_override: string | null;
+ readonly settled_at: string | null;
+ readonly snoozed_until: string | null;
+ }>`
+ SELECT
+ t.thread_id,
+ t.project_id,
+ t.title,
+ t.updated_at,
+ json_extract(t.payload_json, '$.lineage.relationshipToParent') AS relationship_to_parent,
+ json_extract(t.payload_json, '$.lineage.parentThreadId') AS parent_thread_id,
+ json_extract(t.payload_json, '$.lineage.rootThreadId') AS root_thread_id,
+ json_extract(t.payload_json, '$.lineage.sideChat') AS side_chat,
+ json_extract(t.payload_json, '$.settledOverride') AS settled_override,
+ json_extract(t.payload_json, '$.settledAt') AS settled_at,
+ json_extract(t.payload_json, '$.snoozedUntil') AS snoozed_until
+ FROM orchestration_v2_projection_threads t
+ WHERE t.deleted_at IS NULL
+ AND t.archived_at IS NULL${projectId === undefined ? sql`` : sql` AND t.project_id = ${projectId}`}
+ `;
+ return rows.map((row): ActiveThreadListEntry => ({
+ id: ThreadId.make(row.thread_id),
+ projectId: ProjectId.make(row.project_id),
+ title: row.title,
+ lineage: shellIndexLineage(row),
+ settledOverride:
+ row.settled_override === "settled" || row.settled_override === "active"
+ ? row.settled_override
+ : null,
+ settledAt: row.settled_at === null ? null : DateTime.makeUnsafe(row.settled_at),
+ snoozedUntil: row.snoozed_until === null ? null : DateTime.makeUnsafe(row.snoozed_until),
+ updatedAt: DateTime.makeUnsafe(row.updated_at),
+ }));
+ }, Effect.mapError(shellSnapshotReadError));
+
const getThreadLinkMetadata = Effect.fn("ProjectionStore.getThreadLinkMetadata")(function* (
threadIds: ReadonlyArray,
) {
@@ -8981,6 +9051,7 @@ export const layer: Layer.Layer =
listActiveSubagentThreadIds: readOnReader(listActiveSubagentThreadIds),
countThreads: readOnReader(countThreads),
listActiveShellIndex: readOnReader(listActiveShellIndex),
+ listActiveThreads: readOnReader(listActiveThreads),
readShellSnapshot: readOnReader(readShellSnapshot),
listOlderSettledThreads: readOnReader(listOlderSettledThreads),
getThreadShell: readOnReader(getThreadShell),
diff --git a/apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts b/apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts
index 3ede421ad72ab..c3d1096cb3c93 100644
--- a/apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts
+++ b/apps/server/src/orchestration-v2/ProviderTurnControlService.test.ts
@@ -218,6 +218,7 @@ it.effect(
listActiveSubagentThreadIds: () => Effect.die("unused listActiveSubagentThreadIds"),
countThreads: () => Effect.die("unused countThreads"),
listActiveShellIndex: () => Effect.die("unused listActiveShellIndex"),
+ listActiveThreads: () => Effect.die("unused listActiveThreads"),
listOlderSettledThreads: () => Effect.die("unused listOlderSettledThreads"),
readShellSnapshot: () => Effect.die("unused readShellSnapshot"),
getThreadShell: () => Effect.die("unused getThreadShell"),
diff --git a/apps/server/src/orchestration-v2/ThreadManagementService.ts b/apps/server/src/orchestration-v2/ThreadManagementService.ts
index 7788baca062fd..482674b772d43 100644
--- a/apps/server/src/orchestration-v2/ThreadManagementService.ts
+++ b/apps/server/src/orchestration-v2/ThreadManagementService.ts
@@ -3,6 +3,7 @@ import type {
OrchestrationV2SearchThreadResult,
} from "@t3tools/contracts";
import type {
+ ActiveThreadListEntry,
ProjectionRecordField,
ProjectionRecordFilter,
ProjectionRecords,
@@ -30,7 +31,6 @@ import {
type OrchestrationV2ThreadShellSnapshot,
type OrchestrationV2ThreadProjection,
type OrchestrationV2ThreadTranscript,
- type OrchestrationV2ThreadShell,
type OrchestrationV2TurnItem,
ProjectId,
PROVIDER_SEND_TURN_MAX_FILE_BYTES,
@@ -451,7 +451,7 @@ export interface ThreadManagementServiceShape {
readonly listProjectThreads: (input: {
readonly projectId?: ProjectId | undefined;
readonly includeSubagents: boolean;
- }) => Effect.Effect, ThreadManagementError>;
+ }) => Effect.Effect, ThreadManagementError>;
readonly sendToThread: (
input: ThreadManagementSendInput,
) => Effect.Effect;
@@ -835,33 +835,28 @@ const make = Effect.gen(function* () {
);
const listProjectThreads: ThreadManagementServiceShape["listProjectThreads"] = (input) =>
- orchestrator
- .getShellSnapshot({
- location: "active",
- ...(input.projectId === undefined ? {} : { projectId: input.projectId }),
- })
- .pipe(
- Effect.mapError(
- (cause) =>
- new ThreadManagementProjectThreadsListError({
- projectId: input.projectId,
- cause,
- }),
- ),
- Effect.map((snapshot) =>
- snapshot.threads
- .filter((thread) =>
- input.includeSubagents
- ? thread.lineage.sideChat !== true
- : isListedThreadLineage(thread.lineage),
- )
- .toSorted(
- (left, right) =>
- DateTime.toEpochMillis(right.updatedAt) - DateTime.toEpochMillis(left.updatedAt) ||
- right.id.localeCompare(left.id),
- ),
- ),
- );
+ orchestrator.listActiveThreads(input.projectId).pipe(
+ Effect.mapError(
+ (cause) =>
+ new ThreadManagementProjectThreadsListError({
+ projectId: input.projectId,
+ cause,
+ }),
+ ),
+ Effect.map((threads) =>
+ threads
+ .filter((thread) =>
+ input.includeSubagents
+ ? thread.lineage.sideChat !== true
+ : isListedThreadLineage(thread.lineage),
+ )
+ .toSorted(
+ (left, right) =>
+ DateTime.toEpochMillis(right.updatedAt) - DateTime.toEpochMillis(left.updatedAt) ||
+ right.id.localeCompare(left.id),
+ ),
+ ),
+ );
const sendToThread: ThreadManagementServiceShape["sendToThread"] = Effect.fn(
"ThreadManagementService.sendToThread",
diff --git a/apps/server/src/peer/PeerForwarding.test.ts b/apps/server/src/peer/PeerForwarding.test.ts
index 3c0e2652ea48b..84fe35c6f21f8 100644
--- a/apps/server/src/peer/PeerForwarding.test.ts
+++ b/apps/server/src/peer/PeerForwarding.test.ts
@@ -95,6 +95,7 @@ const boxThreads = (seen: Ref.Ref, linkSession: Ref.Ref) => {
getProjectThreadRecords: (input) => projectionOf(input.threadId),
getThreadRecords: (threadId) => projectionOf(threadId),
listProjectThreads: () => linked.pipe(Effect.map((shell) => [shell] as never)),
+ getShellSnapshot: () => linked.pipe(Effect.map((shell) => ({ threads: [shell] }) as never)),
sendToThread: (input) =>
Ref.update(seen, (current) => ({ ...current, sends: [...current.sends, input] })).pipe(
Effect.as({ run: run("running"), delivery: "started" } as never),
From 3c1be119b9c55e0c9d7406cf1601573bf8200c0f Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 13 Nov 2026 15:15:57 -0800
Subject: [PATCH 006/690] fix(web): middle-clicking empty chat space on Linux
no longer pastes into the composer (#36436)
---
apps/web/src/components/ChatView.tsx | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
index 754edc3912db5..693030ae23c33 100644
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -123,7 +123,15 @@ import { orchestrationV2WorktreePending } from "@t3tools/shared/orchestrationV2T
import { resolveThreadReferenceCopyTarget } from "@t3tools/client-runtime/thread-reference";
import { resolveTerminalSessionLabel } from "@t3tools/shared/terminalLabels";
import { useAtomValue } from "@effect/atom-react";
-import { type SetStateAction, use, useEffect, useLayoutEffect, useRef, useState } from "react";
+import {
+ type MouseEvent as ReactMouseEvent,
+ type SetStateAction,
+ use,
+ useEffect,
+ useLayoutEffect,
+ useRef,
+ useState,
+} from "react";
import { BackgroundThreadContext } from "./chat/backgroundThread";
import { WorkspaceMutationIdContext } from "./chat/ChatMarkdownMedia";
import { useLocation, useNavigate } from "@tanstack/react-router";
@@ -3318,6 +3326,7 @@ export default function ChatView(props: ChatViewProps) {
data-chat-column-maximized-away={rightPanelMaximized ? "true" : "false"}
data-chat-column
inert={rightPanelMaximized}
+ onMouseUp={preventMiddleClickPasteOutsideEditable}
>
{embedded ? null : (
Date: Fri, 13 Nov 2026 15:18:57 -0800
Subject: [PATCH 007/690] fix(web): closing Settings no longer leaves it as a
back/forward stop (#36437)
---
.../sidebar/mainAppLocation.browser.test.tsx | 21 +++++++++++++++++++
.../src/components/sidebar/mainAppLocation.ts | 21 +++++++++++++++++--
2 files changed, 40 insertions(+), 2 deletions(-)
diff --git a/apps/web/src/components/sidebar/mainAppLocation.browser.test.tsx b/apps/web/src/components/sidebar/mainAppLocation.browser.test.tsx
index 4c5195534582f..24fb13f0f9acf 100644
--- a/apps/web/src/components/sidebar/mainAppLocation.browser.test.tsx
+++ b/apps/web/src/components/sidebar/mainAppLocation.browser.test.tsx
@@ -74,4 +74,25 @@ describe("main app return location", () => {
sidebarBack: THREAD_PATH,
});
});
+
+ it("leaves no stop on Settings for history back after it closes", async () => {
+ const otherThreadPath = "/environment-one/thread-two";
+ await using screen = await render(, { router: THREAD_PATH });
+ const router = screen.router!;
+ using _tracking = { [Symbol.dispose]: startMainAppLocationTracking(router) };
+
+ await settled(router, router.navigate({ href: otherThreadPath }));
+ await settled(router, router.navigate({ href: "/settings/general" }));
+ await userEvent.click(screen.getByRole("button", { name: "Close modal" }));
+ await vi.waitFor(() => expect(router.state.location.pathname).toBe(otherThreadPath));
+
+ router.history.forward();
+ await vi.waitFor(() => expect(router.state.location.pathname).toBe(otherThreadPath));
+ router.history.back();
+ await vi.waitFor(() => expect(router.state.location.pathname).toBe(THREAD_PATH));
+ router.history.forward();
+ await vi.waitFor(() => expect(router.state.location.pathname).toBe(otherThreadPath));
+ router.history.forward();
+ await vi.waitFor(() => expect(router.state.location.pathname).toBe(otherThreadPath));
+ });
});
diff --git a/apps/web/src/components/sidebar/mainAppLocation.ts b/apps/web/src/components/sidebar/mainAppLocation.ts
index 049ecf3035b42..7cc8db53a9189 100644
--- a/apps/web/src/components/sidebar/mainAppLocation.ts
+++ b/apps/web/src/components/sidebar/mainAppLocation.ts
@@ -1,4 +1,4 @@
-import { useNavigate, type AnyRouter } from "@tanstack/react-router";
+import { useNavigate, useRouter, type AnyRouter } from "@tanstack/react-router";
import { GUEST_PAGE_PATH } from "../../guestPage";
import { withoutAssistantCitationHash } from "../../lib/assistantCitationNavigation";
@@ -20,6 +20,7 @@ const SHELL_LESS_PATHS = new Set(["/pair", "/connect", "/connect-agent", GUEST_P
let mainAppHref: string | null = null;
let appModalReturnHref: string | null = null;
+let appModalReturnHistoryIndex: number | null = null;
let appModalReturnThreadTarget: ThreadRouteTarget | null = null;
/** Remembers where Settings, Usage, and the utility pages lead back to. */
@@ -29,6 +30,9 @@ export function startMainAppLocationTracking(router: AnyRouter): () => void {
if (isAppModalPage(pathname) || SHELL_LESS_PATHS.has(pathname)) return;
const href = withoutAssistantCitationHash(router.state.location.href);
appModalReturnHref = href;
+ // Only an entry that is exactly the return location can be stepped back to.
+ appModalReturnHistoryIndex =
+ href === router.state.location.href ? router.state.location.state.__TSR_index : null;
appModalReturnThreadTarget = resolveThreadRouteTarget(
router.state.matches.at(-1)?.params ?? {},
);
@@ -51,5 +55,18 @@ export function useNavigateToMainApp() {
/** Leaves Settings or Usage for the page that opened it. */
export function useCloseAppModal() {
const navigate = useNavigate();
- return () => navigate({ href: appModalReturnHref ?? mainAppHref ?? "/" });
+ const router = useRouter();
+ return () => {
+ const href = appModalReturnHref ?? mainAppHref ?? "/";
+ const returnIndex = appModalReturnHistoryIndex ?? 0;
+ const steps = router.state.location.state.__TSR_index - returnIndex;
+ if (returnIndex === 0 || steps <= 0) return navigate({ href });
+ // Go back to the entry before the return page and push it again: only a push drops the
+ // modal's entries from forward history, and reusing the old entry avoids a duplicate stop.
+ const unsubscribe = router.history.subscribe(() => {
+ unsubscribe();
+ void navigate({ href });
+ });
+ router.history.go(-(steps + 1));
+ };
}
From eb14ddb79924a6cbcb6c26d1b97c9816c9732c55 Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 13 Nov 2026 15:36:52 -0800
Subject: [PATCH 008/690] fix(opencode): OpenCode 1.x threads report
context-window usage (#36430)
---
.../src/server/OpenCodeRuntime.ts | 49 ++++++++++++-------
.../src/server/adapter.test.ts | 17 +++++++
.../provider-opencode/src/server/adapter.ts | 40 +++++++++++++++
3 files changed, 88 insertions(+), 18 deletions(-)
diff --git a/packages/provider-opencode/src/server/OpenCodeRuntime.ts b/packages/provider-opencode/src/server/OpenCodeRuntime.ts
index 88ff243eccd47..d3e791bce9088 100644
--- a/packages/provider-opencode/src/server/OpenCodeRuntime.ts
+++ b/packages/provider-opencode/src/server/OpenCodeRuntime.ts
@@ -267,24 +267,23 @@ export interface ParsedOpenCodeModelSlug {
readonly modelID: string;
}
-export type OpenCodeVariantCache = Map | undefined>;
+export interface OpenCodeCatalogModel {
+ readonly variants: ReadonlySet;
+ readonly contextWindow: number | undefined;
+}
-export const resolveOpenCodeVariant = Effect.fnUntraced(function* (
+export type OpenCodeVariantCache = Map;
+
+/** The model's catalog entry; a failed read returns undefined and is not cached. */
+export const readOpenCodeCatalogModel = Effect.fnUntraced(function* (
client: OpencodeClient,
- model: ParsedOpenCodeModelSlug | undefined,
- variant: string | undefined,
+ model: ParsedOpenCodeModelSlug,
abortSignal?: AbortSignal,
cache?: OpenCodeVariantCache,
) {
- if (!variant || !model) return variant;
-
const key = `${model.providerID}/${model.modelID}`;
- const cached = cache?.get(key);
- if (cache?.has(key)) {
- return cached !== undefined && !cached.has(variant) ? undefined : variant;
- }
+ if (cache?.has(key)) return cache.get(key);
- // Only a confirmed catalog mismatch can invalidate a persisted selection.
const response = yield* runOpenCodeSdk("provider.list", (signal) =>
client.provider.list(undefined, {
signal: abortSignal ? AbortSignal.any([signal, abortSignal]) : signal,
@@ -293,16 +292,30 @@ export const resolveOpenCodeVariant = Effect.fnUntraced(function* (
Effect.timeout("10 seconds"),
Effect.orElseSucceed(() => undefined),
);
- if (response?.data === undefined) return variant;
+ if (response?.data === undefined) return undefined;
const catalogModel = response.data.all.find((provider) => provider.id === model.providerID)
?.models[model.modelID];
- const variants = catalogModel ? new Set(Object.keys(catalogModel.variants ?? {})) : undefined;
- if (cache !== undefined) {
- // Keep only this model's names; provider.list can return several megabytes.
- cache.set(key, variants);
- }
- return variants !== undefined && !variants.has(variant) ? undefined : variant;
+ const entry: OpenCodeCatalogModel | undefined = catalogModel && {
+ variants: new Set(Object.keys(catalogModel.variants ?? {})),
+ contextWindow: catalogModel.limit?.input ?? catalogModel.limit?.context,
+ };
+ // Keep only what this model needs; provider.list can return several megabytes.
+ cache?.set(key, entry);
+ return entry;
+});
+
+export const resolveOpenCodeVariant = Effect.fnUntraced(function* (
+ client: OpencodeClient,
+ model: ParsedOpenCodeModelSlug | undefined,
+ variant: string | undefined,
+ abortSignal?: AbortSignal,
+ cache?: OpenCodeVariantCache,
+) {
+ if (!variant || !model) return variant;
+ // Only a confirmed catalog mismatch can invalidate a persisted selection.
+ const entry = yield* readOpenCodeCatalogModel(client, model, abortSignal, cache);
+ return entry !== undefined && !entry.variants.has(variant) ? undefined : variant;
});
export interface OpenCodeSkill {
diff --git a/packages/provider-opencode/src/server/adapter.test.ts b/packages/provider-opencode/src/server/adapter.test.ts
index 47cedeefc1c9e..c1c89b2b96263 100644
--- a/packages/provider-opencode/src/server/adapter.test.ts
+++ b/packages/provider-opencode/src/server/adapter.test.ts
@@ -775,6 +775,18 @@ describe("OpenCodeAdapterV2", () => {
let promptId = "";
const harness = yield* makeOpenCodeRuntimeHarness(`usage-${ending}`, "root", {
event: { subscribe: async () => ({ stream: nativeEvents.stream }) },
+ provider: {
+ list: async () => ({
+ data: {
+ all: [
+ {
+ id: "anthropic",
+ models: { "claude-sonnet": { limit: { context: 200_000, output: 8_000 } } },
+ },
+ ],
+ },
+ }),
+ },
session: {
create: async () => ({ data: { id: "root", time: { created: 1, updated: 1 } } }),
promptAsync: async (input: { messageID: string }) => {
@@ -895,6 +907,11 @@ describe("OpenCodeAdapterV2", () => {
hasSubagents: false,
},
);
+ // The thread's context window reads tokenUsage: the last step's context fill.
+ if (ending === "completed") {
+ assert.equal(completed?.providerTurn.tokenUsage?.usedTokens, 22);
+ assert.equal(completed?.providerTurn.tokenUsage?.maxTokens, 200_000);
+ }
}).pipe(Effect.provide(Layer.merge(IdAllocator.layer, McpProviderSessions.layer))),
);
diff --git a/packages/provider-opencode/src/server/adapter.ts b/packages/provider-opencode/src/server/adapter.ts
index b017a8861f408..4d265ab9dc460 100644
--- a/packages/provider-opencode/src/server/adapter.ts
+++ b/packages/provider-opencode/src/server/adapter.ts
@@ -226,6 +226,7 @@ interface OpenCodeTurnTokenUsageAccumulator {
reasoningTokens: number;
hasSubagents: boolean;
complete: boolean;
+ lastStep: OpenCodeStepUsage | null;
}
function makeOpenCodeTurnTokenUsageAccumulator(): OpenCodeTurnTokenUsageAccumulator {
@@ -241,6 +242,7 @@ function makeOpenCodeTurnTokenUsageAccumulator(): OpenCodeTurnTokenUsageAccumula
reasoningTokens: 0,
hasSubagents: false,
complete: true,
+ lastStep: null,
};
}
@@ -255,6 +257,29 @@ function accumulateOpenCodeStepUsage(
accumulator.cacheCreationTokens += part.tokens.cache.write;
accumulator.outputTokens += part.tokens.output + part.tokens.reasoning;
accumulator.reasoningTokens += part.tokens.reasoning;
+ accumulator.lastStep = part;
+}
+
+// The last step's prompt plus output is the context the next request starts from.
+function openCodeContextUsage(
+ lastStep: OpenCodeStepUsage | null,
+ contextWindow: number | undefined,
+ completedAt: DateTime.Utc,
+): Pick {
+ if (lastStep === null) return {};
+ const { tokens } = lastStep;
+ const inputTokens = tokens.input + tokens.cache.read + tokens.cache.write;
+ return {
+ tokenUsage: {
+ usedTokens: inputTokens + tokens.output,
+ maxTokens: contextWindow ?? null,
+ inputTokens,
+ cachedInputTokens: tokens.cache.read,
+ outputTokens: tokens.output,
+ reasoningOutputTokens: tokens.reasoning,
+ updatedAt: DateTime.formatIso(completedAt),
+ },
+ };
}
interface ActiveOpenCodeTurn {
@@ -1179,6 +1204,11 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function
reasoningTokens: turn.usage.reasoningTokens,
hasSubagents: turn.usage.hasSubagents,
},
+ ...openCodeContextUsage(
+ turn.usage.lastStep,
+ variantCache.get(turn.modelSelection.model.trim())?.contextWindow,
+ completedAt,
+ ),
}),
};
Object.assign(turn.providerTurn, providerTurn);
@@ -2094,6 +2124,16 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function
yield* resolveRuntimeRequest(pending.nativeRequestId, "cancelled");
}
}
+ // Prompts read the catalog only for variants; a completed turn needs its context window.
+ const parsedModel = OpenCodeRuntime.parseOpenCodeModelSlug(turn.modelSelection.model);
+ if (status === "completed" && turn.usage.lastStep !== null && parsedModel !== null) {
+ yield* OpenCodeRuntime.readOpenCodeCatalogModel(
+ client,
+ parsedModel,
+ undefined,
+ variantCache,
+ );
+ }
yield* emitProviderTurn(state, turn, status, completedAt);
const threadDisposition = terminal?.threadDisposition ?? "reusable";
yield* updateProviderThread(state, {
From 2d689687f57acafe1b85cbc9d82583ea66d2d0fc Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 13 Nov 2026 15:44:35 -0800
Subject: [PATCH 009/690] fix(web): bare @ in the composer lists workspace
files (#36435)
---
.../src/state/queries.filesystem.test.ts | 18 +++++++++++++++++-
apps/mobile/src/state/queries.ts | 2 +-
apps/mobile/src/state/queryTargets.ts | 4 ++--
.../components/chat/useComposerMenuItems.ts | 2 +-
apps/web/src/state/queries.filesystem.test.ts | 1 +
apps/web/src/state/queries.ts | 2 +-
6 files changed, 23 insertions(+), 6 deletions(-)
diff --git a/apps/mobile/src/state/queries.filesystem.test.ts b/apps/mobile/src/state/queries.filesystem.test.ts
index 8737caccf14bd..46d4fecc9e4d6 100644
--- a/apps/mobile/src/state/queries.filesystem.test.ts
+++ b/apps/mobile/src/state/queries.filesystem.test.ts
@@ -7,6 +7,7 @@ const state = vi.hoisted(() => ({
phase: "connected" as "connected" | "offline",
sessionAtom: {},
searchAtom: {},
+ searchRequests: vi.fn(),
}));
vi.mock("react", async (importOriginal) => ({
@@ -27,7 +28,12 @@ vi.mock("./presentation", () => ({
}));
vi.mock("./pull-requests", () => ({ composerPullRequests: {} }));
vi.mock("./projects", () => ({
- projectEnvironment: { searchEntries: () => state.searchAtom },
+ projectEnvironment: {
+ searchEntries: (target: unknown) => {
+ state.searchRequests(target);
+ return state.searchAtom;
+ },
+ },
}));
vi.mock("./atom-registry", () => ({ appAtomRegistry: {} }));
vi.mock("./orchestration", () => ({ orchestrationEnvironment: {} }));
@@ -59,6 +65,7 @@ beforeEach(() => {
state.session = null;
state.sessionError = null;
state.phase = "connected";
+ state.searchRequests.mockClear();
});
it("keeps a file search pending until its grant loads, then shows matches", () => {
@@ -74,3 +81,12 @@ it("keeps a file search pending until its grant loads, then shows matches", () =
isPending: false,
});
});
+
+it("searches workspace entries for a bare @", () => {
+ state.session = { authenticated: true, scopes: [AuthFilesystemReadScope] };
+ useComposerPathSearch({ ...target, query: "" });
+ expect(state.searchRequests).toHaveBeenCalledExactlyOnceWith({
+ environmentId: target.environmentId,
+ input: { cwd: target.cwd, query: "", limit: 20 },
+ });
+});
diff --git a/apps/mobile/src/state/queries.ts b/apps/mobile/src/state/queries.ts
index a4cdbc6244204..38890d7d57506 100644
--- a/apps/mobile/src/state/queries.ts
+++ b/apps/mobile/src/state/queries.ts
@@ -426,7 +426,7 @@ export function useComposerPathSearch(target: ComposerPathSearchTarget) {
const searchTarget =
debouncedTarget.environmentId !== null &&
debouncedTarget.cwd !== null &&
- debouncedTarget.query.length > 0
+ debouncedTarget.query !== null
? {
environmentId: debouncedTarget.environmentId,
input: {
diff --git a/apps/mobile/src/state/queryTargets.ts b/apps/mobile/src/state/queryTargets.ts
index a52da3fc13483..69237d663826a 100644
--- a/apps/mobile/src/state/queryTargets.ts
+++ b/apps/mobile/src/state/queryTargets.ts
@@ -8,8 +8,8 @@ export interface CheckpointDiffTarget {
readonly ignoreWhitespace: boolean;
}
-export function normalizeComposerPathSearchQuery(query: string | null): string {
- return query?.trim() ?? "";
+export function normalizeComposerPathSearchQuery(query: string | null): string | null {
+ return query?.trim() ?? null;
}
export function buildCheckpointDiffTargets(target: CheckpointDiffTarget) {
diff --git a/apps/web/src/components/chat/useComposerMenuItems.ts b/apps/web/src/components/chat/useComposerMenuItems.ts
index 8090157604986..70ff7e8fd3f04 100644
--- a/apps/web/src/components/chat/useComposerMenuItems.ts
+++ b/apps/web/src/components/chat/useComposerMenuItems.ts
@@ -396,7 +396,7 @@ export function useComposerMenuItems({
return [];
})();
const isLoading =
- (composerTriggerKind === "path" && pathTriggerQuery.length > 0 && workspaceEntries.isPending) ||
+ (composerTriggerKind === "path" && workspaceEntries.isPending) ||
((composerTriggerKind === "skill" || composerTriggerKind === "slash-command") &&
workspaceSkillsScanning) ||
(composerTriggerKind === "pull-request" &&
diff --git a/apps/web/src/state/queries.filesystem.test.ts b/apps/web/src/state/queries.filesystem.test.ts
index 64a307bb2fd70..0e604c68f5f84 100644
--- a/apps/web/src/state/queries.filesystem.test.ts
+++ b/apps/web/src/state/queries.filesystem.test.ts
@@ -100,6 +100,7 @@ const contentTarget = {
};
it.each([
+ { query: "", expected: "" },
{ query: "src", expected: "src" },
{ query: "a".repeat(256), expected: "a".repeat(256) },
{
diff --git a/apps/web/src/state/queries.ts b/apps/web/src/state/queries.ts
index 0461f3ac2b02a..aa97d5f17aa66 100644
--- a/apps/web/src/state/queries.ts
+++ b/apps/web/src/state/queries.ts
@@ -347,7 +347,7 @@ export function useProjectPathSearch(
}
export function useComposerPathSearch(target: ComposerPathSearchTarget) {
- return useProjectPathSearch(target, COMPOSER_PATH_SEARCH_LIMIT);
+ return useProjectPathSearch(target, COMPOSER_PATH_SEARCH_LIMIT, { allowEmptyQuery: true });
}
interface ProjectContentSearchTarget {
From 6b47939e606b17fc6fab278eec3fcbde65995514 Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 13 Nov 2026 15:46:05 -0800
Subject: [PATCH 010/690] fix(mobile): thread list project filter lists
recently active projects first (#36433)
---
.../src/features/home/HomeRouteScreen.tsx | 17 ++++++++++++++---
apps/mobile/src/features/home/HomeScreen.tsx | 10 +++++++++-
.../threads/ThreadNavigationSidebar.tsx | 15 +++++++++++++--
3 files changed, 36 insertions(+), 6 deletions(-)
diff --git a/apps/mobile/src/features/home/HomeRouteScreen.tsx b/apps/mobile/src/features/home/HomeRouteScreen.tsx
index cc66e114d21be..6fdaa28914921 100644
--- a/apps/mobile/src/features/home/HomeRouteScreen.tsx
+++ b/apps/mobile/src/features/home/HomeRouteScreen.tsx
@@ -28,7 +28,11 @@ import {
useAtomValueWhileVisible,
useHomeRouteVisible,
} from "./home-route-visibility";
-import { buildHomeProjectScopes, resolveHomeProjectFilterScope } from "./homeThreadList";
+import {
+ buildHomeProjectScopes,
+ resolveHomeProjectFilterScope,
+ sortHomeProjectScopes,
+} from "./homeThreadList";
import { usePendingTaskListActions } from "./usePendingTaskListActions";
import { useThreadListActions } from "./useThreadListActions";
import { useWorkspaceTabs } from "../layout/workspace/WorkspaceTabsContext";
@@ -138,11 +142,18 @@ export function HomeRouteScreen() {
);
const projectFilterOptions = useMemo(
() =>
- pickableProjectGroups(projectScopes).map((scope) => ({
+ pickableProjectGroups(
+ sortHomeProjectScopes({
+ scopes: projectScopes,
+ threads,
+ pendingTasks: [],
+ projectSortOrder: "updated_at",
+ }),
+ ).map((scope) => ({
key: scope.key,
label: scope.title,
})),
- [projectScopes],
+ [projectScopes, threads],
);
const selectedProjectKey =
resolveHomeProjectFilterScope(projectScopes, projects, listOptions)?.key ?? null;
diff --git a/apps/mobile/src/features/home/HomeScreen.tsx b/apps/mobile/src/features/home/HomeScreen.tsx
index af25be18bb46f..8110cca26793d 100644
--- a/apps/mobile/src/features/home/HomeScreen.tsx
+++ b/apps/mobile/src/features/home/HomeScreen.tsx
@@ -92,6 +92,7 @@ import {
buildHomeProjectScopes,
findHomeProjectScope,
type HomeProjectSortOrder,
+ sortHomeProjectScopes,
} from "./homeThreadList";
import { createSwipeRowActivation } from "./swipe-row-activation";
import { SwipeableScrollGateProvider, useSwipeableScrollGate } from "./thread-swipe-actions";
@@ -678,7 +679,14 @@ export function HomeScreen(props: HomeScreenProps) {
({
+ projects={pickableProjectGroups(
+ sortHomeProjectScopes({
+ scopes: projectScopes,
+ threads: props.threads,
+ pendingTasks: [],
+ projectSortOrder: "updated_at",
+ }),
+ ).map((scope) => ({
key: scope.key,
label: scope.title,
}))}
diff --git a/apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx b/apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
index fb0db83a4dab7..6aa576f984fce 100644
--- a/apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
+++ b/apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
@@ -45,7 +45,11 @@ import { requestThreadMessageJump } from "./threadMessageJump";
import { useThreadListSearchMatches } from "./useThreadListSearchMatches";
import { useHomeListOptions } from "../home/home-list-options";
import { buildHomeListFilterMenuItems } from "../home/home-list-filter-menu";
-import { buildHomeProjectScopes, resolveHomeProjectFilterScope } from "../home/homeThreadList";
+import {
+ buildHomeProjectScopes,
+ resolveHomeProjectFilterScope,
+ sortHomeProjectScopes,
+} from "../home/homeThreadList";
import { SwipeableScrollGateProvider, useSwipeableScrollGate } from "../home/thread-swipe-actions";
import { usePendingTaskListActions } from "../home/usePendingTaskListActions";
import { useThreadListActions } from "../home/useThreadListActions";
@@ -178,7 +182,14 @@ function ThreadNavigationSidebarPane(props: ThreadNavigationSidebarProps) {
}),
[projects, options.selectedEnvironmentId, options.projectGroupingMode],
);
- const projectFilterOptions = pickableProjectGroups(projectScopes).map((scope) => ({
+ const projectFilterOptions = pickableProjectGroups(
+ sortHomeProjectScopes({
+ scopes: projectScopes,
+ threads,
+ pendingTasks: [],
+ projectSortOrder: "updated_at",
+ }),
+ ).map((scope) => ({
key: scope.key,
label: scope.title,
}));
From 2d80cb6bc75fe85b187d92c4322099b17e7f912e Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 13 Nov 2026 15:54:32 -0800
Subject: [PATCH 011/690] fix(clients): live working timers count on the host's
clock (#36420)
---
.../features/threads/ThreadDetailScreen.tsx | 6 ++-
.../threads/floating-working-control.tsx | 19 +++++++--
.../threads/floating-working-status.ts | 3 +-
.../features/threads/thread-list-v2-items.tsx | 12 +++++-
apps/server/src/ws.ts | 1 +
apps/web/src/components/ChatView.logic.ts | 7 +++-
apps/web/src/components/ChatView.tsx | 1 +
.../chat/TimelineWorkGroupRows.logic.ts | 18 ++++-----
.../components/chat/TimelineWorkGroupRows.tsx | 3 +-
.../components/chat/useLocalDispatchState.ts | 4 +-
.../components/sidebar/SidebarThreadRow.tsx | 21 +++++++---
packages/client-runtime/src/clock.test.ts | 37 +++++++++++++++++-
packages/client-runtime/src/clock.ts | 39 +++++++++++++++++++
.../client-runtime/src/rpc/session.test.ts | 24 ++++++++++++
packages/client-runtime/src/rpc/session.ts | 13 +++++++
packages/client-runtime/src/state/server.ts | 4 +-
packages/contracts/src/server.ts | 6 +++
17 files changed, 190 insertions(+), 28 deletions(-)
diff --git a/apps/mobile/src/features/threads/ThreadDetailScreen.tsx b/apps/mobile/src/features/threads/ThreadDetailScreen.tsx
index b36e408c2a5d1..a3e69f65e0827 100644
--- a/apps/mobile/src/features/threads/ThreadDetailScreen.tsx
+++ b/apps/mobile/src/features/threads/ThreadDetailScreen.tsx
@@ -829,7 +829,11 @@ function ThreadDetailBody(
return { kind: "compacting" };
}
if (props.activeWorkStartedAt !== null && contentPresentationKind === "ready") {
- return { kind: "working", startedAt: props.activeWorkStartedAt };
+ return {
+ kind: "working",
+ environmentId: props.environmentId,
+ startedAt: props.activeWorkStartedAt,
+ };
}
if (pendingBackgroundWork !== null && contentPresentationKind === "ready") {
return {
diff --git a/apps/mobile/src/features/threads/floating-working-control.tsx b/apps/mobile/src/features/threads/floating-working-control.tsx
index a41fc2b3de39c..55028a58b7df2 100644
--- a/apps/mobile/src/features/threads/floating-working-control.tsx
+++ b/apps/mobile/src/features/threads/floating-working-control.tsx
@@ -1,4 +1,6 @@
+import { toServerClock } from "@t3tools/client-runtime/clock";
import type { SubagentPillSegment } from "@t3tools/client-runtime/state/thread-subagents";
+import type { EnvironmentId } from "@t3tools/contracts";
import { formatDuration } from "@t3tools/shared/orchestrationTiming";
import { GlassContainer, GlassView } from "expo-glass-effect";
import { type ReactNode, useEffect, useRef, useState } from "react";
@@ -597,7 +599,12 @@ function FloatingStatusLabel(props: {
);
}
return (
-
+
);
}
@@ -638,17 +645,21 @@ function StatusLabelRow(props: {
}
function WorkingDuration(props: {
+ readonly environmentId: EnvironmentId;
readonly startedAt: string;
readonly onLayout: (event: LayoutChangeEvent) => void;
}) {
return (
-
+
);
}
-function WorkingTimer(props: { readonly startedAt: string }) {
+function WorkingTimer(props: {
+ readonly environmentId: EnvironmentId;
+ readonly startedAt: string;
+}) {
const nowMs = useVisibleSecondClock(true);
return (
- Working {formatWorkingDuration(props.startedAt, nowMs)}
+ Working {formatWorkingDuration(props.startedAt, toServerClock(props.environmentId, nowMs))}
);
}
diff --git a/apps/mobile/src/features/threads/floating-working-status.ts b/apps/mobile/src/features/threads/floating-working-status.ts
index e3c137ebfd259..4152ae44227e1 100644
--- a/apps/mobile/src/features/threads/floating-working-status.ts
+++ b/apps/mobile/src/features/threads/floating-working-status.ts
@@ -1,4 +1,5 @@
import type { EnvironmentConnectionPhase } from "@t3tools/client-runtime/connection";
+import type { EnvironmentId } from "@t3tools/contracts";
export interface FloatingStatusAction {
readonly label: string;
@@ -8,7 +9,7 @@ export interface FloatingStatusAction {
}
export type FloatingWorkingStatus =
- | { readonly kind: "working"; readonly startedAt: string }
+ | { readonly kind: "working"; readonly environmentId: EnvironmentId; readonly startedAt: string }
| { readonly kind: "syncing"; readonly label: string }
| { readonly kind: "compacting" }
| {
diff --git a/apps/mobile/src/features/threads/thread-list-v2-items.tsx b/apps/mobile/src/features/threads/thread-list-v2-items.tsx
index d724d8056e95e..b6bf0c98e349c 100644
--- a/apps/mobile/src/features/threads/thread-list-v2-items.tsx
+++ b/apps/mobile/src/features/threads/thread-list-v2-items.tsx
@@ -25,7 +25,12 @@ import {
formatWorkingDurationLabel,
resolveThreadWorkingStartedAt,
} from "@t3tools/client-runtime/state/models";
-import { AuthOrchestrationOperateScope, type EnvironmentMachineKind } from "@t3tools/contracts";
+import {
+ AuthOrchestrationOperateScope,
+ type EnvironmentId,
+ type EnvironmentMachineKind,
+} from "@t3tools/contracts";
+import { toServerClock } from "@t3tools/client-runtime/clock";
import type { SnoozePresetSetting } from "@t3tools/contracts/settings";
import { canSnooze } from "@t3tools/client-runtime/state/thread-settled";
import { threadChipCopyTarget } from "@t3tools/client-runtime/thread-reference";
@@ -153,6 +158,7 @@ const TABULAR_DURATION_STYLE = {
} as const;
function ThreadListV2Duration(props: {
+ readonly environmentId: EnvironmentId;
readonly startedAt: string | null;
readonly leadingSpace: boolean;
}) {
@@ -163,7 +169,7 @@ function ThreadListV2Duration(props: {
<>
{props.leadingSpace ? " " : null}
- {formatWorkingDurationLabel(nowMs - startedAtMs)}
+ {formatWorkingDurationLabel(toServerClock(props.environmentId, nowMs) - startedAtMs)}
>
);
@@ -1254,11 +1260,13 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: {
{statusLabel.label}
{status === "working" ? (
) : status === "waiting" ? (
diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts
index 4d9a0f9bd410e..996383c4e95b4 100644
--- a/apps/server/src/ws.ts
+++ b/apps/server/src/ws.ts
@@ -1885,6 +1885,7 @@ export const layerWsRpc = (
}),
newProjectsRoot: managedFolders.namedProjectsRoot,
...(lastEventLoopStall === undefined ? {} : { lastEventLoopStall }),
+ serverTime: DateTime.formatIso(yield* DateTime.now),
};
});
diff --git a/apps/web/src/components/ChatView.logic.ts b/apps/web/src/components/ChatView.logic.ts
index 94fc6a8cad279..92a456f12d308 100644
--- a/apps/web/src/components/ChatView.logic.ts
+++ b/apps/web/src/components/ChatView.logic.ts
@@ -1,3 +1,4 @@
+import { toServerClock } from "@t3tools/client-runtime/clock";
import { type OrchestrationV2ProviderTurn, type RunAttemptId } from "@t3tools/contracts";
import {
deriveLatestThreadRun,
@@ -979,15 +980,19 @@ export interface LocalDispatchSnapshot {
export function createLocalDispatchSnapshot(
activeThread: Thread | undefined,
options?: {
+ environmentId?: EnvironmentId;
preparingWorktree?: boolean;
latestUserMessageId?: ChatMessage["id"] | null;
submissionIntent?: ComposerSubmissionIntent;
},
): LocalDispatchSnapshot {
+ const environmentId = options?.environmentId ?? activeThread?.environmentId;
const latestRun = activeThread?.latestRun ?? null;
const runtime = activeThread?.runtime ?? null;
return {
- startedAt: new Date().toISOString(),
+ startedAt: new Date(
+ environmentId === undefined ? Date.now() : toServerClock(environmentId, Date.now()),
+ ).toISOString(),
preparingWorktree: Boolean(options?.preparingWorktree),
submissionIntent: options?.submissionIntent ?? "foreground",
latestUserMessageId: options?.latestUserMessageId ?? null,
diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
index 693030ae23c33..b161342f231d1 100644
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -1441,6 +1441,7 @@ export default function ChatView(props: ChatViewProps) {
isSendBusy,
backgroundSubmissionPending,
} = useLocalDispatchState({
+ environmentId,
activeThread,
activeLatestRun,
latestUserMessageId:
diff --git a/apps/web/src/components/chat/TimelineWorkGroupRows.logic.ts b/apps/web/src/components/chat/TimelineWorkGroupRows.logic.ts
index e4a8fe4a583c9..bb53e5fa896d1 100644
--- a/apps/web/src/components/chat/TimelineWorkGroupRows.logic.ts
+++ b/apps/web/src/components/chat/TimelineWorkGroupRows.logic.ts
@@ -1,20 +1,20 @@
+import { toServerClock } from "@t3tools/client-runtime/clock";
+import type { EnvironmentId } from "@t3tools/contracts";
import { formatDuration } from "@t3tools/shared/orchestrationTiming";
-function formatWorkingTimer(startIso: string, endIso: string): string | null {
+export function formatWorkingTimerNow(startIso: string, environmentId: EnvironmentId): string {
const startedAtMs = Date.parse(startIso);
- const endedAtMs = Date.parse(endIso);
- if (!Number.isFinite(startedAtMs) || !Number.isFinite(endedAtMs)) {
- return null;
+ if (!Number.isFinite(startedAtMs)) {
+ return "0s";
}
- const elapsedSeconds = Math.max(0, Math.floor((endedAtMs - startedAtMs) / 1000));
+ const elapsedSeconds = Math.max(
+ 0,
+ Math.floor((toServerClock(environmentId, Date.now()) - startedAtMs) / 1000),
+ );
if (elapsedSeconds < 60) {
return `${elapsedSeconds}s`;
}
return formatDuration(elapsedSeconds * 1_000);
}
-
-export function formatWorkingTimerNow(startIso: string): string {
- return formatWorkingTimer(startIso, new Date().toISOString()) ?? "0s";
-}
diff --git a/apps/web/src/components/chat/TimelineWorkGroupRows.tsx b/apps/web/src/components/chat/TimelineWorkGroupRows.tsx
index 33608965ef313..1784f38da4557 100644
--- a/apps/web/src/components/chat/TimelineWorkGroupRows.tsx
+++ b/apps/web/src/components/chat/TimelineWorkGroupRows.tsx
@@ -127,9 +127,10 @@ export const WorkGroupSection = memo(function WorkGroupSection({
});
function WorkingTimer({ createdAt }: { createdAt: string }) {
+ const { activeThreadEnvironmentId } = use(TimelineRowCtx);
return (
formatWorkingTimerNow(createdAt), true)}
+ ref={tickTextContent(() => formatWorkingTimerNow(createdAt, activeThreadEnvironmentId), true)}
className="tabular-nums"
/>
);
diff --git a/apps/web/src/components/chat/useLocalDispatchState.ts b/apps/web/src/components/chat/useLocalDispatchState.ts
index 977028db17b9e..eee86c699d8c5 100644
--- a/apps/web/src/components/chat/useLocalDispatchState.ts
+++ b/apps/web/src/components/chat/useLocalDispatchState.ts
@@ -1,4 +1,4 @@
-import { type MessageId, type RuntimeRequestId } from "@t3tools/contracts";
+import { type EnvironmentId, type MessageId, type RuntimeRequestId } from "@t3tools/contracts";
import { useState } from "react";
import { type ComposerSubmissionIntent } from "../../composer-logic";
import { type SessionPhase, type Thread } from "../../types";
@@ -9,6 +9,7 @@ import {
} from "../ChatView.logic";
export function useLocalDispatchState(input: {
+ environmentId: EnvironmentId;
activeThread: Thread | undefined;
activeLatestRun: Thread["latestRun"] | null;
latestUserMessageId: MessageId | null;
@@ -50,6 +51,7 @@ export function useLocalDispatchState(input: {
}
return createLocalDispatchSnapshot(input.activeThread, {
...options,
+ environmentId: input.environmentId,
latestUserMessageId: input.latestUserMessageId,
});
});
diff --git a/apps/web/src/components/sidebar/SidebarThreadRow.tsx b/apps/web/src/components/sidebar/SidebarThreadRow.tsx
index 89ab26f51b9a0..216f6635f7088 100644
--- a/apps/web/src/components/sidebar/SidebarThreadRow.tsx
+++ b/apps/web/src/components/sidebar/SidebarThreadRow.tsx
@@ -26,6 +26,7 @@ import {
resolveThreadProviderStack,
} from "@t3tools/client-runtime/state/models";
import { scopeThreadRef, scopedThreadKey } from "@t3tools/client-runtime/environment";
+import { toServerClock } from "@t3tools/client-runtime/clock";
import {
AuthOrchestrationOperateScope,
type EnvironmentId,
@@ -163,18 +164,20 @@ function JumpHintBadge(props: { label: string }) {
const JUMP_HINT_SLOT_CLASS_NAME =
"[&>:not([data-jump-hint])]:invisible [&>:not([data-jump-hint])]:absolute";
-function WorkingDuration(props: { startedAt: string | null }) {
+function WorkingDuration(props: { environmentId: EnvironmentId; startedAt: string | null }) {
const startedMs = props.startedAt !== null ? Date.parse(props.startedAt) : Number.NaN;
if (Number.isNaN(startedMs)) return null;
- return ;
+ return ;
}
// Self-ticking so only this span re-renders each second, not the whole row;
// the shared tick lands every working row's update in the same frame.
-function WorkingDurationLabel(props: { startedMs: number }) {
+function WorkingDurationLabel(props: { environmentId: EnvironmentId; startedMs: number }) {
const nowMs = useNowSecond();
return (
- {formatWorkingDurationLabel(nowMs - props.startedMs)}
+
+ {formatWorkingDurationLabel(toServerClock(props.environmentId, nowMs) - props.startedMs)}
+
);
}
@@ -1524,11 +1527,17 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
{topStatus.label}
{status === "preparing" || status === "working" ? (
-
+
) : status === "waiting" ? (
-
+
) : null}
diff --git a/packages/client-runtime/src/clock.test.ts b/packages/client-runtime/src/clock.test.ts
index ca8130a379ad4..1f45860addf7c 100644
--- a/packages/client-runtime/src/clock.test.ts
+++ b/packages/client-runtime/src/clock.test.ts
@@ -1,5 +1,6 @@
+import { EnvironmentId } from "@t3tools/contracts";
import { afterEach, describe, expect, it, vi } from "vite-plus/test";
-import { deadlineStore, secondTick } from "./clock.ts";
+import { deadlineStore, recordServerClock, secondTick, toServerClock } from "./clock.ts";
describe("deadlineStore", () => {
afterEach(() => {
@@ -138,3 +139,37 @@ describe("secondTick", () => {
expect(vi.getTimerCount()).toBe(0);
});
});
+
+describe("server clock", () => {
+ const HOUR = 3_600_000;
+
+ it("counts from a host clock running ahead, minus only the reply's latency", () => {
+ const environmentId = EnvironmentId.make("ahead");
+ recordServerClock(environmentId, {
+ serverTimeMs: HOUR + 1_000,
+ requestedAtMs: 900,
+ receivedAtMs: 1_050,
+ });
+ expect(toServerClock(environmentId, 2_050)).toBe(HOUR + 2_000);
+ });
+
+ it("counts from a host clock running behind, minus only the request's latency", () => {
+ const environmentId = EnvironmentId.make("behind");
+ recordServerClock(environmentId, {
+ serverTimeMs: 1_000,
+ requestedAtMs: HOUR + 900,
+ receivedAtMs: HOUR + 1_050,
+ });
+ expect(toServerClock(environmentId, HOUR + 1_900)).toBe(2_000);
+ });
+
+ it("leaves clocks that agree within the round trip unshifted", () => {
+ const environmentId = EnvironmentId.make("synced");
+ recordServerClock(environmentId, {
+ serverTimeMs: 5_000,
+ requestedAtMs: 1_000,
+ receivedAtMs: 9_000,
+ });
+ expect(toServerClock(environmentId, 12_345)).toBe(12_345);
+ });
+});
diff --git a/packages/client-runtime/src/clock.ts b/packages/client-runtime/src/clock.ts
index d0e9db7259530..f8f1290730084 100644
--- a/packages/client-runtime/src/clock.ts
+++ b/packages/client-runtime/src/clock.ts
@@ -1,5 +1,7 @@
// @effect-diagnostics globalTimers:off globalDate:off - Render clocks for React hooks, outside an Effect runtime.
+import type { EnvironmentId } from "@t3tools/contracts";
+
/**
* A `useSyncExternalStore`-shaped clock. Snapshots stay cached between
* notifications while subscribed, as `useSyncExternalStore` requires.
@@ -70,6 +72,43 @@ export const minuteTick: ClockStore = makeTickStore(
60_000,
);
+// How far each environment's clock runs ahead of this one. Timers read it on
+// every tick, so a reconnect's new reading lands without a re-render.
+const serverClockOffsets = new Map();
+
+/**
+ * The host stamped `serverTimeMs` between the request and its reply, so the
+ * offset lies within that window. Keep the one nearest 0, so clocks that agree
+ * read as 0 instead of shifting every timer by network latency.
+ */
+export function recordServerClock(
+ environmentId: EnvironmentId,
+ sample: {
+ readonly serverTimeMs: number | undefined;
+ readonly requestedAtMs: number;
+ readonly receivedAtMs: number;
+ },
+): void {
+ const { serverTimeMs, requestedAtMs, receivedAtMs } = sample;
+ if (serverTimeMs === undefined || !Number.isFinite(serverTimeMs)) {
+ serverClockOffsets.delete(environmentId);
+ return;
+ }
+ serverClockOffsets.set(
+ environmentId,
+ serverTimeMs > receivedAtMs
+ ? serverTimeMs - receivedAtMs
+ : serverTimeMs < requestedAtMs
+ ? serverTimeMs - requestedAtMs
+ : 0,
+ );
+}
+
+/** Converts client epoch milliseconds to the environment's clock. */
+export function toServerClock(environmentId: EnvironmentId, clientMs: number): number {
+ return clientMs + (serverClockOffsets.get(environmentId) ?? 0);
+}
+
export interface DeadlineStore extends ClockStore {
/**
* Re-checks `Date.now()` and notifies if the deadline has passed. For callers
diff --git a/packages/client-runtime/src/rpc/session.test.ts b/packages/client-runtime/src/rpc/session.test.ts
index cc1932b54ea76..b6e0908a1f1a9 100644
--- a/packages/client-runtime/src/rpc/session.test.ts
+++ b/packages/client-runtime/src/rpc/session.test.ts
@@ -35,6 +35,7 @@ import {
import * as EnvironmentSupervisor from "../connection/supervisor.ts";
import * as Persistence from "../platform/persistence.ts";
import * as RpcSession from "./session.ts";
+import { toServerClock } from "../clock.ts";
import { makeEnvironmentServerConfigState } from "../state/server.ts";
import { applyServerConfigProjection } from "../state/serverConfigProjection.ts";
@@ -1048,6 +1049,29 @@ describe("RpcSessionFactory", () => {
}),
);
+ it.effect("reads the host clock from each connection's config, dropping it when absent", () =>
+ Effect.gen(function* () {
+ const { factory, sockets } = yield* makeFactory();
+ const connectWith = (config: unknown) =>
+ Effect.scoped(
+ Effect.gen(function* () {
+ const socketIndex = sockets.length;
+ const session = yield* factory.connect(PREPARED);
+ const ready = yield* Effect.forkChild(session.ready);
+ const socket = yield* awaitSocket(sockets, socketIndex);
+ socket.open();
+ yield* completeInitialConfig(socket, config);
+ yield* Fiber.join(ready);
+ }),
+ );
+
+ yield* connectWith({ ...ENCODED_SERVER_CONFIG, serverTime: "1970-01-01T01:00:00.000Z" });
+ expect(toServerClock(PREPARED.environmentId, 0)).toBe(3_600_000);
+ yield* connectWith(ENCODED_SERVER_CONFIG);
+ expect(toServerClock(PREPARED.environmentId, 0)).toBe(0);
+ }),
+ );
+
it.effect("reaches ready when a newer server sends unknown config members", () =>
Effect.gen(function* () {
const { factory, sockets } = yield* makeFactory();
diff --git a/packages/client-runtime/src/rpc/session.ts b/packages/client-runtime/src/rpc/session.ts
index 66c7d8b9908bd..8be598dc178ab 100644
--- a/packages/client-runtime/src/rpc/session.ts
+++ b/packages/client-runtime/src/rpc/session.ts
@@ -9,6 +9,7 @@ import {
SOCKET_FRAME_PARTS_QUERY_PARAM,
} from "@t3tools/shared/socketFrameParts";
import * as Cause from "effect/Cause";
+import * as Clock from "effect/Clock";
import * as Context from "effect/Context";
import * as Deferred from "effect/Deferred";
import * as Duration from "effect/Duration";
@@ -46,6 +47,7 @@ import {
withoutEnvironmentThemes,
} from "../state/serverConfigProjection.ts";
import { environmentMismatchError } from "../connection/errors.ts";
+import { recordServerClock } from "../clock.ts";
const SOCKET_OPEN_TIMEOUT = "15 seconds";
@@ -295,6 +297,7 @@ export const make = Effect.fn("RpcSessionFactory.make")(function* (
reason: "remote-unavailable",
detail: `${connection.label} config subscription ended.`,
});
+ const configRequestedAtMs = yield* Clock.currentTimeMillis;
const serverConfigSource = protocolClient[WS_METHODS.subscribeServerConfig](
onDemandSnapshots(serverConfigInput),
).pipe(
@@ -332,6 +335,16 @@ export const make = Effect.fn("RpcSessionFactory.make")(function* (
yield* PubSub.publish(serverConfigUpdates, buffered.value);
}
if (event.type === "snapshot") {
+ // Later snapshots may repeat the first one's serverTime. Record
+ // before readiness so the first timers already use its clock.
+ if (!(yield* Deferred.isDone(initialConfigDeferred))) {
+ const { serverTime } = event.config;
+ recordServerClock(connection.environmentId, {
+ serverTimeMs: serverTime === undefined ? undefined : Date.parse(serverTime),
+ requestedAtMs: configRequestedAtMs,
+ receivedAtMs: yield* Clock.currentTimeMillis,
+ });
+ }
yield* Deferred.succeed(initialConfigDeferred, event.config);
}
}),
diff --git a/packages/client-runtime/src/state/server.ts b/packages/client-runtime/src/state/server.ts
index 55fd0c928280d..f16861f5333fd 100644
--- a/packages/client-runtime/src/state/server.ts
+++ b/packages/client-runtime/src/state/server.ts
@@ -489,11 +489,13 @@ export const makeEnvironmentServerConfigState = Effect.fn("EnvironmentServerConf
const persist = Effect.fn("EnvironmentServerConfigState.persist")(function* (
config: ServerConfig,
) {
- // A cached stall would read as a fresh freeze on the next launch.
+ // A cached stall would read as a fresh freeze on the next launch, and
+ // serverTime changes every connect, which would rewrite the cache each time.
const {
environmentThemes: _themes,
usageLimitSources: _sources,
lastEventLoopStall: _stall,
+ serverTime: _serverTime,
...cacheable
} = config;
if (lastPersisted !== undefined && sameCacheValue(lastPersisted, cacheable)) {
diff --git a/packages/contracts/src/server.ts b/packages/contracts/src/server.ts
index e8bcda3a76f5d..706f920770751 100644
--- a/packages/contracts/src/server.ts
+++ b/packages/contracts/src/server.ts
@@ -726,6 +726,12 @@ export const ServerConfig = Schema.Struct({
* answering. Clients do not cache it, since it describes the host at connect.
*/
lastEventLoopStall: Schema.optionalKey(ServerEventLoopStall),
+ /**
+ * The host's clock when it built the config, so clients can count live
+ * elapsed times against host timestamps despite clock skew. Only a
+ * subscription's first snapshot is fresh; later snapshots may repeat it.
+ */
+ serverTime: Schema.optionalKey(IsoDateTime),
/**
* Never sent in a config snapshot: the theme stream emits the current set
* before any change, so a snapshot carrying it too would hand every
From 770028f965e0639e4eefb639f25f2b211d5ccdfc Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 13 Nov 2026 15:56:48 -0800
Subject: [PATCH 012/690] perf(relay): lighter Postgres load and PS-160 cluster
(#36442)
Co-authored-by: Claude Opus 5.5
---
.../AgentActivityPublisher.test.ts | 67 +++++++++++++++++++
.../agentActivity/AgentActivityPublisher.ts | 21 +++---
.../src/agentActivity/DeliveryAttempts.ts | 2 +-
infra/relay/src/db.ts | 2 +-
4 files changed, 80 insertions(+), 12 deletions(-)
diff --git a/infra/relay/src/agentActivity/AgentActivityPublisher.test.ts b/infra/relay/src/agentActivity/AgentActivityPublisher.test.ts
index 2f4ed3ff02272..679c410470397 100644
--- a/infra/relay/src/agentActivity/AgentActivityPublisher.test.ts
+++ b/infra/relay/src/agentActivity/AgentActivityPublisher.test.ts
@@ -207,6 +207,73 @@ describe("AgentActivityPublisher", () => {
);
});
+ it.effect.each([
+ {
+ name: "skips the activity query without a running iOS Live Activity",
+ activityToken: null,
+ listCalls: 0,
+ },
+ {
+ name: "aggregates activity for a running iOS Live Activity",
+ activityToken: "activity-token",
+ listCalls: 1,
+ },
+ ])("$name", ({ activityToken, listCalls }) => {
+ const android = { ...target("android"), platform: "android" as const, ios_major_version: null };
+ const ios = { ...target("ios"), activity_push_token: activityToken };
+ let listed = 0;
+ const hasAggregate: boolean[] = [];
+ return Effect.gen(function* () {
+ const publisher = yield* AgentActivityPublisher.AgentActivityPublisher;
+ yield* publisher.publish({
+ environmentId: state.environmentId,
+ environmentPublicKey: "key",
+ threadId: state.threadId,
+ state,
+ });
+ expect(listed).toBe(listCalls);
+ expect(hasAggregate).toEqual([listCalls > 0]);
+ }).pipe(
+ Effect.provide(
+ AgentActivityPublisher.layer.pipe(
+ Layer.provide(
+ Layer.mergeAll(
+ Layer.succeed(
+ AgentActivityRows.AgentActivityRows,
+ makeAgentActivityRows({
+ listForUser: () =>
+ Effect.sync(() => {
+ listed += 1;
+ return [state];
+ }),
+ }),
+ ),
+ Layer.succeed(EnvironmentLinks.EnvironmentLinks, makeEnvironmentLinks()),
+ Layer.succeed(
+ LiveActivities.LiveActivities,
+ makeLiveActivities({ listTargets: () => Effect.succeed([android, ios]) }),
+ ),
+ Layer.succeed(
+ ApnsDeliveries.ApnsDeliveries,
+ makeApnsDeliveries({
+ sendForTarget: (input) =>
+ Effect.sync(() => {
+ hasAggregate.push(input.aggregate !== null);
+ return null;
+ }),
+ }),
+ ),
+ Layer.succeed(FcmDeliveries.FcmDeliveries, {
+ enqueue: () => Effect.succeed(null),
+ process: () => Effect.void,
+ }),
+ ),
+ ),
+ ),
+ ),
+ );
+ });
+
it.effect("replays the latest aggregate when a Live Activity token registers", () => {
const registeredTarget: LiveActivities.TargetRow = {
...target("device-1"),
diff --git a/infra/relay/src/agentActivity/AgentActivityPublisher.ts b/infra/relay/src/agentActivity/AgentActivityPublisher.ts
index d58a91c7f4eae..6533d021f0f29 100644
--- a/infra/relay/src/agentActivity/AgentActivityPublisher.ts
+++ b/infra/relay/src/agentActivity/AgentActivityPublisher.ts
@@ -63,17 +63,18 @@ const make = Effect.gen(function* () {
readonly state: RelayAgentActivityState | null;
readonly nowMs: number;
}) {
- const activeStates = input.deliveryUser.liveActivitiesEnabled
- ? yield* rows.listForUser({ userId: input.deliveryUser.userId })
- : [];
- const liveActivityAggregate = input.deliveryUser.liveActivitiesEnabled
- ? makeAggregateState({
- activeStates,
- terminalState: input.state && isTerminalPhase(input.state) ? input.state : null,
- nowMs: input.nowMs,
- })
- : null;
const targets = yield* liveActivities.listTargets({ userId: input.deliveryUser.userId });
+ // Only iOS targets with a running Live Activity read the aggregate, and few users have one,
+ // so skip the per-user activity query otherwise.
+ const liveActivityAggregate =
+ input.deliveryUser.liveActivitiesEnabled &&
+ targets.some((target) => target.platform !== "android" && target.activity_push_token)
+ ? makeAggregateState({
+ activeStates: yield* rows.listForUser({ userId: input.deliveryUser.userId }),
+ terminalState: input.state && isTerminalPhase(input.state) ? input.state : null,
+ nowMs: input.nowMs,
+ })
+ : null;
return yield* Effect.forEach(
targets,
Effect.fnUntraced(function* (target) {
diff --git a/infra/relay/src/agentActivity/DeliveryAttempts.ts b/infra/relay/src/agentActivity/DeliveryAttempts.ts
index fef33de106d55..d638712d380c6 100644
--- a/infra/relay/src/agentActivity/DeliveryAttempts.ts
+++ b/infra/relay/src/agentActivity/DeliveryAttempts.ts
@@ -68,7 +68,7 @@ export class DeliveryAttempts extends Context.Service<
>()("t3code-relay/agentActivity/DeliveryAttempts") {}
const SOURCE_JOB_CLAIM_LEASE_MINUTES = 10;
-export const DELIVERY_ATTEMPT_RETENTION_DAYS = 7;
+export const DELIVERY_ATTEMPT_RETENTION_DAYS = 1;
export const DELIVERY_ATTEMPT_PRUNE_BATCH_SIZE = 5_000;
// Caps each cron run so draining the pre-retention backlog cannot monopolize the database.
export const DELIVERY_ATTEMPT_PRUNE_MAX_BATCHES = 4;
diff --git a/infra/relay/src/db.ts b/infra/relay/src/db.ts
index 0b7fba38bffa8..52133544d4641 100644
--- a/infra/relay/src/db.ts
+++ b/infra/relay/src/db.ts
@@ -49,7 +49,7 @@ export const PlanetscaleDatabase = Effect.gen(function* () {
? yield* Planetscale.PostgresDatabase("RelayPostgresDatabase", {
name: "t3coderelay",
region: { slug: "us-west" },
- clusterSize: "PS_80",
+ clusterSize: "PS_160",
migrations: { dir: schema.out, table: "relay_migrations" },
replicas: 2,
}).pipe(RemovalPolicy.retain())
From a6d91409729be6ba854b3f16adedc08847e3b24c Mon Sep 17 00:00:00 2001
From: Julius Marminge
Date: Fri, 13 Nov 2026 15:57:32 -0800
Subject: [PATCH 013/690] fix(mobile): mobile:install --session works on pooled
devices (#36447)
---
.agents/skills/test-t3-mobile/SKILL.md | 13 +++++---
scripts/mobile-native-client.ts | 41 ++++++++++++++++++--------
2 files changed, 37 insertions(+), 17 deletions(-)
diff --git a/.agents/skills/test-t3-mobile/SKILL.md b/.agents/skills/test-t3-mobile/SKILL.md
index 306038007e9a0..a11eee4e3f4eb 100644
--- a/.agents/skills/test-t3-mobile/SKILL.md
+++ b/.agents/skills/test-t3-mobile/SKILL.md
@@ -43,10 +43,15 @@ prints one JSON line; on failure it names the next command. Mobile verification
includes that step unless the user prohibits it.
For a device you opened in an agent-device session elsewhere (a device pool,
-cloud, or remote host), run `vp run mobile:install --session `
-from the directory you opened the session in. It installs the cached build into
-that session (`--abi arm64-v8a` for an ARM Android device; `--metro ` also
-opens it against Metro) and never builds.
+cloud, or remote host), run `vp run mobile:install --session
+--config ` from the directory you opened the session in, passing the
+`--config` file from `device_open`'s `targetArgs`. Without it, a device pool's
+gateway refuses the install, wrongly saying agent-device is too old. It installs
+the cached build into that session (`--abi arm64-v8a` for an ARM Android device;
+pool Android emulators are x86_64, the default; `--metro ` also opens it
+against Metro). Without a cached build for this checkout it builds first, which can
+outlast the lease, so run `vp run mobile:install --prepare` (with
+the same `--abi`) before you open the device.
Start `vp run dev:client` from `apps/mobile`, or reuse a healthy Metro belonging
to this checkout. Open its printed development-client URL with AgentDevice
diff --git a/scripts/mobile-native-client.ts b/scripts/mobile-native-client.ts
index 9e6c38f7bd175..e28bf957e3bce 100644
--- a/scripts/mobile-native-client.ts
+++ b/scripts/mobile-native-client.ts
@@ -175,9 +175,14 @@ const command = Effect.fn("nativeClient.command")(function* (
args: string[],
inherit = false,
cwd?: string,
+ withoutEnvironment: ReadonlyArray = [],
) {
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
- const environment = yield* HostProcess.Environment;
+ const environment = Object.fromEntries(
+ Object.entries(yield* HostProcess.Environment).filter(
+ ([name]) => !withoutEnvironment.includes(name),
+ ),
+ );
const log = inherit ? yield* BuildLog : null;
const spawn = yield* resolveSpawnCommand(program === "adb" ? yield* resolveAdb : program, args);
const child = yield* spawner.spawn(
@@ -1098,6 +1103,7 @@ const installIntoSession = Effect.fn("nativeClient.installIntoSession")(function
session: string,
abi: string,
metro: string | undefined,
+ config: string | undefined,
) {
const started = yield* Clock.currentTimeMillis;
const path = yield* Path.Path;
@@ -1108,6 +1114,13 @@ const installIntoSession = Effect.fn("nativeClient.installIntoSession")(function
);
// agent-device scopes session ownership to the workspace (working directory) it runs in.
const workspace = yield* HostProcess.WorkingDirectory;
+ // A session behind a gateway (a device pool) is reachable only with the config that opened it.
+ const target = ["--session", session, ...(config ? ["--config", config] : [])];
+ // Daemon variables in the shell (a device-pool login) override the config and authenticate
+ // as someone who does not hold this lease; T3 Code's own launcher drops them the same way.
+ const agentDeviceEnvironment = config
+ ? ["AGENT_DEVICE_DAEMON_BASE_URL", "AGENT_DEVICE_DAEMON_AUTH_TOKEN", "AGENT_DEVICE_CONFIG"]
+ : [];
const deviceAbi = platform === "android" ? abi : null;
const task = deviceAbi ? `native-client:android-${deviceAbi}` : `native-client:${platform}`;
const current = yield* fingerprint(platform);
@@ -1118,17 +1131,10 @@ const installIntoSession = Effect.fn("nativeClient.installIntoSession")(function
const downloadOnDevice = source
? command(
node,
- [
- agentDevice,
- "install-from-source",
- source.url,
- "--header",
- source.header,
- "--session",
- session,
- ],
+ [agentDevice, "install-from-source", source.url, "--header", source.header, ...target],
true,
workspace,
+ agentDeviceEnvironment,
).pipe(
Effect.as(true),
Effect.catch((error) =>
@@ -1150,9 +1156,10 @@ const installIntoSession = Effect.fn("nativeClient.installIntoSession")(function
yield* Console.error(`Installing into agent-device session ${session}...`);
yield* command(
node,
- [agentDevice, "install", bundleId, local.file, "--session", session],
+ [agentDevice, "install", bundleId, local.file, ...target],
true,
workspace,
+ agentDeviceEnvironment,
);
return local;
});
@@ -1165,11 +1172,11 @@ const installIntoSession = Effect.fn("nativeClient.installIntoSession")(function
"open",
bundleId,
`t3code-dev://expo-development-client/?url=${url}&disableOnboarding=1&disableFab=1&disableAutoLaunch=1`,
- "--session",
- session,
+ ...target,
],
true,
workspace,
+ agentDeviceEnvironment,
);
}
yield* reportTelemetry(
@@ -1213,6 +1220,12 @@ const main = Command.make(
Flag.withDescription("With --session, open the dev client against this Metro URL."),
Flag.optional,
),
+ config: Flag.String("config").pipe(
+ Flag.withDescription(
+ "With --session, the agent-device config the session was opened with (T3 Code's device_open returns it in targetArgs). Needed for a device pool behind a gateway.",
+ ),
+ Flag.optional,
+ ),
prepare: Flag.Boolean("prepare").pipe(
Flag.withDescription(
"Fetch or build the client for this checkout without a device (Android: for --abi). Run it before leasing a device: a build can outlast the lease.",
@@ -1233,6 +1246,7 @@ const main = Command.make(
session,
abi: sessionAbi,
metro,
+ config,
prepare,
expectFingerprint,
}) {
@@ -1280,6 +1294,7 @@ const main = Command.make(
session.value,
sessionAbi,
Option.getOrUndefined(metro),
+ Option.getOrUndefined(config),
);
}
const target = yield* resolveDevice(platform, Option.getOrUndefined(requested));
From 9d8998b436f7d81295386e4f6aa7012784aa86a6 Mon Sep 17 00:00:00 2001
From: Theo Browne
Date: Fri, 13 Nov 2026 16:11:09 -0800
Subject: [PATCH 014/690] fix(native): KDE SnapShot no longer loses the first
reply from KWin (#36218)
Co-authored-by: Claude Fable 5.2
---
native/kde-snap-shot/src/main.rs | 44 +++++++++++++--------
native/kde-snap-shot/src/transport_tests.rs | 6 +--
2 files changed, 28 insertions(+), 22 deletions(-)
diff --git a/native/kde-snap-shot/src/main.rs b/native/kde-snap-shot/src/main.rs
index ec12567793972..2c303d408dba8 100644
--- a/native/kde-snap-shot/src/main.rs
+++ b/native/kde-snap-shot/src/main.rs
@@ -71,10 +71,11 @@ struct Bounds {
height: u32,
}
-struct ScriptReply {
- owner: String,
- result: mpsc::SyncSender,
-}
+const REPLY: &str = "/com/t3tools/KdeCapture";
+
+/// KWin's bus name and the channel of the script call in flight. `None` between calls.
+#[derive(Default)]
+struct ScriptReply(Option<(String, mpsc::SyncSender)>);
#[zbus::interface(name = "com.t3tools.KdeCapture")]
impl ScriptReply {
fn reply(
@@ -82,7 +83,12 @@ impl ScriptReply {
value: &str,
#[zbus(header)] header: zbus::message::Header<'_>,
) -> zbus::fdo::Result<()> {
- if header.sender().map(|name| name.as_str()) != Some(self.owner.as_str()) {
+ let Some((owner, result)) = &self.0 else {
+ return Err(zbus::fdo::Error::AccessDenied(
+ "No script is running".into(),
+ ));
+ };
+ if header.sender().map(|name| name.as_str()) != Some(owner.as_str()) {
return Err(zbus::fdo::Error::AccessDenied("Not KWin".into()));
}
if value.len() > 64 * 1024 {
@@ -90,23 +96,29 @@ impl ScriptReply {
"Oversized window metadata".into(),
));
}
- let _ = self.result.try_send(value.to_owned());
+ let _ = result.try_send(value.to_owned());
Ok(())
}
}
+/// Serves the reply object from the start, so zbus waits for its method-call listener.
+/// A listener started after connecting can miss the first call from KWin.
+fn connect(builder: Builder<'_>) -> zbus::Result {
+ builder
+ .method_timeout(DEADLINE)
+ .serve_at(REPLY, ScriptReply::default())?
+ .build()
+}
+
/// Scripts exist only for this operation. Unload even when evaluation/callback fails.
fn script(connection: &Connection, directory: &Path, body: &str) -> Result {
let dbus = zbus::blocking::fdo::DBusProxy::new(connection)?;
let owner = dbus.get_name_owner("org.kde.KWin".try_into()?)?.to_string();
let (send, receive) = mpsc::sync_channel(1);
- connection.object_server().at(
- "/com/t3tools/KdeCapture",
- ScriptReply {
- owner,
- result: send,
- },
- )?;
+ let reply = connection
+ .object_server()
+ .interface::<_, ScriptReply>(REPLY)?;
+ reply.get_mut().0 = Some((owner, send));
let path = directory.join("window.js");
let name = format!("t3-capture-{}", std::process::id());
let destination = serde_json::to_string(
@@ -147,9 +159,7 @@ fn script(connection: &Connection, directory: &Path, body: &str) -> Result = scripting.call("unloadScript", &(&name,));
result
})();
- connection
- .object_server()
- .remove::("/com/t3tools/KdeCapture")?;
+ reply.get_mut().0 = None;
let _ = std::fs::remove_file(path);
result
}
@@ -336,7 +346,7 @@ fn capture(connection: &Connection, directory: &Path) -> Result
))}
{/* Machine names truncate first. Provider lists wrap between names, never inside one. */}
-