mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-21 14:47:17 +00:00
fix(agent-core-v2): emit subagent.spawned after task registration (#3005)
* fix(agent-core-v2): emit subagent.spawned after task registration
The spawned signal previously fired at launch, before the run's task
registration, so clients learned the agent id with no task id to bind
cancel/status actions to; a failed registration also left a spawned row
behind for a run that never registered. Emit it only after registerTask
succeeds and carry the task id on the event.
* fix(agent-core-v2): keep spawned ahead of started for Agent-tool runs
The TUI drops subagent.started until spawned has established the row,
and a failed registration must not leave a started row behind with no
terminal event. Defer the mirrored started dispatch so the Agent tool
can emit it itself after registration and spawned.
* fix(agent-core-v2): void the deferred started dispatch
* fix(kap-server): key Agent-tool transcript rows by the registered task id
Transcript-protocol clients suppress the raw task.*/subagent.* session
events, so they only saw a subagent row keyed by agent id that cannot
address /tasks/{id}, plus a second row once task.started landed. Key the
spawned row by the task id it now carries, fold task.started and the
subagent lifecycle back into it, and keep the agent-id path for spawns
without a registration (swarm/session-init/tower). Statement-level
ordering notes move to the file headers per package convention.
* test(agent-core-v2): split the spawned/started ordering contract into its own test
* fix(kap-server): keep subagent result details across task termination and drop stale task mappings on taskless respawns
* fix(kap-server): recover the agent-to-task association from a backfilled task.started
* fix(kap-server): seed pre-attach Agent task mappings on the transcript binding
* fix(kap-server): seed the full in-flight task row on transcript bind, not only its id
* docs(agent-core-v2): name the state-domain event dispatcher in the Agent tool header
* style(kap-server): drop comments in transcript services per the no-comments lint rule
This commit is contained in:
parent
01eeacb59b
commit
be8e017597
9 changed files with 308 additions and 16 deletions
5
.changeset/subagent-spawned-task-id.md
Normal file
5
.changeset/subagent-spawned-task-id.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
Emit subagent.spawned after the run's task registration so the signal carries the task id clients bind cancel/status actions to.
|
||||
|
|
@ -51,7 +51,8 @@ import type { Runtime } from '#/runtime/runtime';
|
|||
import { ISessionMetadata } from '#/session/sessionMetadata/sessionMetadata';
|
||||
import { ISessionWorkspaceContext } from '#/session/workspaceContext/workspaceContext';
|
||||
|
||||
import { emitAgentRunSpawned, mirrorAgentRun } from '#/session/subagent/mirrorAgentRun';
|
||||
import { emitAgentRunSpawned, mirrorAgentRun, SubagentStarted } from '#/session/subagent/mirrorAgentRun';
|
||||
import { IEventDispatcher } from '#/state/eventDispatcher';
|
||||
import { ISessionSubagentService } from '#/session/subagent/subagent';
|
||||
import {
|
||||
buildSubagentModelDescriptions,
|
||||
|
|
@ -310,15 +311,6 @@ export class SubagentTool implements ISubagentTool {
|
|||
});
|
||||
}
|
||||
|
||||
const runInBackground = args.run_in_background === true;
|
||||
emitAgentRunSpawned(requester, agentId, {
|
||||
profileName,
|
||||
parentToolCallId: toolCallId,
|
||||
description: args.description,
|
||||
runInBackground,
|
||||
model: displayModel,
|
||||
});
|
||||
|
||||
const run = await this.subagents.run(
|
||||
agentId,
|
||||
{ kind: 'prompt', prompt: promptText },
|
||||
|
|
@ -328,6 +320,7 @@ export class SubagentTool implements ISubagentTool {
|
|||
profileName,
|
||||
prompt: promptText,
|
||||
signal: controller.signal,
|
||||
deferStarted: true,
|
||||
cancel: (reason) => {
|
||||
controller.abort(reason);
|
||||
},
|
||||
|
|
@ -451,6 +444,21 @@ export class SubagentTool implements ISubagentTool {
|
|||
};
|
||||
}
|
||||
|
||||
const requester = this.lifecycle.get(this.callerAgentId);
|
||||
if (requester !== undefined) {
|
||||
emitAgentRunSpawned(requester, handle.agentId, {
|
||||
profileName: handle.profileName,
|
||||
parentToolCallId: toolCallId,
|
||||
description: args.description,
|
||||
runInBackground,
|
||||
model: handle.model,
|
||||
taskId,
|
||||
});
|
||||
void requester.accessor
|
||||
.get(IEventDispatcher)
|
||||
?.dispatch(new SubagentStarted({ subagentId: handle.agentId }));
|
||||
}
|
||||
|
||||
if (runInBackground) {
|
||||
return {
|
||||
output: formatBackgroundAgentResult(taskId, handle, args.description, allowBackground),
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export interface SubagentSpawnedPayload {
|
|||
readonly runInBackground: boolean;
|
||||
readonly model?: string;
|
||||
readonly thinkingEffort?: string;
|
||||
readonly taskId?: string;
|
||||
}
|
||||
|
||||
export class SubagentSpawned extends Event2<SubagentSpawnedPayload> {
|
||||
|
|
@ -75,6 +76,7 @@ export interface AgentRunSpawnedMeta {
|
|||
readonly swarmIndex?: number;
|
||||
readonly runInBackground?: boolean;
|
||||
readonly model?: string;
|
||||
readonly taskId?: string;
|
||||
}
|
||||
|
||||
export interface MirrorAgentRunOptions {
|
||||
|
|
@ -83,6 +85,7 @@ export interface MirrorAgentRunOptions {
|
|||
readonly suppressRateLimitFailureEvent?: boolean;
|
||||
readonly signal: AbortSignal;
|
||||
readonly cancel?: (reason?: unknown) => void;
|
||||
readonly deferStarted?: boolean;
|
||||
}
|
||||
|
||||
export function emitAgentRunSpawned(
|
||||
|
|
@ -107,6 +110,7 @@ export function emitAgentRunSpawned(
|
|||
runInBackground: meta.runInBackground ?? false,
|
||||
model: meta.model,
|
||||
thinkingEffort: childProfile?.getEffectiveThinkingLevel(),
|
||||
taskId: meta.taskId,
|
||||
}),
|
||||
);
|
||||
childProfile?.republishStatus();
|
||||
|
|
@ -127,7 +131,9 @@ export async function mirrorAgentRun(
|
|||
const dispatcher = requester.accessor.get(IEventDispatcher);
|
||||
const subagents = requester.accessor.get(ISessionSubagentService);
|
||||
const agentLifecycle = requester.accessor.get(IAgentLifecycleService);
|
||||
void dispatcher?.dispatch(new SubagentStarted({ subagentId: run.agentId }));
|
||||
if (options.deferStarted !== true) {
|
||||
void dispatcher?.dispatch(new SubagentStarted({ subagentId: run.agentId }));
|
||||
}
|
||||
if (options.prompt !== undefined) {
|
||||
const cancelAndRethrow = (reason: unknown): never => {
|
||||
options.cancel?.(reason);
|
||||
|
|
|
|||
|
|
@ -1778,6 +1778,36 @@ describe('Agent tool execution contract', () => {
|
|||
completion.resolve({ summary: 'finished later' });
|
||||
});
|
||||
|
||||
it('emits spawned with the registered task id ahead of started', async () => {
|
||||
const completion = deferred<{ readonly summary: string }>();
|
||||
const lifecycle = createAgentLifecycleStub({
|
||||
createAgentIds: ['agent-child'],
|
||||
runCompletion: () => completion.promise,
|
||||
});
|
||||
const context = createAgentToolContext(lifecycle);
|
||||
|
||||
const result = await executeAgentTool(context, {
|
||||
prompt: 'Investigate',
|
||||
description: 'Find cause',
|
||||
run_in_background: true,
|
||||
});
|
||||
|
||||
if (typeof result.output !== 'string') throw new TypeError('expected string output');
|
||||
const taskId = result.output.match(/task_id: (agent-[0-9a-z]{8})/)?.[1];
|
||||
expect(taskId).toBeDefined();
|
||||
expect(lifecycle.publishedEvents).toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: 'subagent.spawned',
|
||||
subagentId: 'agent-child',
|
||||
taskId,
|
||||
}),
|
||||
);
|
||||
const eventOrder = lifecycle.publishedEvents.map((event) => event.type);
|
||||
expect(eventOrder.indexOf('subagent.spawned')).toBeGreaterThanOrEqual(0);
|
||||
expect(eventOrder.indexOf('subagent.started')).toBeGreaterThan(eventOrder.indexOf('subagent.spawned'));
|
||||
completion.resolve({ summary: 'finished later' });
|
||||
});
|
||||
|
||||
it('rejects background subagents when background execution is disabled', async () => {
|
||||
const lifecycle = createAgentLifecycleStub();
|
||||
const context = createAgentToolContext(lifecycle);
|
||||
|
|
@ -1878,6 +1908,11 @@ describe('Agent tool execution contract', () => {
|
|||
output: 'Too many background tasks are already running.',
|
||||
});
|
||||
expect(lifecycle.create).toHaveBeenCalledTimes(2);
|
||||
expect(
|
||||
lifecycle.publishedEvents.filter(
|
||||
(event) => (event as { subagentId?: string }).subagentId === 'agent-second',
|
||||
),
|
||||
).toEqual([]);
|
||||
completions[0]?.resolve({ summary: 'finished later' });
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -845,6 +845,7 @@ export const subagentSpawnedEventSchema = z.object({
|
|||
runInBackground: z.boolean(),
|
||||
model: z.string().optional(),
|
||||
thinkingEffort: z.string().optional(),
|
||||
taskId: z.string().optional(),
|
||||
}) satisfies z.ZodType<SubagentSpawnedPayload>;
|
||||
|
||||
export const subagentStartedEventSchema = z.object({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
IAgentLifecycleService,
|
||||
IAgentActivityView,
|
||||
IAgentTaskService,
|
||||
IEventBus,
|
||||
ISessionMetadata,
|
||||
ISessionInteractionService,
|
||||
|
|
@ -103,6 +104,25 @@ export function bindSessionTranscript(
|
|||
},
|
||||
turn: (turnId) => store.getAgent(agentId)?.getTurn(turnId),
|
||||
});
|
||||
for (const agent of agents.list()) {
|
||||
if (agent.id !== agentId) continue;
|
||||
const tasks = agent.accessor.get(IAgentTaskService)?.list() ?? [];
|
||||
for (const info of tasks) {
|
||||
if (info.kind === 'agent' && typeof info.agentId === 'string' && info.agentId.length > 0) {
|
||||
applyOps(
|
||||
agentId,
|
||||
projector.seedSubagentTask({
|
||||
taskId: info.taskId,
|
||||
agentId: info.agentId,
|
||||
description: info.description,
|
||||
status: info.status,
|
||||
detached: info.detached ?? false,
|
||||
startedAt: info.startedAt,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
projectors.set(agentId, projector);
|
||||
}
|
||||
return projector;
|
||||
|
|
|
|||
|
|
@ -211,6 +211,40 @@ export class AgentTranscriptProjector {
|
|||
private readonly tasks = new Map<string, TranscriptTask>();
|
||||
/** shell `commandId` → transcript `taskId` (`shell.output` is keyed by command id only). */
|
||||
private readonly shellTasks = new Map<string, string>();
|
||||
/** subagent agent id → registered task id, for Agent-tool runs whose spawned
|
||||
carried the registration (`taskId`): the task row keys by the task id so
|
||||
`/tasks/{id}` actions resolve, and lifecycle events fold back to it. */
|
||||
private readonly subagentTaskIds = new Map<string, string>();
|
||||
|
||||
/** Pre-seed the association and the row for a task registered before
|
||||
attach: a foreground Agent run emits no `task.started` at all, so
|
||||
without this a late-bound projector never learns the mapping, shows no
|
||||
cancellable row, and lets the terminal event invent foreground-wrong
|
||||
defaults. Only in-flight tasks seed (a terminal one has no lifecycle
|
||||
left to fold). */
|
||||
seedSubagentTask(info: {
|
||||
readonly taskId: string;
|
||||
readonly agentId: string;
|
||||
readonly description: string;
|
||||
readonly status: string;
|
||||
readonly detached: boolean;
|
||||
readonly startedAt: number;
|
||||
}): TranscriptOperation[] {
|
||||
if (info.status !== 'running') return [];
|
||||
this.subagentTaskIds.set(info.agentId, info.taskId);
|
||||
const task = this.upsertTask(info.taskId, (prev) => ({
|
||||
taskId: info.taskId,
|
||||
kind: 'subagent',
|
||||
state: 'running',
|
||||
detached: info.detached,
|
||||
description: info.description,
|
||||
agentId: info.agentId,
|
||||
outputTail: prev?.outputTail ?? '',
|
||||
startedAt: prev?.startedAt ?? epochMsToIso(info.startedAt),
|
||||
endedAt: prev?.endedAt,
|
||||
}));
|
||||
return [{ op: 'task.upsert', task }];
|
||||
}
|
||||
/** interaction id → the pending entity as last emitted (resolve spreads it). */
|
||||
private readonly interactions = new Map<string, TranscriptInteraction>();
|
||||
/** promptId → the prompt queue entity as last emitted (`prompt.upsert` replaces). */
|
||||
|
|
@ -871,9 +905,16 @@ export class AgentTranscriptProjector {
|
|||
outputTail: prev?.outputTail ?? '',
|
||||
startedAt: prev?.startedAt ?? epochMsToIso(info.startedAt),
|
||||
endedAt: info.endedAt === null ? prev?.endedAt : epochMsToIso(info.endedAt),
|
||||
resultSummary: prev?.resultSummary,
|
||||
usage: prev?.usage,
|
||||
error: prev?.error,
|
||||
stateReason: prev?.stateReason,
|
||||
}));
|
||||
const ops: TranscriptOperation[] = [{ op: 'task.upsert', task }];
|
||||
if (event.type === 'task.started') {
|
||||
if (info.kind === 'agent' && typeof info.agentId === 'string' && info.agentId.length > 0) {
|
||||
this.subagentTaskIds.set(info.agentId, info.taskId);
|
||||
}
|
||||
ops.push({
|
||||
op: 'taskref.upsert',
|
||||
item: { kind: 'taskref', refId: `ref-${info.taskId}`, taskId: info.taskId, at: nowIso() },
|
||||
|
|
@ -1004,9 +1045,16 @@ export class AgentTranscriptProjector {
|
|||
description?: string;
|
||||
swarmIndex?: number;
|
||||
runInBackground: boolean;
|
||||
taskId?: string;
|
||||
}): TranscriptOperation[] {
|
||||
const task = this.upsertTask(event.subagentId, (prev) => ({
|
||||
taskId: event.subagentId,
|
||||
const taskKey = event.taskId ?? event.subagentId;
|
||||
if (event.taskId !== undefined) {
|
||||
this.subagentTaskIds.set(event.subagentId, event.taskId);
|
||||
} else {
|
||||
this.subagentTaskIds.delete(event.subagentId);
|
||||
}
|
||||
const task = this.upsertTask(taskKey, (prev) => ({
|
||||
taskId: taskKey,
|
||||
kind: 'subagent',
|
||||
state: 'running',
|
||||
detached: event.runInBackground,
|
||||
|
|
@ -1048,8 +1096,9 @@ export class AgentTranscriptProjector {
|
|||
: event.type === 'subagent.failed'
|
||||
? 'failed'
|
||||
: 'running';
|
||||
const task = this.upsertTask(event.subagentId, (prev) => ({
|
||||
taskId: event.subagentId,
|
||||
const taskKey = this.subagentTaskIds.get(event.subagentId) ?? event.subagentId;
|
||||
const task = this.upsertTask(taskKey, (prev) => ({
|
||||
taskId: taskKey,
|
||||
kind: 'subagent',
|
||||
state,
|
||||
detached: prev?.detached ?? true,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { join } from 'node:path';
|
|||
import {
|
||||
IAgentLifecycleService,
|
||||
IAgentLoopService,
|
||||
IAgentTaskService,
|
||||
IEventBus,
|
||||
ISessionIndex,
|
||||
ISessionInteractionService,
|
||||
|
|
@ -970,6 +971,124 @@ describe('AgentTranscriptProjector', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('keys an Agent-tool subagent row by its registered task id and folds the lifecycle', () => {
|
||||
const projector = new AgentTranscriptProjector('main');
|
||||
const tx = new AgentTranscript('main');
|
||||
const feed = (event: ProjectorBusEvent): void => void tx.apply(projector.map(event));
|
||||
|
||||
feed(
|
||||
ev({
|
||||
type: 'subagent.spawned',
|
||||
subagentId: 'agent-1',
|
||||
subagentName: 'explore',
|
||||
parentToolCallId: 'call-1',
|
||||
description: 'Inspect files',
|
||||
runInBackground: true,
|
||||
taskId: 'task-9',
|
||||
}),
|
||||
);
|
||||
feed(
|
||||
ev({
|
||||
type: 'task.started',
|
||||
info: {
|
||||
taskId: 'task-9',
|
||||
kind: 'agent',
|
||||
description: 'Inspect files',
|
||||
status: 'running',
|
||||
detached: true,
|
||||
agentId: 'agent-1',
|
||||
startedAt: 1_700_000_000_000,
|
||||
endedAt: null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
feed(ev({ type: 'subagent.completed', subagentId: 'agent-1', resultSummary: 'done' }));
|
||||
feed(
|
||||
ev({
|
||||
type: 'task.terminated',
|
||||
info: {
|
||||
taskId: 'task-9',
|
||||
kind: 'agent',
|
||||
description: 'Inspect files',
|
||||
status: 'completed',
|
||||
detached: true,
|
||||
agentId: 'agent-1',
|
||||
startedAt: 1_700_000_000_000,
|
||||
endedAt: 1_700_000_001_000,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(tx.getTask('task-9')).toMatchObject({
|
||||
kind: 'subagent',
|
||||
state: 'completed',
|
||||
agentId: 'agent-1',
|
||||
description: 'Inspect files',
|
||||
detached: true,
|
||||
resultSummary: 'done',
|
||||
});
|
||||
expect(tx.getTask('agent-1')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('drops the stale task mapping when a child respawns without a task id', () => {
|
||||
const projector = new AgentTranscriptProjector('main');
|
||||
const tx = new AgentTranscript('main');
|
||||
const feed = (event: ProjectorBusEvent): void => void tx.apply(projector.map(event));
|
||||
|
||||
feed(
|
||||
ev({
|
||||
type: 'subagent.spawned',
|
||||
subagentId: 'agent-1',
|
||||
subagentName: 'explore',
|
||||
parentToolCallId: 'call-1',
|
||||
description: 'Inspect files',
|
||||
runInBackground: true,
|
||||
taskId: 'task-9',
|
||||
}),
|
||||
);
|
||||
feed(ev({ type: 'subagent.completed', subagentId: 'agent-1', resultSummary: 'done' }));
|
||||
feed(
|
||||
ev({
|
||||
type: 'subagent.spawned',
|
||||
subagentId: 'agent-1',
|
||||
subagentName: 'worker',
|
||||
parentToolCallId: 'call-2',
|
||||
description: 'scan again',
|
||||
runInBackground: false,
|
||||
}),
|
||||
);
|
||||
feed(ev({ type: 'subagent.started', subagentId: 'agent-1' }));
|
||||
|
||||
expect(tx.getTask('task-9')).toMatchObject({ state: 'completed', resultSummary: 'done' });
|
||||
expect(tx.getTask('agent-1')).toMatchObject({ kind: 'subagent', state: 'running' });
|
||||
});
|
||||
|
||||
it('recovers the agent → task association from a backfilled task.started', () => {
|
||||
const projector = new AgentTranscriptProjector('main');
|
||||
const tx = new AgentTranscript('main');
|
||||
const feed = (event: ProjectorBusEvent): void => void tx.apply(projector.map(event));
|
||||
|
||||
feed(
|
||||
ev({
|
||||
type: 'task.started',
|
||||
info: {
|
||||
taskId: 'task-9',
|
||||
kind: 'agent',
|
||||
description: 'Inspect files',
|
||||
status: 'running',
|
||||
detached: true,
|
||||
agentId: 'agent-1',
|
||||
startedAt: 1_700_000_000_000,
|
||||
endedAt: null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
feed(ev({ type: 'subagent.completed', subagentId: 'agent-1', resultSummary: 'done' }));
|
||||
|
||||
expect(tx.getTask('task-9')).toMatchObject({ state: 'completed', resultSummary: 'done' });
|
||||
expect(tx.getTask('agent-1')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('projects goal updates into meta.goal plus an inline marker', () => {
|
||||
const projector = new AgentTranscriptProjector('main');
|
||||
const tx = new AgentTranscript('main');
|
||||
|
|
@ -1811,7 +1930,7 @@ describe('bindSessionTranscript', () => {
|
|||
this.disposeHandlers.add(cb);
|
||||
return { dispose: () => this.disposeHandlers.delete(cb) };
|
||||
}
|
||||
add(id: string, opts?: { loopStatus?: unknown }): FakeAgentHandle {
|
||||
add(id: string, opts?: { loopStatus?: unknown; tasks?: readonly unknown[] }): FakeAgentHandle {
|
||||
const bus = new FakeBus();
|
||||
const handle: FakeAgentHandle = {
|
||||
id,
|
||||
|
|
@ -1822,6 +1941,9 @@ describe('bindSessionTranscript', () => {
|
|||
if (token === IAgentLoopService) {
|
||||
return { status: () => opts?.loopStatus ?? { state: 'idle' } };
|
||||
}
|
||||
if (token === IAgentTaskService) {
|
||||
return { list: () => opts?.tasks ?? [] };
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
},
|
||||
|
|
@ -1911,6 +2033,46 @@ describe('bindSessionTranscript', () => {
|
|||
binding.dispose();
|
||||
});
|
||||
|
||||
it('seeds pre-attach Agent task mappings so a late-bound projector folds the lifecycle', () => {
|
||||
const agents = new FakeAgents();
|
||||
agents.add('main', {
|
||||
tasks: [
|
||||
{
|
||||
taskId: 'task-9',
|
||||
kind: 'agent',
|
||||
agentId: 'agent-1',
|
||||
status: 'running',
|
||||
description: 'Inspect',
|
||||
detached: false,
|
||||
startedAt: 1_700_000_000_000,
|
||||
},
|
||||
],
|
||||
});
|
||||
const store = new TranscriptStore('s1');
|
||||
const binding = bindSessionTranscript(
|
||||
store,
|
||||
fakeSession(new SessionInteractionService(new TestSessionStateService()), agents),
|
||||
);
|
||||
|
||||
expect(store.getAgent('main')?.getTask('task-9')).toMatchObject({
|
||||
kind: 'subagent',
|
||||
state: 'running',
|
||||
detached: false,
|
||||
description: 'Inspect',
|
||||
agentId: 'agent-1',
|
||||
});
|
||||
|
||||
agents.get('main')!.bus.emit(ev({ type: 'subagent.completed', subagentId: 'agent-1', resultSummary: 'done' }));
|
||||
|
||||
expect(store.getAgent('main')?.getTask('task-9')).toMatchObject({
|
||||
state: 'completed',
|
||||
resultSummary: 'done',
|
||||
detached: false,
|
||||
});
|
||||
expect(store.getAgent('main')?.getTask('agent-1')).toBeUndefined();
|
||||
binding.dispose();
|
||||
});
|
||||
|
||||
const SHOT_PNG_UPLOAD = {
|
||||
type: 'file',
|
||||
file_id: 'file_1',
|
||||
|
|
|
|||
|
|
@ -864,6 +864,11 @@ export interface SubagentSpawnedEvent {
|
|||
/** The child's effective thinking effort at spawn (same vocabulary as
|
||||
* `agent.status.updated`). Optional for cross-version tolerance. */
|
||||
readonly thinkingEffort?: string;
|
||||
/** Background-task id the run registered under in the caller's task store.
|
||||
* Emitted after task registration, so cancel/status actions can bind to
|
||||
* the task store without waiting for `task.started`. Optional for
|
||||
* cross-version tolerance (older producers never send it). */
|
||||
readonly taskId?: string;
|
||||
}
|
||||
|
||||
export interface SubagentStartedEvent {
|
||||
|
|
@ -1800,6 +1805,7 @@ export const subagentSpawnedEventSchema = z.object({
|
|||
runInBackground: z.boolean(),
|
||||
model: z.string().optional(),
|
||||
thinkingEffort: z.string().optional(),
|
||||
taskId: z.string().optional(),
|
||||
}) satisfies z.ZodType<SubagentSpawnedEvent>;
|
||||
|
||||
export const subagentStartedEventSchema = z.object({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue