Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3444,6 +3444,51 @@ describe("ClaudeAdapterV2 background wake turns", () => {
),
);

it.effect("rosters an artifact live-updates watcher as a monitor, not a subagent", () =>
Effect.scoped(
Effect.gen(function* () {
const harness = yield* makeWakeHarness;
const now = yield* DateTime.now;
const taskId = "artifact-watch-1";
const description = "live updates for artifact https://claude.ai/artifact/abc";
yield* harness.runtime.startTurn(
makeClaudeTestTurnInput({
threadId: harness.threadId,
providerThread: harness.providerThread,
now,
attemptId: RunAttemptId.make("attempt-claude-artifact-watch"),
text: "Publish the report.",
attachments: [],
}),
);
yield* Queue.offer(
harness.sdkMessages,
claudeSdkFrame({
type: "system",
subtype: "task_started",
task_id: taskId,
tool_use_id: "toolu_artifact_1",
description,
task_type: "monitor_ws",
uuid: "00000000-0000-4000-8000-000000000901",
session_id: WAKE_NATIVE_SESSION,
}),
);
yield* Queue.offer(
harness.sdkMessages,
makeResultFrame({ uuid: "00000000-0000-4000-8000-000000000902", result: "Published." }),
);
yield* awaitUntil(() => harness.terminalEvents().length === 1, "turn terminal");

assert.deepEqual(
providerThreadRosterEvents(harness.events).at(-1)?.providerThread.pendingBackgroundTasks,
[{ taskId, kind: "monitor", description }],
);
assert.isFalse(harness.events.some((event) => event.type === "subagent.updated"));
}).pipe(Effect.provide(Layer.merge(IdAllocator.layer, NodeServices.layer))),
),
);

it.effect("stops background work after the turn settled", () =>
Effect.scoped(
Effect.gen(function* () {
Expand Down
9 changes: 7 additions & 2 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1674,11 +1674,16 @@ function commandInputFromClaudeTool(toolName: string, input: ClaudeNativeToolInp
// Opaque non-subagent background work admitted onto the Waiting roster, by
// Claude SDK `task_type`, and the kind the roster names it by. Subagents
// project through the normal subagent lifecycle and must not be
// double-counted when background_tasks_changed includes them.
// double-counted when background_tasks_changed includes them. monitor_ws is
// the websocket watch Claude Code arms for a published artifact; it runs no
// model, so it is a monitor, not a subagent.
const CLAUDE_OPAQUE_BACKGROUND_TASK_KINDS: ReadonlyMap<
string,
Exclude<OrchestrationV2PendingBackgroundTask["kind"], "subagent">
> = new Map([["local_bash", "command"]]);
> = new Map([
["local_bash", "command"],
["monitor_ws", "monitor"],
]);

function isClaudeOpaqueBackgroundTaskType(taskType: string | null | undefined): boolean {
return typeof taskType === "string" && CLAUDE_OPAQUE_BACKGROUND_TASK_KINDS.has(taskType);
Expand Down
Loading