Skip to content
Open
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
30 changes: 19 additions & 11 deletions src/vs/platform/agentHost/node/claude/claudeSdkMessageRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ISessionDatabase } from '../../common/sessionDataService.js';
import { ClaudeFileEditObserver } from './claudeFileEditObserver.js';
import { ClaudeMapperState, mapSDKMessageToAgentSignals } from './claudeMapSessionEvents.js';
import type { SubagentRegistry } from './claudeSubagentRegistry.js';
import { mapSubagentSystemMessage } from './claudeSubagentSignals.js';

interface IClaudeSdkMessageContext {
readonly turnDuration?: number;
Expand Down Expand Up @@ -72,20 +73,27 @@ export class ClaudeSdkMessageRouter extends Disposable {
await this._editObserver.observeUser(message, turnId, this._mapperState);
}
if (turnId === undefined) {
// A background subagent settles with the queue already drained.
if (message.type === 'system') {
this._produceSignals(() => mapSubagentSystemMessage(message, this._chatChannelUri, this._subagents));
}
return;
}
this._produceSignals(() => mapSDKMessageToAgentSignals(
message,
this._chatChannelUri,
turnId,
this._mapperState,
this._logService,
this._subagents,
this._clientToolOwner,
context?.turnDuration,
));
}

private _produceSignals(map: () => readonly AgentSignal[]): void {
try {
const signals = mapSDKMessageToAgentSignals(
message,
this._chatChannelUri,
turnId,
this._mapperState,
this._logService,
this._subagents,
this._clientToolOwner,
context?.turnDuration,
);
for (const signal of signals) {
for (const signal of map()) {
this._onDidProduceSignal.fire(signal);
}
} catch (mapperErr) {
Expand Down
23 changes: 21 additions & 2 deletions src/vs/platform/agentHost/test/node/claudeSdkMessageRouter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ interface IRouterHarness {
readonly router: ClaudeSdkMessageRouter;
readonly signals: AgentSignal[];
readonly fileService: FileService;
readonly subagents: SubagentRegistry;
}

class RecordingAgentEditAttributionService extends NullAgentEditAttributionService {
Expand Down Expand Up @@ -88,7 +89,7 @@ function createRouter(
));
const signals: AgentSignal[] = [];
disposables.add(router.onDidProduceSignal(s => signals.push(s)));
return { router, signals, fileService };
return { router, signals, fileService, subagents };
}

function assistantMessage(content: unknown): Extract<SDKMessage, { type: 'assistant' }> {
Expand All @@ -103,12 +104,30 @@ suite('ClaudeSdkMessageRouter', () => {

const disposables = ensureNoDisposablesAreLeakedInTestSuite();

test('handle with turnId=undefined produces no signals (turn-less messages are routed to nowhere)', async () => {
test('handle with turnId=undefined drops turn-scoped messages', async () => {
const { router, signals } = createRouter(disposables);
await router.handle(makeStreamEvent('sess-1', makeMessageStart()), undefined);
assert.deepStrictEqual(signals, []);
});

test('a background subagent that settles after its turn still completes', async () => {
const { router, signals, subagents } = createRouter(disposables);
subagents.recordSpawn('tu-1');
subagents.getSpawn('tu-1')!.background = true;

// The queue has drained by the time a background task notification
// arrives, so there is no turn id to scope it to.
await router.handle({
type: 'system',
subtype: 'task_notification',
tool_use_id: 'tu-1',
status: 'completed',
} as unknown as SDKMessage, undefined);

assert.deepStrictEqual(signals.map(s => s.kind), ['subagent_completed']);
assert.strictEqual(subagents.getSpawn('tu-1'), undefined);
});

test('handle with a turnId on a text content block produces ChatResponsePart + ChatDelta signals', async () => {
const { router, signals } = createRouter(disposables);
await router.handle(makeStreamEvent('sess-1', makeMessageStart()), 'turn-1');
Expand Down