diff --git a/apps/server/src/orchestration-v2/RestartBackgroundNote.ts b/apps/server/src/orchestration-v2/RestartBackgroundNote.ts index d957daad6ff7b..62d534a81ad07 100644 --- a/apps/server/src/orchestration-v2/RestartBackgroundNote.ts +++ b/apps/server/src/orchestration-v2/RestartBackgroundNote.ts @@ -51,7 +51,7 @@ export function cancelledTurnItemWork( } case "command_execution": return { - kind: "shell", + kind: item.monitor === true ? "monitor" : "shell", label: compactLabel(item.input) ?? compactLabel(item.title) ?? "background command", id: item.id, }; diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/grok_monitor/output.ts b/apps/server/src/orchestration-v2/testkit/fixtures/grok_monitor/output.ts index 285a8c40a12da..705639d6f4140 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/grok_monitor/output.ts +++ b/apps/server/src/orchestration-v2/testkit/fixtures/grok_monitor/output.ts @@ -1,7 +1,9 @@ import { assert } from "@effect/vitest"; import type { ProviderReplayTranscript } from "@t3tools/contracts"; +import { derivePendingBackgroundWork } from "@t3tools/shared/orchestrationV2PendingBackgroundWork"; import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts"; +import { cancelledTurnItemWork } from "../../../RestartBackgroundNote.ts"; import { assertBaseProjection, assertSemanticProjectionIntegrity, @@ -54,6 +56,20 @@ export function assertGrokMonitorOutput( assert.equal(monitorUpdates[tickUpdate]?.status, "running", `${tick} must show running`); } + // A persistent monitor keeps running after its run ends. Background work and + // the restart note must then name it a monitor, as they do for Claude's Monitor. + const runningMonitor = monitorUpdates.find((update) => update.status === "running"); + assert.isDefined(runningMonitor); + assert.deepEqual( + derivePendingBackgroundWork({ + latestRun: rootRun, + providerThreads: [], + turnItems: [runningMonitor], + }).map((task) => task.kind), + ["monitor"], + ); + assert.equal(cancelledTurnItemWork(runningMonitor)?.kind, "monitor"); + const monitorCompletedAt = result.domainEvents.findIndex( (event) => event.type === "turn-item.updated" && diff --git a/packages/contracts/src/orchestrationV2.ts b/packages/contracts/src/orchestrationV2.ts index b708f3187d1aa..46403eb635d9d 100644 --- a/packages/contracts/src/orchestrationV2.ts +++ b/packages/contracts/src/orchestrationV2.ts @@ -1477,6 +1477,8 @@ export const OrchestrationV2TurnItem = Schema.Union([ outputOmitted: Schema.optional(Schema.Boolean), outputIndicatesFailure: Schema.optional(Schema.Boolean), exitCode: Schema.optional(Schema.Int), + /** Set when the command is a monitor, so background work lists it as a monitor. */ + monitor: Schema.optional(Schema.Boolean), }), Schema.Struct({ ...OrchestrationV2TurnItemBaseFields, @@ -2244,6 +2246,8 @@ export const OrchestrationV2TurnItemJson = Schema.Union([ outputOmitted: Schema.optional(Schema.Boolean), outputIndicatesFailure: Schema.optional(Schema.Boolean), exitCode: Schema.optional(Schema.Int), + /** Set when the command is a monitor, so background work lists it as a monitor. */ + monitor: Schema.optional(Schema.Boolean), }), Schema.Struct({ ...OrchestrationV2TurnItemJsonBaseFields, diff --git a/packages/provider-acp/src/server/adapter.ts b/packages/provider-acp/src/server/adapter.ts index 54409011f5877..9f71d55dea7e4 100644 --- a/packages/provider-acp/src/server/adapter.ts +++ b/packages/provider-acp/src/server/adapter.ts @@ -3916,6 +3916,7 @@ export const makeAcpAdapterV2 = Effect.fn("makeAcpAdapterV2")(function* ( ? {} : { output: textFromUnknown(rawOutput) }), ...(exitCode === undefined ? {} : { exitCode }), + ...(inputVariant === "monitor" ? { monitor: true } : {}), }; } else { turnItem = { diff --git a/packages/shared/src/orchestrationV2PendingBackgroundWork.ts b/packages/shared/src/orchestrationV2PendingBackgroundWork.ts index f96662e57b242..3041d1937b17f 100644 --- a/packages/shared/src/orchestrationV2PendingBackgroundWork.ts +++ b/packages/shared/src/orchestrationV2PendingBackgroundWork.ts @@ -132,6 +132,7 @@ type PendingBackgroundWorkTurnItem = { readonly nativeId: string | null; } | null; readonly input?: unknown; + readonly monitor?: boolean | undefined; readonly prompt?: string | undefined; readonly childThreadId?: ThreadId | null; }; @@ -190,7 +191,7 @@ function pendingTaskFromTurnItem( ...(item.childThreadId == null ? {} : { childThreadId: item.childThreadId }), }; case "command_execution": - return { ...named, kind: "command" }; + return { ...named, kind: item.monitor === true ? "monitor" : "command" }; default: return { ...named, kind: "background_task" }; }