From 16e7ffff013b45a509fc95df667205496acaf7bd Mon Sep 17 00:00:00 2001 From: qer Date: Fri, 30 Oct 2026 03:04:38 +0800 Subject: [PATCH 01/13] feat(agent-core): emit task changes and register swarm members as tasks The task service now publishes onDidChangeTask whenever a task is registered (foreground tasks included), moved to the background, ended, or marked lost after a restart, and refresh(taskId) republishes a running task whose details changed. Clients get live task state straight from the engine instead of folding wire records. Each swarm member is registered as a foreground agent task of the caller when the swarm starts. The task records the member's child agent, spawning tool call and swarm index, its suspension reason while rate limited, and its result summary when it settles. Stopping a member stops the whole swarm, which is the only cancellation the swarm supports. Foreground tasks are not persisted and send no notifications, so the model and the disk see nothing new. Background task output is read by range instead of loading the whole file, readOutputTail and onDidAppendOutput serve task output streaming, and task.terminated records no longer carry outputTail. --- .../agent-core-v2/docs/wire-manifest.d.ts | 1 - .../agent-core-v2/src/agent/task/persist.ts | 20 +++ packages/agent-core-v2/src/agent/task/task.ts | 17 +++ .../agent-core-v2/src/agent/task/taskOps.ts | 2 - .../src/agent/task/taskService.ts | 59 ++++++-- .../src/agent/tools/agent/subagent-task.ts | 3 + .../features/swarm/session/agentRunBatch.ts | 22 ++- .../swarm/session/sessionSwarmService.ts | 20 ++- .../features/swarm/session/swarmMemberTask.ts | 132 ++++++++++++++++++ .../test/agent/task/output-access.test.ts | 49 +++++++ .../test/agent/task/taskManager.test.ts | 58 ++++++++ .../test/agent/task/taskOps.test.ts | 33 ----- .../test/agent/task/taskService.test.ts | 42 ------ .../test/agent/task/tools/task-tools.test.ts | 10 ++ .../test/features/swarm/sessionSwarm.test.ts | 92 ++++++++++++ .../os/backends/node-local/tools/bash.test.ts | 9 ++ 16 files changed, 471 insertions(+), 98 deletions(-) create mode 100644 packages/agent-core-v2/src/features/swarm/session/swarmMemberTask.ts diff --git a/packages/agent-core-v2/docs/wire-manifest.d.ts b/packages/agent-core-v2/docs/wire-manifest.d.ts index 05d372bef8f..b344fd4067e 100644 --- a/packages/agent-core-v2/docs/wire-manifest.d.ts +++ b/packages/agent-core-v2/docs/wire-manifest.d.ts @@ -725,7 +725,6 @@ interface TaskTerminatedPayload { agentId: string; /** AgentTaskInfo */ info: AgentTaskInfoByKind[AgentTaskKind]; - outputTail?: string; } /** diff --git a/packages/agent-core-v2/src/agent/task/persist.ts b/packages/agent-core-v2/src/agent/task/persist.ts index 823534191b3..8abe2396a6d 100644 --- a/packages/agent-core-v2/src/agent/task/persist.ts +++ b/packages/agent-core-v2/src/agent/task/persist.ts @@ -203,6 +203,26 @@ export class AgentTaskPersistence { return textDecoder.decode(output.data.subarray(start, end)); } + async readTaskOutputTail( + taskId: string, + maxBytes: number, + ): Promise<{ readonly offset: number; readonly data: Uint8Array } | undefined> { + const roots = this.fallbackRoot === undefined ? [this.primaryRoot()] : [this.primaryRoot(), this.fallbackRoot]; + for (const root of roots) { + const scope = this.taskOutputScope(taskId, root); + const size = await this.bytes.size(scope, OUTPUT_LOG_KEY); + if (size === undefined) continue; + const offset = Math.max(0, size - Math.max(0, Math.trunc(maxBytes))); + if (offset === size) return { offset, data: new Uint8Array() }; + const parts: Uint8Array[] = []; + for await (const part of this.bytes.readStream(scope, OUTPUT_LOG_KEY, { start: offset, end: size - 1 })) { + parts.push(part); + } + return { offset, data: Buffer.concat(parts) }; + } + return undefined; + } + async readTaskOutputSnapshot( taskId: string, maxPreviewBytes: number, diff --git a/packages/agent-core-v2/src/agent/task/task.ts b/packages/agent-core-v2/src/agent/task/task.ts index 2a362988b06..00410258107 100644 --- a/packages/agent-core-v2/src/agent/task/task.ts +++ b/packages/agent-core-v2/src/agent/task/task.ts @@ -1,4 +1,5 @@ import { createDecorator } from '#/_base/di/instantiation'; +import type { Event } from '#/_base/event'; import type { ITaskHandle } from '#/app/task/task'; import type { AgentTask, @@ -29,6 +30,17 @@ export interface AgentTaskOutputSnapshot { readonly preview: string; } +export interface AgentTaskOutputChunk { + readonly taskId: string; + readonly offset: number; + readonly text: string; +} + +export interface AgentTaskOutputTail { + readonly offset: number; + readonly data: Uint8Array; +} + export interface RegisterAgentTaskOptions { readonly detached?: boolean; readonly timeoutMs?: number; @@ -74,9 +86,13 @@ export interface AgentTaskWaitDelivery { export interface IAgentTaskService { readonly _serviceBrand: undefined; + readonly onDidAppendOutput: Event; + readonly onDidChangeTask: Event; + track(handle: ITaskHandle, options: AgentTaskTrackOptions): IAgentTaskEntry; registerTask(task: AgentTask, options?: RegisterAgentTaskOptions): string; getTask(taskId: string): AgentTaskInfo | undefined; + refresh(taskId: string): void; list(activeOnly?: boolean, limit?: number): readonly AgentTaskInfo[]; persistOutput(taskId: string): void; getOutputSnapshot( @@ -84,6 +100,7 @@ export interface IAgentTaskService { maxPreviewBytes: number, ): Promise; readOutput(taskId: string, tail?: number): Promise; + readOutputTail(taskId: string, maxBytes: number): Promise; suppressTerminalNotification(taskId: string): Promise; suppressAllTerminalNotifications(): Promise; markTasksDeliveredViaWait(tasks: readonly AgentTaskWaitDelivery[]): void; diff --git a/packages/agent-core-v2/src/agent/task/taskOps.ts b/packages/agent-core-v2/src/agent/task/taskOps.ts index cc6aebc15ac..0b0192ee4d3 100644 --- a/packages/agent-core-v2/src/agent/task/taskOps.ts +++ b/packages/agent-core-v2/src/agent/task/taskOps.ts @@ -28,7 +28,6 @@ export interface TaskStarted { const taskTerminatedSchema = z.object({ agentId: z.string(), info: z.custom(), - outputTail: z.string().optional(), }); export class TaskTerminated extends AgentEvent2> { @@ -39,7 +38,6 @@ export class TaskTerminated extends AgentEvent2( export class AgentTaskService extends Disposable implements IAgentTaskService { declare readonly _serviceBrand: undefined; + private readonly _onDidAppendOutput = this._register(new Emitter()); + readonly onDidAppendOutput: Event = this._onDidAppendOutput.event; + private readonly _onDidChangeTask = this._register(new Emitter()); + readonly onDidChangeTask: Event = this._onDidChangeTask.event; + private readonly tasks = new Map(); private exitSuppressionArmed = false; private readonly buildingNotificationKeys = new Set(); @@ -426,6 +432,7 @@ export class AgentTaskService extends Disposable implements IAgentTaskService { void this.persistLive(entry); this.recordTaskStarted(this.toInfo(entry)); } + this.fireChange(entry); return entry.taskId; } @@ -498,6 +505,7 @@ export class AgentTaskService extends Disposable implements IAgentTaskService { void this.persistLive(entry); this.recordTaskStarted(this.toInfo(entry)); } + this.fireChange(entry); return { taskId, @@ -510,6 +518,16 @@ export class AgentTaskService extends Disposable implements IAgentTaskService { return entry === undefined ? this.ghosts.get(taskId) : this.toInfo(entry); } + refresh(taskId: string): void { + const entry = this.tasks.get(taskId); + if (entry === undefined || TERMINAL_STATUSES.has(entry.status)) return; + this.fireChange(entry); + } + + private fireChange(entry: ManagedTask): void { + this._onDidChangeTask.fire(this.toInfo(entry)); + } + list(activeOnly = true, limit?: number): readonly AgentTaskInfo[] { const result: AgentTaskInfo[] = []; for (const entry of this.tasks.values()) { @@ -609,6 +627,25 @@ export class AgentTaskService extends Disposable implements IAgentTaskService { }; } + async readOutputTail(taskId: string, maxBytes: number): Promise { + if (this.getTask(taskId) === undefined) return { offset: 0, data: new Uint8Array() }; + + await this.tasks.get(taskId)?.outputWriteQueue; + + const persisted = await this.persistence.readTaskOutputTail(taskId, maxBytes); + if (persisted !== undefined) return persisted; + + const entry = this.tasks.get(taskId); + if (entry === undefined) return { offset: 0, data: new Uint8Array() }; + + const available = Buffer.from(entry.outputChunks.join(''), 'utf-8'); + const tailBytes = Math.min(Math.max(0, Math.trunc(maxBytes)), available.byteLength); + return { + offset: entry.outputSizeBytes - tailBytes, + data: available.subarray(available.byteLength - tailBytes), + }; + } + async readOutput(taskId: string, tail?: number): Promise { const output = (await this.getOutputSnapshot(taskId, Number.MAX_SAFE_INTEGER)).preview; if (tail === undefined) return output; @@ -676,6 +713,7 @@ export class AgentTaskService extends Disposable implements IAgentTaskService { void this.persistLive(entry); this.recordTaskStarted(this.toInfo(entry)); foregroundRelease.resolve(viaTimeout ? 'timeout_detached' : 'detached'); + this.fireChange(entry); return this.toInfo(entry); } @@ -971,6 +1009,7 @@ export class AgentTaskService extends Disposable implements IAgentTaskService { this.ghosts.set(taskId, updated); await persistence.writeTask(updated); lostTasks.push(updated); + this._onDidChangeTask.fire(updated); } return lostTasks; } @@ -986,6 +1025,7 @@ export class AgentTaskService extends Disposable implements IAgentTaskService { private appendOutput(entry: ManagedTask, chunk: string): void { const chunkBytes = Buffer.byteLength(chunk, 'utf-8'); + const offset = entry.outputSizeBytes; entry.outputSizeBytes += chunkBytes; this.appendRetainedOutput(entry, chunk, chunkBytes); @@ -999,6 +1039,7 @@ export class AgentTaskService extends Disposable implements IAgentTaskService { } if (entry.outputLimitTripped) return; + this._onDidAppendOutput.fire({ taskId: entry.taskId, offset, text: chunk }); if (!entry.outputPersistStarted) { entry.pendingOutput.push(chunk); @@ -1085,6 +1126,7 @@ export class AgentTaskService extends Disposable implements IAgentTaskService { this.fireTerminalEffects(entry); foregroundRelease?.resolve('terminal'); this.resolveWaiters(entry); + this.fireChange(entry); return true; } @@ -1096,14 +1138,7 @@ export class AgentTaskService extends Disposable implements IAgentTaskService { void this.notifyAgentTask(info).catch((error) => { this.log.error('task notification delivery failed', { taskId: info.taskId, error }); }); - this.recordTaskTerminated(info, this.retainedOutputTail(entry)); - } - - private retainedOutputTail(entry: ManagedTask): string | undefined { - if (entry.outputChunks.length === 0) return undefined; - const retained = Buffer.from(entry.outputChunks.join(''), 'utf-8'); - const offset = Math.max(0, retained.byteLength - TERMINAL_OUTPUT_TAIL_BYTES); - return retained.subarray(offset).toString('utf-8'); + this.recordTaskTerminated(info); } private recordTaskStarted(info: AgentTaskInfo): void { @@ -1118,10 +1153,10 @@ export class AgentTaskService extends Disposable implements IAgentTaskService { }); } - private recordTaskTerminated(info: AgentTaskInfo, outputTail?: string): void { + private recordTaskTerminated(info: AgentTaskInfo): void { if (this.lifecycleActive()) { void this.dispatcher.dispatch( - new TaskTerminated({ agentId: this.scopeContext.agentId, info, outputTail }), + new TaskTerminated({ agentId: this.scopeContext.agentId, info }), ); } else { this.log.warn('task terminal event dropped: agent lifecycle inactive', { diff --git a/packages/agent-core-v2/src/agent/tools/agent/subagent-task.ts b/packages/agent-core-v2/src/agent/tools/agent/subagent-task.ts index 91428f8358a..fab20fd45cd 100644 --- a/packages/agent-core-v2/src/agent/tools/agent/subagent-task.ts +++ b/packages/agent-core-v2/src/agent/tools/agent/subagent-task.ts @@ -44,6 +44,9 @@ export interface SubagentTaskInfo extends AgentTaskInfoBase { readonly model?: string; readonly thinkingEffort?: string; readonly stopCode?: string; + readonly swarmIndex?: number; + readonly stateReason?: string; + readonly resultSummary?: string; } declare module '#/agent/task/types' { diff --git a/packages/agent-core-v2/src/features/swarm/session/agentRunBatch.ts b/packages/agent-core-v2/src/features/swarm/session/agentRunBatch.ts index f96c5a1f73b..835ecee1066 100644 --- a/packages/agent-core-v2/src/features/swarm/session/agentRunBatch.ts +++ b/packages/agent-core-v2/src/features/swarm/session/agentRunBatch.ts @@ -99,8 +99,10 @@ type ActiveAttempt = { timedOut: boolean; }; -export type AgentRunBatchOptions = { +export type AgentRunBatchOptions = { readonly maxConcurrency?: number; + readonly onStarted?: (task: QueuedAgentRunTask, agentId: string) => void; + readonly onSettled?: (result: AgentRunResult) => void; }; export class AgentRunBatch { @@ -131,7 +133,7 @@ export class AgentRunBatch { constructor( private readonly launcher: AgentRunBatchLauncher, tasks: readonly QueuedAgentRunTask[], - options: AgentRunBatchOptions = {}, + private readonly options: AgentRunBatchOptions = {}, ) { this.maxConcurrency = options.maxConcurrency; this.states = tasks.map((task, index) => ({ @@ -312,6 +314,7 @@ export class AgentRunBatch { } attempt.state.agentId = handle.agentId; + this.options.onStarted?.(task, handle.agentId); try { const completion = await handle.completion; return { @@ -370,7 +373,7 @@ export class AgentRunBatch { if (this.finished) return; if ('status' in outcome) { - this.results[attempt.state.index] = outcome; + this.record(attempt.state.index, outcome); } else if (this.isOnlyUnfinishedTask(attempt.state)) { this.launcher.abandoned?.({ task: attempt.state.task, @@ -378,13 +381,13 @@ export class AgentRunBatch { outcome: 'failed', error: outcome.error, }); - this.results[attempt.state.index] = { + this.record(attempt.state.index, { task: attempt.state.task, agentId: outcome.agentId, status: 'failed', state: 'started', error: outcome.error, - }; + }); } else { this.requeueRateLimited(attempt, outcome.agentId); } @@ -394,15 +397,20 @@ export class AgentRunBatch { private handleAttemptError(attempt: ActiveAttempt, error: unknown): void { if (!this.releaseAttempt(attempt)) return; if (this.finished) return; - this.results[attempt.state.index] = { + this.record(attempt.state.index, { task: attempt.state.task, agentId: attempt.state.agentId, status: 'failed', error: error instanceof Error ? error.message : String(error), - }; + }); this.schedule(); } + private record(index: number, result: AgentRunResult): void { + this.results[index] = result; + this.options.onSettled?.(result); + } + private releaseAttempt(attempt: ActiveAttempt): boolean { if (!this.active.delete(attempt)) return false; attempt.cleanup(); diff --git a/packages/agent-core-v2/src/features/swarm/session/sessionSwarmService.ts b/packages/agent-core-v2/src/features/swarm/session/sessionSwarmService.ts index baa7ef3691f..5e7b881011a 100644 --- a/packages/agent-core-v2/src/features/swarm/session/sessionSwarmService.ts +++ b/packages/agent-core-v2/src/features/swarm/session/sessionSwarmService.ts @@ -4,6 +4,7 @@ import { Error2, ErrorCodes } from '#/errors'; import { linkAbortSignal } from '#/_base/utils/abort'; import type { IAgentScopeHandle } from '#/_base/di/scope'; import { IAgentProfileService } from '#/agent/profile/profile'; +import { IAgentTaskService } from '#/agent/task/task'; import { IAgentLoopService } from '#/agent/loop/loop'; import { IAgentPermissionModeService } from '#/agent/permissionMode/permissionMode'; import { Event2 } from '#/app/event/event2'; @@ -43,6 +44,7 @@ import { type AgentRunBatchLauncher, type AgentRunAttemptHandle, } from './agentRunBatch'; +import { SwarmMemberTasks } from './swarmMemberTask'; export interface SubagentSuspendedPayload { readonly subagentId: string; @@ -93,6 +95,11 @@ export class SessionSwarmService implements ISessionSwarmService { if (task.signal !== undefined) unlinks.push(linkAbortSignal(task.signal, controller)); return { ...task, signal: controller.signal }; }); + const members = new SwarmMemberTasks( + this.agentLifecycle.handleOf(callerAgentId)?.accessor.get(IAgentTaskService), + linkedTasks, + (reason) => controller.abort(reason), + ); const terminalized = new Set(); const terminalize: TerminalizeSubagent = (agentId, event) => { if (terminalized.has(agentId)) return; @@ -111,6 +118,7 @@ export class SessionSwarmService implements ISessionSwarmService { reason: event.reason, }), ); + members.suspended(event.task, event.reason); }, abandoned: (event) => { terminalize( @@ -125,7 +133,17 @@ export class SessionSwarmService implements ISessionSwarmService { }, }; const maxConcurrency = resolveSwarmMaxConcurrency(); - const promise = new AgentRunBatch(launcher, linkedTasks, { maxConcurrency }).run(); + const promise = new AgentRunBatch(launcher, linkedTasks, { + maxConcurrency, + onStarted: (task, agentId) => members.started(task, agentId), + onSettled: (result) => members.settled(result), + }).run(); + void promise.then( + (results) => { + for (const result of results) members.settled(result); + }, + (error: unknown) => members.failAll(error), + ); void promise .finally(() => { for (const unlink of unlinks) unlink(); diff --git a/packages/agent-core-v2/src/features/swarm/session/swarmMemberTask.ts b/packages/agent-core-v2/src/features/swarm/session/swarmMemberTask.ts new file mode 100644 index 00000000000..d837705a312 --- /dev/null +++ b/packages/agent-core-v2/src/features/swarm/session/swarmMemberTask.ts @@ -0,0 +1,132 @@ +import type { IAgentTaskService } from '#/agent/task/task'; +import type { AgentTask, AgentTaskInfoBase, AgentTaskSettlement, AgentTaskSink } from '#/agent/task/types'; +import type { SubagentTaskInfo } from '#/agent/tools/agent/subagent-task'; + +import type { SessionSwarmRunResult, SessionSwarmTask } from './sessionSwarm'; + +export class SwarmMemberTasks { + private readonly members = new Map(); + + constructor( + private readonly tasks: IAgentTaskService | undefined, + members: readonly SessionSwarmTask[], + cancelSwarm: (reason: unknown) => void, + ) { + if (tasks === undefined) return; + for (const member of members) { + const task = new SwarmMemberTask(member, cancelSwarm); + try { + this.members.set(member, { task, taskId: tasks.registerTask(task, { detached: member.runInBackground }) }); + } catch { + } + } + } + + started(member: SessionSwarmTask, agentId: string): void { + const entry = this.members.get(member); + if (entry === undefined) return; + entry.task.started(agentId); + this.tasks?.refresh(entry.taskId); + } + + suspended(member: SessionSwarmTask, reason: string): void { + const entry = this.members.get(member); + if (entry === undefined) return; + entry.task.suspended(reason); + this.tasks?.refresh(entry.taskId); + } + + settled(result: SessionSwarmRunResult): void { + this.members.get(result.task)?.task.settled(result); + } + + failAll(error: unknown): void { + const message = error instanceof Error ? error.message : String(error); + for (const [member, { task }] of this.members) { + task.settled({ task: member, status: 'failed', error: message }); + } + } +} + +export class SwarmMemberTask implements AgentTask { + readonly kind = 'agent' as const; + readonly idPrefix = 'agent'; + readonly description: string; + private agentId: string | undefined; + private stateReason: string | undefined; + private resultSummary: string | undefined; + private sink: AgentTaskSink | undefined; + private result: SessionSwarmRunResult | undefined; + private finish: (() => void) | undefined; + private readonly cancelOnAbort = (): void => { + this.cancelSwarm(this.sink?.signal.reason); + }; + + constructor( + private readonly member: SessionSwarmTask, + private readonly cancelSwarm: (reason: unknown) => void, + ) { + this.description = member.description; + this.agentId = member.kind === 'resume' ? member.resumeAgentId : undefined; + } + + start(sink: AgentTaskSink): Promise { + this.sink = sink; + if (sink.signal.aborted) this.cancelOnAbort(); + else sink.signal.addEventListener('abort', this.cancelOnAbort, { once: true }); + return new Promise((resolve) => { + this.finish = resolve; + if (this.result !== undefined) void this.settleWith(this.result); + }); + } + + started(agentId: string): void { + this.agentId = agentId; + this.stateReason = undefined; + } + + suspended(reason: string): void { + this.stateReason = reason; + } + + settled(result: SessionSwarmRunResult): void { + if (this.result !== undefined) return; + this.result = result; + if (this.sink !== undefined) void this.settleWith(result); + } + + toInfo(base: AgentTaskInfoBase): SubagentTaskInfo { + return { + ...base, + kind: 'agent', + agentId: this.agentId, + subagentType: this.member.profileName, + parentToolCallId: this.member.parentToolCallId, + model: this.member.kind === 'spawn' ? this.member.plan.model : undefined, + swarmIndex: this.member.swarmIndex, + stateReason: this.stateReason, + resultSummary: this.resultSummary, + }; + } + + private async settleWith(result: SessionSwarmRunResult): Promise { + const sink = this.sink!; + sink.signal.removeEventListener('abort', this.cancelOnAbort); + this.agentId = result.agentId ?? this.agentId; + this.stateReason = undefined; + this.resultSummary = result.result; + await sink.settle(settlementOf(result)); + this.finish?.(); + } +} + +function settlementOf(result: SessionSwarmRunResult): AgentTaskSettlement { + switch (result.status) { + case 'completed': + return { status: 'completed' }; + case 'failed': + return { status: 'failed', stopReason: result.error }; + case 'aborted': + return { status: 'killed', stopReason: result.error }; + } +} diff --git a/packages/agent-core-v2/test/agent/task/output-access.test.ts b/packages/agent-core-v2/test/agent/task/output-access.test.ts index 2ddcbb5a159..c8ad1dc9412 100644 --- a/packages/agent-core-v2/test/agent/task/output-access.test.ts +++ b/packages/agent-core-v2/test/agent/task/output-access.test.ts @@ -322,6 +322,55 @@ describe('AgentTaskService — readOutput / getOutputSnapshot', () => { } }); + it('onDidAppendOutput reports contiguous UTF-8 byte offsets', async () => { + const chunks: { offset: number; text: string }[] = []; + const subscription = manager.onDidAppendOutput(({ offset, text }) => chunks.push({ offset, text })); + const output = ' VITE ready\n ➜ Local: http://localhost:5173/\n'; + const taskId = registerProcess(manager, immediateProcess(0, output), 'npm run dev', 'dev server'); + + await manager.wait(taskId); + subscription.dispose(); + + expect(chunks.map((chunk) => chunk.text).join('')).toBe(output); + let expectedOffset = 0; + for (const chunk of chunks) { + expect(chunk.offset).toBe(expectedOffset); + expectedOffset += Buffer.byteLength(chunk.text, 'utf-8'); + } + expect(expectedOffset).toBe(Buffer.byteLength(output, 'utf-8')); + }); + + it('readOutputTail returns the persisted tail bytes with their offset', async () => { + const output = 'HEAD\n' + 'x'.repeat(200 * 1024) + '➜ TAIL\n'; + const taskId = registerProcess(manager, immediateProcess(0, output), 'echo big', 'large'); + + await manager.wait(taskId); + const tail = await manager.readOutputTail(taskId, 4096); + + const total = Buffer.byteLength(output, 'utf-8'); + expect(tail.offset).toBe(total - 4096); + expect(tail.data.byteLength).toBe(4096); + expect(Buffer.from(tail.data).toString('utf-8').endsWith('➜ TAIL\n')).toBe(true); + }); + + it('readOutputTail returns the in-memory tail before output is persisted', async () => { + const taskId = registerProcess(manager, immediateProcess(0, 'short ➜ line\n'), 'echo', 'demo'); + + await waitForOutput(manager, taskId, 'short'); + const tail = await manager.readOutputTail(taskId, 4096); + + expect(tail.offset).toBe(0); + expect(Buffer.from(tail.data).toString('utf-8')).toBe('short ➜ line\n'); + await manager.wait(taskId); + }); + + it('readOutputTail returns nothing for unknown task ids', async () => { + const tail = await manager.readOutputTail('bash-deadbeef', 4096); + + expect(tail.offset).toBe(0); + expect(tail.data.byteLength).toBe(0); + }); + it('readOutput respects tail length', async () => { const taskId = registerProcess( manager, diff --git a/packages/agent-core-v2/test/agent/task/taskManager.test.ts b/packages/agent-core-v2/test/agent/task/taskManager.test.ts index 6cf66232858..d3ae51b1ffc 100644 --- a/packages/agent-core-v2/test/agent/task/taskManager.test.ts +++ b/packages/agent-core-v2/test/agent/task/taskManager.test.ts @@ -754,6 +754,64 @@ describe('AgentTaskService', () => { } }); + it('publishes a task change on register, refresh, detach and end, foreground tasks included', async () => { + const { manager } = createAgentTaskService(); + const changes: Array<[string, string, boolean | undefined]> = []; + const subscription = manager.onDidChangeTask((info) => { + changes.push([info.taskId, info.status, info.detached]); + }); + let finish!: (value: { result: string }) => void; + const taskId = manager.registerTask( + agentTask(new Promise((resolve) => { finish = resolve; }), 'foreground child'), + { detached: false, signal: new AbortController().signal }, + ); + + manager.refresh(taskId); + manager.detach(taskId); + finish({ result: 'done' }); + await waitForTerminal(manager, taskId); + manager.refresh(taskId); + subscription.dispose(); + + expect(changes).toEqual([ + [taskId, 'running', false], + [taskId, 'running', false], + [taskId, 'running', true], + [taskId, 'completed', true], + ]); + }); + + it('publishes only the output that is kept once the output limit trips', async () => { + const sessionDir = await mkdtemp(join(tmpdir(), 'kimi-bg-limit-publish-')); + try { + const { manager } = createAgentTaskService({ sessionDir }); + const chunks = Array.from({ length: 20 }, () => 'x'.repeat(MiB)); + const { proc } = sigtermIgnoringProcess(chunks); + let publishedEnd = 0; + const subscription = manager.onDidAppendOutput(({ offset, text }) => { + publishedEnd = offset + Buffer.byteLength(text, 'utf-8'); + }); + + const taskId = manager.registerTask( + new ProcessTask(proc, 'runaway', 'hash', () => {}), + { + detached: false, + signal: new AbortController().signal, + timeoutMs: 60_000, + }, + ); + + await waitForTerminal(manager, taskId); + subscription.dispose(); + const tail = await manager.readOutputTail(taskId, 1); + + expect(publishedEnd).toBeLessThanOrEqual(LIMIT_BYTES); + expect(publishedEnd).toBe(tail.offset + tail.data.byteLength); + } finally { + await rm(sessionDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }); + } + }); + it('stops appending persisted output once the output limit trips for a detached process task', async () => { const sessionDir = await mkdtemp(join(tmpdir(), 'kimi-bg-limit-bg-')); try { diff --git a/packages/agent-core-v2/test/agent/task/taskOps.test.ts b/packages/agent-core-v2/test/agent/task/taskOps.test.ts index a24bd94dd3a..555a1fc5b86 100644 --- a/packages/agent-core-v2/test/agent/task/taskOps.test.ts +++ b/packages/agent-core-v2/test/agent/task/taskOps.test.ts @@ -29,7 +29,6 @@ let disposables: DisposableStore; let dispatcher: IEventDispatcher; let agentState: IAgentStateService; let log: IAppendLogStore; -let eventBus: IEventBus; function buildHost(key: string): { dispatcher: IEventDispatcher; @@ -57,7 +56,6 @@ beforeEach(() => { dispatcher = host.dispatcher; agentState = host.agentState; log = host.log; - eventBus = host.eventBus; }); afterEach(() => disposables.dispose()); @@ -118,37 +116,6 @@ describe('task ops (wire-backed)', () => { ]); }); - it('task.terminated persists the optional outputTail snapshot (record-only, never in the state or the bus)', async () => { - const published: Record[] = []; - disposables.add( - eventBus.subscribe((e) => { - published.push(Object.assign({}, e) as unknown as Record); - }), - ); - await dispatcher.dispatch( - new TaskTerminated({ agentId: 'test-agent', info: info('t1', 'completed'), outputTail: 'last lines' }), - ); - - expect(await readRecords()).toEqual([ - { - type: 'task.terminated', - agentId: 'test-agent', - info: info('t1', 'completed'), - outputTail: 'last lines', - time: expect.any(Number), - }, - ]); - expect(agentState.get(taskKey).get('t1')).toEqual(info('t1', 'completed')); - expect(published).toEqual([ - { - type: 'task.terminated', - agentId: 'test-agent', - info: info('t1', 'completed'), - time: expect.any(Number), - }, - ]); - }); - it('apply returns a new Map on change (the model is the restore seed)', async () => { const before = agentState.get(taskKey); await dispatcher.dispatch(new TaskStarted({ agentId: 'test-agent', info: info('t1', 'running') })); diff --git a/packages/agent-core-v2/test/agent/task/taskService.test.ts b/packages/agent-core-v2/test/agent/task/taskService.test.ts index 1bd5b398153..73e0297acbe 100644 --- a/packages/agent-core-v2/test/agent/task/taskService.test.ts +++ b/packages/agent-core-v2/test/agent/task/taskService.test.ts @@ -235,48 +235,6 @@ describe('AgentTaskService', () => { }; } - it('task.terminated dispatch carries the retained output tail as outputTail', async () => { - const { records } = capturingWire(); - const svc = ix.get(IAgentTaskService); - const taskId = svc.registerTask(outputtingTask('line one\nline two\n')); - - await svc.wait(taskId, 1000); - - const terminated = records.filter((record) => record['type'] === 'task.terminated'); - expect(terminated).toHaveLength(1); - expect(terminated[0]).toMatchObject({ - info: { taskId, status: 'completed' }, - outputTail: 'line one\nline two\n', - }); - }); - - it('task.terminated outputTail is bounded to the last 4 KiB of retained output', async () => { - const { records } = capturingWire(); - const svc = ix.get(IAgentTaskService); - const taskId = svc.registerTask(outputtingTask('x'.repeat(8 * 1024))); - - await svc.wait(taskId, 1000); - - const terminated = records.find((record) => record['type'] === 'task.terminated'); - expect(terminated?.['outputTail']).toBe('x'.repeat(4 * 1024)); - }); - - it('task.terminated dispatch omits outputTail when the task produced no output', async () => { - const { records } = capturingWire(); - const svc = ix.get(IAgentTaskService); - const taskId = svc.registerTask({ - ...fakeProcessTask(), - start: async (sink) => { - await sink.settle({ status: 'completed' }); - }, - }); - - await svc.wait(taskId, 1000); - - const terminated = records.find((record) => record['type'] === 'task.terminated'); - expect(terminated?.['outputTail']).toBeUndefined(); - }); - function stubLoop(): StubLoop { return ix.get(IAgentLoopService) as unknown as StubLoop; } diff --git a/packages/agent-core-v2/test/agent/task/tools/task-tools.test.ts b/packages/agent-core-v2/test/agent/task/tools/task-tools.test.ts index b0d5ca866c5..9d209bb728e 100644 --- a/packages/agent-core-v2/test/agent/task/tools/task-tools.test.ts +++ b/packages/agent-core-v2/test/agent/task/tools/task-tools.test.ts @@ -7,6 +7,7 @@ import { IAgentTaskService, type AgentTask, type AgentTaskInfo, + type AgentTaskOutputChunk, type AgentTaskOutputSnapshot, type AgentTaskTrackOptions, type AgentTaskWaitDelivery, @@ -25,6 +26,7 @@ import { TaskStopTool } from '#/agent/tools/task/task-stop/taskStopTool'; import { WaitForInputSchema } from '#/agent/tools/task/task-wait/task-wait'; import { WaitForTool, startWaitProgress, waitForProgressUpdate } from '#/agent/tools/task/task-wait/taskWaitTool'; import { abortError } from '#/_base/utils/abort'; +import { Event } from '#/_base/event'; import type { ITaskHandle } from '#/app/task/task'; import type { IHostProcess } from '#/os/interface/hostProcess'; import { compileToolArgsValidator, validateToolArgs } from '#/tool/args-validator'; @@ -121,6 +123,8 @@ interface FakeTaskEntry { class FakeTaskService implements IAgentTaskService { declare readonly _serviceBrand: undefined; + readonly onDidAppendOutput = Event.None as Event; + readonly onDidChangeTask = Event.None as Event; readonly stopCalls: Array<{ taskId: string; reason: string | undefined }> = []; readonly suppressCalls: string[] = []; @@ -162,6 +166,8 @@ class FakeTaskService implements IAgentTaskService { throw new Error('registerTask is not implemented in FakeTaskService.'); } + refresh(): void {} + getTask(taskId: string): AgentTaskInfo | undefined { return this.entries.get(taskId)?.info; } @@ -196,6 +202,10 @@ class FakeTaskService implements IAgentTaskService { return preview.slice(-Math.max(0, Math.trunc(tail))); } + async readOutputTail(): Promise { + throw new Error('FakeTaskService.readOutputTail is not implemented'); + } + async suppressTerminalNotification(taskId: string): Promise { this.suppressCalls.push(taskId); const entry = this.entries.get(taskId); diff --git a/packages/agent-core-v2/test/features/swarm/sessionSwarm.test.ts b/packages/agent-core-v2/test/features/swarm/sessionSwarm.test.ts index 0f5f9a10e4c..19560b611f7 100644 --- a/packages/agent-core-v2/test/features/swarm/sessionSwarm.test.ts +++ b/packages/agent-core-v2/test/features/swarm/sessionSwarm.test.ts @@ -41,6 +41,9 @@ import { } from '#/session/sessionMetadata/sessionMetadata'; import { IEventDispatcher } from '#/state/eventDispatcher'; import { IAgentEnvironmentBindingService } from '#/agent/environmentBinding/environmentBinding'; +import { IAgentTaskService, type RegisterAgentTaskOptions } from '#/agent/task/task'; +import type { AgentTask, AgentTaskSettlement } from '#/agent/task/types'; +import type { SubagentTaskInfo } from '#/agent/tools/agent/subagent-task'; import { AgentRunBatch, resolveSwarmMaxConcurrency, @@ -1466,6 +1469,95 @@ describe('SessionSwarmService metadata compatibility', () => { } }); + it('registers each member as a foreground task of the caller that follows suspension and completion', async () => { + vi.useFakeTimers(); + try { + agents['agent-rl'] = { labels: { parentAgentId: 'main' } }; + agents['agent-peer'] = { labels: { parentAgentId: 'main' } }; + handles.set('agent-rl', agentHandle('agent-rl', lifecycle, eventBus)); + handles.set('agent-peer', agentHandle('agent-peer', lifecycle, eventBus)); + const registered: Array<{ + readonly task: AgentTask; + readonly detached: boolean | undefined; + settlement?: AgentTaskSettlement; + readonly refreshed: Array; + }> = []; + const infoOf = (task: AgentTask): SubagentTaskInfo => + task.toInfo({ taskId: 't', description: task.description, status: 'running', startedAt: 0, endedAt: null }) as SubagentTaskInfo; + const callerTasks = { + registerTask: (task: AgentTask, options?: RegisterAgentTaskOptions) => { + const entry: (typeof registered)[number] = { task, detached: options?.detached, refreshed: [] }; + registered.push(entry); + void task.start({ + signal: new AbortController().signal, + appendOutput: () => {}, + settle: async (settlement) => { + entry.settlement = settlement; + return true; + }, + }); + return `task-${registered.length - 1}`; + }, + refresh: (taskId: string) => { + const entry = registered[Number(taskId.slice('task-'.length))]!; + entry.refreshed.push(infoOf(entry.task)); + }, + } as unknown as IAgentTaskService; + handles.set( + 'main', + agentHandle('main', lifecycle, eventBus, {}, new Map([[IAgentTaskService, callerTasks]])), + ); + const rateLimited = createControlledPromise<{ summary: string }>(); + const peer = createControlledPromise<{ summary: string }>(); + let rlRuns = 0; + runAgent.mockImplementation((agent, _request, options) => { + options?.onReady?.(); + const agentId = (agent as AgentContext).agentId; + if (agentId === 'agent-rl') { + rlRuns += 1; + return { + agentId, + turn: {} as never, + completion: rlRuns === 1 ? rateLimited : Promise.resolve({ summary: 'recovered summary' }), + }; + } + return { agentId, turn: {} as never, completion: peer }; + }); + const service = ix.get(ISessionSwarmService); + + const running = service.run({ + callerAgentId: 'main', + tasks: [resumeSessionTask('agent-rl'), resumeSessionTask('agent-peer')], + }); + await vi.advanceTimersByTimeAsync(0); + rateLimited.reject(new APIProviderRateLimitError('Rate limited')); + await vi.advanceTimersByTimeAsync(0); + peer.resolve({ summary: 'peer summary' }); + await vi.advanceTimersByTimeAsync(3_000); + await running; + await vi.advanceTimersByTimeAsync(0); + + expect(registered.map((entry) => entry.detached)).toEqual([false, false]); + const [rl, peerTask] = registered; + expect(rl!.refreshed.map((info) => info.stateReason)).toContain( + 'Provider rate limit; subagent requeued for retry.', + ); + expect(rl!.refreshed.at(-1)?.stateReason).toBeUndefined(); + expect(rl!.settlement).toEqual({ status: 'completed' }); + expect(infoOf(rl!.task)).toMatchObject({ + kind: 'agent', + agentId: 'agent-rl', + parentToolCallId: 'call_swarm', + swarmIndex: 1, + resultSummary: 'recovered summary', + }); + expect(peerTask!.settlement).toEqual({ status: 'completed' }); + expect(infoOf(peerTask!.task)).toMatchObject({ agentId: 'agent-peer', resultSummary: 'peer summary' }); + } finally { + vi.useRealTimers(); + } + }); + it('emits subagent.cancelled for a rate-limit-suspended child when the batch is cancelled', async () => { vi.useFakeTimers(); try { diff --git a/packages/agent-core-v2/test/os/backends/node-local/tools/bash.test.ts b/packages/agent-core-v2/test/os/backends/node-local/tools/bash.test.ts index 89ad4ba2919..b62b3598f40 100644 --- a/packages/agent-core-v2/test/os/backends/node-local/tools/bash.test.ts +++ b/packages/agent-core-v2/test/os/backends/node-local/tools/bash.test.ts @@ -6,6 +6,7 @@ import { IAgentTaskService, type AgentTask, type AgentTaskInfo, + type AgentTaskOutputChunk, type AgentTaskOutputSnapshot, type AgentTaskStatus, type ForegroundTaskReleaseReason, @@ -13,6 +14,7 @@ import { } from '#/agent/task/task'; import type { AgentTaskSettlement } from '#/agent/task/types'; import { userCancellationReason } from '#/_base/utils/abort'; +import { Event } from '#/_base/event'; import type { IConfigService } from '#/app/config/config'; import { ProcessTask } from '#/agent/tools/os/bash/process-task'; import type { IHostEnvironment } from '#/os/interface/hostEnvironment'; @@ -474,6 +476,9 @@ function createFakeTaskService( const service: IAgentTaskService = { _serviceBrand: undefined, + onDidAppendOutput: Event.None as Event, + onDidChangeTask: Event.None as Event, + refresh(): void {}, track(): never { throw new Error('fake IAgentTaskService.track is not implemented'); }, @@ -597,6 +602,10 @@ function createFakeTaskService( return output.slice(-Math.max(0, Math.trunc(tail))); }, + async readOutputTail(): Promise { + throw new Error('fake IAgentTaskService.readOutputTail is not implemented'); + }, + async suppressTerminalNotification(): Promise { }, From 5f4aa61b3430d3d587b238eb6affa889a38178ba Mon Sep 17 00:00:00 2001 From: qer Date: Fri, 30 Oct 2026 03:04:55 +0800 Subject: [PATCH 02/13] refactor(transcript): drop tasks from the transcript Tasks are not conversation content, so the transcript no longer models them: TranscriptTask, taskref items, the task.upsert / taskref.upsert / task output append ops, AgentTranscriptSnapshot.tasks, the projection of task / subagent / shell events into task rows, the subagent task seeding on bind, and the lost marking on cold reads are all removed. The REST transcript endpoints no longer return tasks. The one task fact that belongs to the conversation stays visible: the injected reminder about background tasks lost when the app exited is now shown as a warning notice marker instead of being hidden with other injected messages. --- packages/kap-server/src/routes/transcript.ts | 4 +- .../src/services/transcript/coreBinding.ts | 20 - .../src/services/transcript/coreEventMap.ts | 331 +-------- .../services/transcript/transcriptService.ts | 69 +- .../test/services/transcript.test.ts | 640 ++---------------- packages/kap-server/test/transcript.test.ts | 2 +- packages/transcript/src/contract/schema.ts | 49 +- packages/transcript/src/history/foldFacts.ts | 178 +---- packages/transcript/src/history/groupTurns.ts | 16 +- packages/transcript/src/index.ts | 1 - packages/transcript/src/model/ids.ts | 1 - packages/transcript/src/model/item.ts | 13 +- packages/transcript/src/model/task.ts | 30 - packages/transcript/src/ops/apply.ts | 54 +- packages/transcript/src/ops/operation.ts | 27 +- .../transcript/src/store/agentTranscript.ts | 12 +- packages/transcript/src/view/registry.ts | 2 - packages/transcript/test/layers.test.ts | 210 +----- packages/transcript/test/store.test.ts | 43 +- 19 files changed, 134 insertions(+), 1568 deletions(-) delete mode 100644 packages/transcript/src/model/task.ts diff --git a/packages/kap-server/src/routes/transcript.ts b/packages/kap-server/src/routes/transcript.ts index 122c0c4fe1b..f78769fd7ff 100644 --- a/packages/kap-server/src/routes/transcript.ts +++ b/packages/kap-server/src/routes/transcript.ts @@ -156,7 +156,6 @@ export function registerTranscriptRoutes(app: TranscriptRouteHost, deps: Transcr agent_id: query.agent_id, items: page.items, has_more: page.hasMore, - tasks: [...transcript.getTasks().values()], interactions: [...transcript.getInteractions().values()], attachments: [...transcript.getAttachments().values()], todos: [...transcript.getTodos().values()], @@ -181,7 +180,7 @@ export function registerTranscriptRoutes(app: TranscriptRouteHost, deps: Transcr const roster = (await transcriptService.readColdRoster(session_id)) ?? []; if ( !roster.some((d) => d.agentId === query.agent_id) && - (snapshot.items.length > 0 || snapshot.tasks.length > 0 || query.agent_id === MAIN_AGENT_ID) + (snapshot.items.length > 0 || query.agent_id === MAIN_AGENT_ID) ) { roster.push({ agentId: query.agent_id, @@ -194,7 +193,6 @@ export function registerTranscriptRoutes(app: TranscriptRouteHost, deps: Transcr agent_id: query.agent_id, items: page.items, has_more: page.hasMore, - tasks: snapshot.tasks, interactions: snapshot.interactions, attachments: snapshot.attachments, todos: snapshot.todos, diff --git a/packages/kap-server/src/services/transcript/coreBinding.ts b/packages/kap-server/src/services/transcript/coreBinding.ts index 8d4d37e0e28..1f5b2da8d35 100644 --- a/packages/kap-server/src/services/transcript/coreBinding.ts +++ b/packages/kap-server/src/services/transcript/coreBinding.ts @@ -3,7 +3,6 @@ import { IAgentConversationUndoParticipantRegistry, IAgentLoopService, IAgentScopeContext, - IAgentTaskService, IEventBus, INTERACTION_TAG_AGENT_ID, INTERACTION_TAG_SESSION_ID, @@ -118,25 +117,6 @@ export function bindSessionTranscript( resolvePlanRevisionKey: (key) => agents.handleOf(agentId)?.accessor.get(IAgentScopeContext).scope(key) ?? key, }); - const agentHandle = agents.handleOf(agentId); - if (agentHandle !== undefined) { - const tasks = agentHandle.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; diff --git a/packages/kap-server/src/services/transcript/coreEventMap.ts b/packages/kap-server/src/services/transcript/coreEventMap.ts index f3ad6c3d19b..ba094b4e531 100644 --- a/packages/kap-server/src/services/transcript/coreEventMap.ts +++ b/packages/kap-server/src/services/transcript/coreEventMap.ts @@ -39,17 +39,8 @@ import type { PromptSteered, PromptSubmitted, } from '@moonshot-ai/agent-core-v2/agent/prompt/promptEvents'; -import type { - ShellCompleted, - ShellOutput, - ShellStarted, -} from '@moonshot-ai/agent-core-v2/agent/shellCommand/shellCommandService'; import type { SkillActivated } from '@moonshot-ai/agent-core-v2/features/skill/skillOps'; -import type { - TaskNotified, - TaskStarted, - TaskTerminatedNotice, -} from '@moonshot-ai/agent-core-v2/agent/task/taskOps'; +import type { TaskNotified } from '@moonshot-ai/agent-core-v2/agent/task/taskOps'; import type { PermissionApprovalRequested, PermissionApprovalResolved, @@ -61,14 +52,7 @@ import type { } from '@moonshot-ai/agent-core-v2/agent/toolExecutor/toolExecutorEvents'; import type { AgentStatusUpdated } from '@moonshot-ai/agent-core-v2/agent/usage/usageEvents'; import type { PlanRevision } from '@moonshot-ai/agent-core-v2/features/plan/planOps'; -import type { SubagentSuspended } from '@moonshot-ai/agent-core-v2/features/swarm/session/sessionSwarmService'; -import type { - SubagentCancelled, - SubagentCompleted, - SubagentFailed, - SubagentSpawned, - SubagentStarted, -} from '@moonshot-ai/agent-core-v2/session/subagent/mirrorAgentRun'; +import type { SubagentSpawned } from '@moonshot-ai/agent-core-v2/session/subagent/mirrorAgentRun'; import { projectTranscriptUserOrigin, projectTranscriptUserTurnOrigin, @@ -85,7 +69,6 @@ import { type TranscriptMarker, type TranscriptOperation, type TranscriptPrompt, - type TranscriptTask, type TranscriptTodo, turnId as exportTurnKey, turnOrdinal, @@ -137,18 +120,8 @@ export type ProjectorBusEvent = | ({ readonly type: 'tool.result' } & ToolResultEvent) | ({ readonly type: 'permission.approval.requested' } & PermissionApprovalRequested) | ({ readonly type: 'permission.approval.resolved' } & PermissionApprovalResolved) - | ({ readonly type: 'task.started' } & TaskStarted) - | ({ readonly type: 'task.terminated' } & TaskTerminatedNotice) | ({ readonly type: 'task.notified' } & TaskNotified) - | ({ readonly type: 'shell.started' } & ShellStarted) - | ({ readonly type: 'shell.output' } & ShellOutput) - | ({ readonly type: 'shell.completed' } & ShellCompleted) | ({ readonly type: 'subagent.spawned' } & SubagentSpawned) - | ({ readonly type: 'subagent.started' } & SubagentStarted) - | ({ readonly type: 'subagent.completed' } & SubagentCompleted) - | ({ readonly type: 'subagent.failed' } & SubagentFailed) - | ({ readonly type: 'subagent.cancelled' } & SubagentCancelled) - | ({ readonly type: 'subagent.suspended' } & SubagentSuspended) | ({ readonly type: 'goal.updated' } & GoalUpdated) | ({ readonly type: 'agent.status.updated' } & AgentStatusUpdated) | PromptQueuedEvent @@ -252,35 +225,8 @@ export class AgentTranscriptProjector { private openText: OpenTextFrame | undefined; private openThinking: OpenTextFrame | undefined; private readonly toolFrames = new Map(); - private readonly tasks = new Map(); - private readonly shellTasks = new Map(); - private readonly subagentTaskIds = new Map(); private activityTracker: LegacyActivityTracker | undefined; - 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 }]; - } - seedActiveTurn(info: { turnId: number; promptId?: string }): void { this.ensureHighWater(); const alloc = allocateExportTurn( @@ -411,25 +357,10 @@ export class AgentTranscriptProjector { case 'permission.approval.requested': case 'permission.approval.resolved': return []; - case 'task.started': - case 'task.terminated': - return this.onTaskLifecycle(event); case 'task.notified': return this.onTaskNotified(event); - case 'shell.started': - return this.onShellStarted(event); - case 'shell.output': - return this.onShellOutput(event); - case 'shell.completed': - return this.onShellCompleted(event); case 'subagent.spawned': return this.onSubagentSpawned(event); - case 'subagent.started': - case 'subagent.completed': - case 'subagent.failed': - case 'subagent.cancelled': - case 'subagent.suspended': - return this.onSubagentRun(event); case 'goal.updated': return this.onGoalUpdated(event); case 'agent.status.updated': @@ -1114,250 +1045,24 @@ export class AgentTranscriptProjector { return []; } - private onTaskLifecycle(event: { - type: 'task.started' | 'task.terminated'; - info: { - taskId: string; - kind: string; - description: string; - status: TranscriptTask['state']; - detached?: boolean; - agentId?: string; - startedAt: number; - endedAt: number | null; - }; - }): TranscriptOperation[] { - const { info } = event; - const task = this.upsertTask(info.taskId, (prev) => ({ - taskId: info.taskId, - kind: mapTaskKind(info.kind), - state: info.status, - detached: info.detached ?? prev?.detached ?? true, - description: info.description, - agentId: info.agentId ?? prev?.agentId, - 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, - model: prev?.model, - thinkingEffort: prev?.thinkingEffort, - })); - 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() }, - }); - } - return ops; - } - - private onShellStarted(event: { commandId: string; taskId: string }): TranscriptOperation[] { - this.shellTasks.set(event.commandId, event.taskId); - const task = this.upsertTask(event.taskId, (prev) => ({ - taskId: event.taskId, - kind: 'shell', - state: 'running', - detached: prev?.detached ?? false, - description: prev?.description, - agentId: prev?.agentId, - outputTail: prev?.outputTail ?? '', - startedAt: prev?.startedAt ?? nowIso(), - endedAt: prev?.endedAt, - })); - return [ - { op: 'task.upsert', task }, - { - op: 'taskref.upsert', - item: { kind: 'taskref', refId: `ref-${event.taskId}`, taskId: event.taskId, at: nowIso() }, - }, - ]; - } - - private shellTaskId(event: { commandId: string; taskId?: string }): string { - const taskId = this.shellTasks.get(event.commandId) ?? event.taskId ?? `shell-${event.commandId}`; - this.shellTasks.set(event.commandId, taskId); - return taskId; - } - - private onShellOutput(event: { - commandId: string; - taskId?: string; - update: { kind: string; text?: string }; - }): TranscriptOperation[] { - const taskId = this.shellTaskId(event); - const text = event.update.text; - if (typeof text !== 'string' || text.length === 0) return []; - const ops: TranscriptOperation[] = []; - let task = this.tasks.get(taskId); - if (task === undefined) { - task = this.upsertTask(taskId, (prev) => ({ - taskId, - kind: 'shell', - state: 'running', - detached: prev?.detached ?? false, - description: prev?.description, - agentId: prev?.agentId, - outputTail: prev?.outputTail ?? '', - startedAt: prev?.startedAt ?? nowIso(), - endedAt: prev?.endedAt, - })); - ops.push( - { op: 'task.upsert', task }, - { - op: 'taskref.upsert', - item: { kind: 'taskref', refId: `ref-${taskId}`, taskId, at: nowIso() }, - }, - ); - } - const offset = task.outputTail.length; - this.tasks.set(taskId, { ...task, outputTail: task.outputTail + text }); - ops.push({ op: 'append', target: { type: 'task', taskId }, offset, text }); - return ops; - } - - private onShellCompleted(event: { - commandId: string; - taskId?: string; - isError: boolean; - }): TranscriptOperation[] { - const taskId = this.shellTaskId(event); - const hadTask = this.tasks.has(taskId); - const task = this.upsertTask(taskId, (prev) => ({ - taskId, - kind: prev?.kind ?? 'shell', - state: event.isError ? 'failed' : 'completed', - detached: prev?.detached ?? false, - description: prev?.description, - agentId: prev?.agentId, - outputTail: prev?.outputTail ?? '', - startedAt: prev?.startedAt ?? nowIso(), - endedAt: nowIso(), - })); - const ops: TranscriptOperation[] = [{ op: 'task.upsert', task }]; - if (!hadTask) { - ops.push({ - op: 'taskref.upsert', - item: { kind: 'taskref', refId: `ref-${taskId}`, taskId, at: nowIso() }, - }); - } - return ops; - } - - private upsertTask( - taskId: string, - build: (prev: TranscriptTask | undefined) => TranscriptTask, - ): TranscriptTask { - const task = build(this.tasks.get(taskId)); - this.tasks.set(taskId, task); - return task; - } - private onSubagentSpawned(event: { subagentId: string; - subagentName: string; parentToolCallId: string; - description?: string; swarmIndex?: number; - runInBackground: boolean; - taskId?: string; - model?: string; - thinkingEffort?: string; }): TranscriptOperation[] { - 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) => { - const newGeneration = prev !== undefined && prev.state !== 'running'; - return { - taskId: taskKey, - kind: 'subagent', - state: 'running', - detached: event.runInBackground, - description: event.description ?? prev?.description, - agentId: event.subagentId, - outputTail: prev?.outputTail ?? '', - startedAt: newGeneration ? nowIso() : (prev?.startedAt ?? nowIso()), - endedAt: newGeneration ? undefined : prev?.endedAt, - model: event.model ?? prev?.model, - thinkingEffort: event.thinkingEffort ?? prev?.thinkingEffort, - }; - }); - const ops: TranscriptOperation[] = [{ op: 'task.upsert', task }]; const hit = this.toolFrames.get(event.parentToolCallId) ?? this.adoptToolFrame(event.parentToolCallId); - if (hit !== undefined) { - const ref: AgentRef = { - agentId: event.subagentId, - role: event.swarmIndex !== undefined ? 'member' : 'child', - }; - const frame: ToolCallFrame = { - ...hit.frame, - agentRefs: [...(hit.frame.agentRefs ?? []), ref], - }; - this.toolFrames.set(event.parentToolCallId, { ...hit, frame }); - ops.push({ op: 'frame.upsert', turnId: hit.turnId, stepId: hit.stepId, frame }); - } - return ops; - } - - private onSubagentRun(event: { - type: 'subagent.started' | 'subagent.completed' | 'subagent.failed' | 'subagent.cancelled' | 'subagent.suspended'; - subagentId: string; - resultSummary?: string; - usage?: StepUsage; - error?: string; - reason?: string; - }): TranscriptOperation[] { - const terminal = - event.type === 'subagent.completed' || - event.type === 'subagent.failed' || - event.type === 'subagent.cancelled'; - const state: TranscriptTask['state'] = - event.type === 'subagent.completed' - ? 'completed' - : event.type === 'subagent.failed' - ? 'failed' - : event.type === 'subagent.cancelled' - ? 'killed' - : 'running'; - const build = (taskId: string) => (prev: TranscriptTask | undefined): TranscriptTask => { - const newGeneration = !terminal && prev !== undefined && prev.state !== 'running'; - return { - taskId, - kind: 'subagent', - state, - detached: prev?.detached ?? true, - description: prev?.description, - agentId: event.subagentId, - outputTail: prev?.outputTail ?? '', - startedAt: newGeneration ? nowIso() : (prev?.startedAt ?? nowIso()), - endedAt: terminal ? nowIso() : newGeneration ? undefined : prev?.endedAt, - resultSummary: newGeneration ? undefined : (event.resultSummary ?? prev?.resultSummary), - usage: newGeneration ? undefined : (event.usage ?? prev?.usage), - error: newGeneration ? undefined : (event.error ?? prev?.error), - stateReason: event.reason ?? (newGeneration ? undefined : prev?.stateReason), - model: prev?.model, - thinkingEffort: prev?.thinkingEffort, - }; + if (hit === undefined) return []; + const ref: AgentRef = { + agentId: event.subagentId, + role: event.swarmIndex !== undefined ? 'member' : 'child', }; - const taskKey = this.subagentTaskIds.get(event.subagentId) ?? event.subagentId; - const task = this.upsertTask(taskKey, build(taskKey)); - const ops: TranscriptOperation[] = [{ op: 'task.upsert', task }]; - if (taskKey !== event.subagentId && this.tasks.has(event.subagentId)) { - const agentTask = this.upsertTask(event.subagentId, build(event.subagentId)); - ops.push({ op: 'task.upsert', task: agentTask }); - } - return ops; + const frame: ToolCallFrame = { + ...hit.frame, + agentRefs: [...(hit.frame.agentRefs ?? []), ref], + }; + this.toolFrames.set(event.parentToolCallId, { ...hit, frame }); + return [{ op: 'frame.upsert', turnId: hit.turnId, stepId: hit.stepId, frame }]; } private onGoalUpdated(event: { @@ -1808,18 +1513,6 @@ function mapTurnEndState(reason: 'completed' | 'cancelled' | 'failed' | 'blocked } } -function mapTaskKind(kind: string): TranscriptTask['kind'] { - switch (kind) { - case 'process': - return 'shell'; - case 'agent': - return 'subagent'; - default: - return 'other'; - } -} - - const TODO_LIST_TOOL_NAME = 'TodoList'; const TODO_ENTITY_ID = 'todo'; diff --git a/packages/kap-server/src/services/transcript/transcriptService.ts b/packages/kap-server/src/services/transcript/transcriptService.ts index 7bf96b0151d..b3447c85f9c 100644 --- a/packages/kap-server/src/services/transcript/transcriptService.ts +++ b/packages/kap-server/src/services/transcript/transcriptService.ts @@ -44,8 +44,6 @@ import { type TranscriptChangeEvent, type TranscriptMarker, type TranscriptOperation, - type TranscriptTask, - type TranscriptTaskRef, type TranscriptTurn, } from '@moonshot-ai/transcript'; @@ -222,8 +220,7 @@ export class TranscriptService { this.live.get(sessionId)?.binding.syncFromStore(agentId); } const existing = store.agents().find((d) => d.agentId === agentId); - const hasContent = - snapshot !== undefined && (snapshot.items.length > 0 || snapshot.tasks.length > 0); + const hasContent = snapshot !== undefined && snapshot.items.length > 0; if (existing !== undefined || hasContent) { store.describeAgent({ agentId, @@ -683,14 +680,7 @@ export class TranscriptService { activeWireTurnId: status?.state === 'running' ? status.activeTurnId : undefined, }); const activity: ActivityMeta = status?.state === 'running' ? 'turn' : 'idle'; - const snapshot = { - ...folded, - tasks: markLostSubagentTasks(folded, activity, (memberId) => - liveAgents?.handleOf(memberId)?.accessor.get(IAgentLoopService).snapshot().state === - 'running', - ), - meta: { ...folded.meta, activity }, - }; + const snapshot = { ...folded, meta: { ...folded.meta, activity } }; if (snapshot.meta.modes?.tower === undefined) return snapshot; const flags = this.deps.core.accessor.get(IFlagService); if ( @@ -752,16 +742,10 @@ export function snapshotToOps( turnOps: (turn: TranscriptTurn) => TranscriptOperation[] = snapshotTurnOps, ): TranscriptOperation[] { const ops: TranscriptOperation[] = []; - const pending: (TranscriptMarker | TranscriptTaskRef)[] = []; + const pending: TranscriptMarker[] = []; let lastTurnOrdinal: number | undefined; const flushPending = (beforeTurn?: number): void => { - for (const item of pending) { - ops.push( - item.kind === 'marker' - ? { op: 'marker.upsert', item, beforeTurn } - : { op: 'taskref.upsert', item, beforeTurn }, - ); - } + for (const item of pending) ops.push({ op: 'marker.upsert', item, beforeTurn }); pending.length = 0; }; for (const item of snapshot.items) { @@ -777,9 +761,6 @@ export function snapshotToOps( for (const attachment of snapshot.attachments) { ops.push({ op: 'attachment.upsert', attachment }); } - for (const task of snapshot.tasks) { - ops.push({ op: 'task.upsert', task }); - } ops.push({ op: 'meta.merge', meta: snapshot.meta }); return ops; } @@ -805,48 +786,6 @@ const TERMINAL_TURN_STATES: ReadonlySet = new Set([ 'cancelled', ]); -function memberTurnOrdinals(snapshot: AgentTranscriptSnapshot): ReadonlyMap { - const ordinals = new Map(); - for (const item of snapshot.items) { - if (item.kind !== 'turn') continue; - for (const step of item.steps) { - for (const frame of step.frames) { - if (frame.kind !== 'tool') continue; - for (const ref of frame.agentRefs ?? []) ordinals.set(ref.agentId, item.ordinal); - } - } - } - return ordinals; -} - -function latestTurnOrdinal(snapshot: AgentTranscriptSnapshot): number | undefined { - let latest: number | undefined; - for (const item of snapshot.items) { - if (item.kind !== 'turn') continue; - if (latest === undefined || item.ordinal > latest) latest = item.ordinal; - } - return latest; -} - -function markLostSubagentTasks( - snapshot: AgentTranscriptSnapshot, - activity: ActivityMeta, - isMemberRunning: (memberId: string) => boolean, -): readonly TranscriptTask[] { - const ordinals = memberTurnOrdinals(snapshot); - const latest = latestTurnOrdinal(snapshot); - const interrupted = (task: TranscriptTask): boolean => { - if (task.kind !== 'subagent' || task.detached || task.state !== 'running') return false; - if (task.agentId !== undefined && isMemberRunning(task.agentId)) return false; - if (activity !== 'turn') return true; - const owner = task.agentId === undefined ? undefined : ordinals.get(task.agentId); - return owner !== undefined && owner !== latest; - }; - return snapshot.tasks.map((task) => - interrupted(task) ? { ...task, state: 'lost' as const } : task, - ); -} - function projectQuestionInteractionRecords( records: readonly ContextRecord[], sessionId: string, diff --git a/packages/kap-server/test/services/transcript.test.ts b/packages/kap-server/test/services/transcript.test.ts index eac332c983d..4eaff107ebf 100644 --- a/packages/kap-server/test/services/transcript.test.ts +++ b/packages/kap-server/test/services/transcript.test.ts @@ -11,7 +11,6 @@ import { IAgentConversationUndoParticipantRegistry, IAgentLoopService, IAgentScopeContext, - IAgentTaskService, IEventBus, IFlagService, IBlobStore, @@ -44,13 +43,13 @@ import { type InteractionUpsertOp, type TranscriptFrame, type TranscriptOperation, - type TranscriptTask, type TranscriptTurn, } from '@moonshot-ai/transcript'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { bindSessionTranscript } from '../../src/services/transcript/coreBinding'; import { toWireQuestion } from '../../src/protocol/question-wire'; +import type { TaskEventMessage } from '../../src/protocol/ws-task'; import type { AgentActivitySnapshot } from '@moonshot-ai/agent-core-v2/agent/loop/loop'; import type { LegacyActivityApproval } from '../../src/services/legacyStatus/legacyStatus'; import { @@ -665,14 +664,15 @@ describe('AgentTranscriptProjector', () => { prompt: 'two', steps: [], }, - { kind: 'taskref', refId: 'r1', taskId: 'bash-1' }, + { kind: 'marker', markerId: 'm2', marker: 'notice' }, ], - tasks: [], meta: {}, }; const ops = snapshotToOps(snapshot); - expect(ops.find((op) => op.op === 'marker.upsert')).toMatchObject({ beforeTurn: 1 }); - expect(ops.find((op) => op.op === 'taskref.upsert')).toMatchObject({ beforeTurn: 2 }); + expect(ops.filter((op) => op.op === 'marker.upsert')).toMatchObject([ + { beforeTurn: 1 }, + { beforeTurn: 2 }, + ]); const tx = new AgentTranscript('main'); tx.apply([ @@ -683,12 +683,8 @@ describe('AgentTranscriptProjector', () => { ]); tx.apply(ops); expect( - tx.getItems().map((item) => { - if (item.kind === 'turn') return item.turnId; - if (item.kind === 'marker') return item.markerId; - return item.refId; - }), - ).toEqual(['t0', 'm1', 't1', 'r1', 't2']); + tx.getItems().map((item) => (item.kind === 'turn' ? item.turnId : item.markerId)), + ).toEqual(['t0', 'm1', 't1', 'm2', 't2']); }); it('snapshotToOps flattens attachment entities so backfilled attachmentIds never dangle', () => { @@ -716,7 +712,6 @@ describe('AgentTranscriptProjector', () => { steps: [], }, ], - tasks: [], meta: {}, }; @@ -1085,127 +1080,6 @@ describe('AgentTranscriptProjector', () => { }); }); - it('projects process tasks as shell tasks with streaming output', () => { - const projector = new AgentTranscriptProjector('main', TEST_SESSION_ID); - const tx = new AgentTranscript('main'); - const ops: TranscriptOperation[] = []; - const feed = (event: ProjectorBusEvent): void => { - const mapped = projector.map(event); - ops.push(...mapped); - tx.apply(mapped); - }; - - const started = { - taskId: 'bash-1', - kind: 'process', - description: 'ls -la', - status: 'running', - detached: false, - startedAt: 1_700_000_000_000, - endedAt: null, - }; - feed(ev({ type: 'task.started', info: started })); - feed(ev({ type: 'shell.started', commandId: 'cmd-1', taskId: 'bash-1' })); - feed(ev({ type: 'shell.output', commandId: 'cmd-1', update: { kind: 'stdout', text: 'a\n' } })); - feed(ev({ type: 'shell.output', commandId: 'cmd-1', update: { kind: 'stderr', text: 'b\n' } })); - feed( - ev({ - type: 'task.terminated', - info: { ...started, status: 'completed', endedAt: 1_700_000_001_000 }, - }), - ); - - expect(ops.some((op) => op.op === 'taskref.upsert' && op.item.taskId === 'bash-1')).toBe(true); - const appends = ops.filter((op): op is AppendOp => op.op === 'append'); - expect(appends.map((op) => [op.offset, op.text])).toEqual([ - [0, 'a\n'], - [2, 'b\n'], - ]); - - const task = tx.getTask('bash-1'); - expect(task).toMatchObject({ - kind: 'shell', - state: 'completed', - detached: false, - description: 'ls -la', - outputTail: 'a\nb\n', - }); - expect( - projector.map( - ev({ type: 'shell.output', commandId: 'cmd-1', update: { kind: 'progress', percent: 50 } }), - ), - ).toEqual([]); - }); - - it('fills the shell task output from late stderr chunks before completing', () => { - const projector = new AgentTranscriptProjector('main', TEST_SESSION_ID); - const tx = new AgentTranscript('main'); - - tx.apply(projector.map(ev({ type: 'shell.started', commandId: 'c1', taskId: 'task-1' }))); - tx.apply( - projector.map(ev({ type: 'shell.output', commandId: 'c1', update: { kind: 'stderr', text: 'boom' } })), - ); - tx.apply(projector.map(ev({ type: 'shell.completed', commandId: 'c1', isError: true }))); - - expect(tx.getTask('task-1')).toMatchObject({ state: 'failed', outputTail: 'boom' }); - }); - - it('routes shell output/completion via the event taskId when shell.started was missed', () => { - const projector = new AgentTranscriptProjector('main', TEST_SESSION_ID); - const tx = new AgentTranscript('main'); - - tx.apply( - projector.map( - ev({ type: 'shell.output', commandId: 'c1', taskId: 'task-1', update: { kind: 'stdout', text: 'hello' } }), - ), - ); - expect(tx.getTask('task-1')).toMatchObject({ kind: 'shell', state: 'running', outputTail: 'hello' }); - expect(tx.getItems()).toContainEqual(expect.objectContaining({ kind: 'taskref', taskId: 'task-1' })); - - tx.apply(projector.map(ev({ type: 'shell.completed', commandId: 'c1', taskId: 'task-1', isError: false }))); - expect(tx.getTask('task-1')).toMatchObject({ state: 'completed', outputTail: 'hello' }); - }); - - it('emits a taskref when only shell.completed arrives for a command', () => { - const projector = new AgentTranscriptProjector('main', TEST_SESSION_ID); - const tx = new AgentTranscript('main'); - - tx.apply(projector.map(ev({ type: 'shell.completed', commandId: 'c1', taskId: 'task-1', isError: true }))); - - expect(tx.getTask('task-1')).toMatchObject({ kind: 'shell', state: 'failed' }); - expect(tx.getItems()).toContainEqual(expect.objectContaining({ kind: 'taskref', taskId: 'task-1' })); - }); - - it('projects no-taskId shell failures under a synthetic per-command task id', () => { - const projector = new AgentTranscriptProjector('main', TEST_SESSION_ID); - const tx = new AgentTranscript('main'); - - tx.apply( - projector.map(ev({ type: 'shell.output', commandId: 'c1', update: { kind: 'stderr', text: 'boom' } })), - ); - expect(tx.getTask('shell-c1')).toMatchObject({ kind: 'shell', state: 'running', outputTail: 'boom' }); - - tx.apply(projector.map(ev({ type: 'shell.completed', commandId: 'c1', isError: true }))); - expect(tx.getTask('shell-c1')).toMatchObject({ state: 'failed', outputTail: 'boom' }); - expect(tx.getItems()).toContainEqual(expect.objectContaining({ kind: 'taskref', taskId: 'shell-c1' })); - }); - - it('marks a foreground shell task terminal on shell.completed', () => { - const projector = new AgentTranscriptProjector('main', TEST_SESSION_ID); - const tx = new AgentTranscript('main'); - - tx.apply(projector.map(ev({ type: 'shell.started', commandId: 'c1', taskId: 'task-1' }))); - expect(tx.getTask('task-1')?.state).toBe('running'); - - tx.apply(projector.map(ev({ type: 'shell.completed', commandId: 'c1', isError: false }))); - expect(tx.getTask('task-1')).toMatchObject({ kind: 'shell', state: 'completed' }); - expect(tx.getTask('task-1')?.endedAt).toBeTypeOf('string'); - - tx.apply(projector.map(ev({ type: 'shell.started', commandId: 'c2', taskId: 'task-2' }))); - tx.apply(projector.map(ev({ type: 'shell.completed', commandId: 'c2', isError: true }))); - expect(tx.getTask('task-2')?.state).toBe('failed'); - }); - it('ignores task.notified (it re-surfaces as an origin:task turn)', () => { const projector = new AgentTranscriptProjector('main', TEST_SESSION_ID); expect(projector.map(ev({ type: 'task.notified', taskId: 't' }))).toEqual([]); @@ -1236,284 +1110,13 @@ describe('AgentTranscriptProjector', () => { description: 'scan the repo', swarmIndex: 0, runInBackground: false, - model: 'example-model', - thinkingEffort: 'high', }), ); - feed(ev({ type: 'subagent.completed', subagentId: 'agent-0', resultSummary: 'done' })); const tool = turnOps('t1', tx.getItems()).steps[0]!.frames.find((f) => f.kind === 'tool'); expect(tool).toMatchObject({ agentRefs: [{ agentId: 'agent-0', role: 'member' }], }); - const task = tx.getTask('agent-0'); - expect(task).toMatchObject({ - kind: 'subagent', - state: 'completed', - agentId: 'agent-0', - description: 'scan the repo', - detached: false, - model: 'example-model', - thinkingEffort: 'high', - }); - }); - - it('keys an Agent-tool subagent row by its registered task id and folds the lifecycle', () => { - const projector = new AgentTranscriptProjector('main', TEST_SESSION_ID); - 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', TEST_SESSION_ID); - 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('resets the agent-run generation stamps when a completed subagent is resumed for rework', () => { - const projector = new AgentTranscriptProjector('main', TEST_SESSION_ID); - 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: 'worker', - parentToolCallId: 'call-1', - description: 'tower worker', - runInBackground: true, - }), - ); - feed( - ev({ - type: 'task.started', - info: { - taskId: 'task-9', - kind: 'agent', - description: 'tower worker', - 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: 'tower worker', - status: 'completed', - detached: true, - agentId: 'agent-1', - startedAt: 1_700_000_000_000, - endedAt: 1_700_000_001_000, - }, - }), - ); - const firstRun = tx.getTask('agent-1'); - expect(firstRun).toMatchObject({ state: 'completed', resultSummary: 'done' }); - - feed( - ev({ - type: 'subagent.spawned', - subagentId: 'agent-1', - subagentName: 'worker', - parentToolCallId: 'call-2', - description: 'tower worker', - runInBackground: true, - taskId: 'task-10', - }), - ); - feed(ev({ type: 'subagent.started', subagentId: 'agent-1' })); - - const resumed = tx.getTask('agent-1'); - expect(resumed).toMatchObject({ kind: 'subagent', state: 'running' }); - expect(resumed?.endedAt).toBeUndefined(); - expect(resumed?.resultSummary).toBeUndefined(); - expect(resumed?.error).toBeUndefined(); - expect((resumed?.startedAt ?? '') >= (firstRun?.endedAt ?? '')).toBe(true); - expect(tx.getTask('task-9')).toMatchObject({ - state: 'completed', - resultSummary: 'done', - endedAt: new Date(1_700_000_001_000).toISOString(), - }); - expect(tx.getTask('task-10')).toMatchObject({ - kind: 'subagent', - state: 'running', - agentId: 'agent-1', - }); - }); - - it('clears the terminal stamps when a terminal subagent is respawned without a task id', () => { - const projector = new AgentTranscriptProjector('main', TEST_SESSION_ID); - 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: 'worker', - parentToolCallId: 'call-1', - description: 'scan', - runInBackground: false, - }), - ); - feed(ev({ type: 'subagent.completed', subagentId: 'agent-1', resultSummary: 'done' })); - const finished = tx.getTask('agent-1'); - expect(finished).toMatchObject({ state: 'completed', resultSummary: 'done' }); - - feed( - ev({ - type: 'subagent.spawned', - subagentId: 'agent-1', - subagentName: 'worker', - parentToolCallId: 'call-2', - description: 'scan again', - runInBackground: false, - }), - ); - - const respawned = tx.getTask('agent-1'); - expect(respawned).toMatchObject({ kind: 'subagent', state: 'running' }); - expect(respawned?.endedAt).toBeUndefined(); - expect(respawned?.resultSummary).toBeUndefined(); - expect((respawned?.startedAt ?? '') >= (finished?.endedAt ?? '')).toBe(true); - }); - - it('keeps the run stamps when subagent.started arrives for an already-running task', () => { - const projector = new AgentTranscriptProjector('main', TEST_SESSION_ID); - 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.started', subagentId: 'agent-1' })); - const startedAt = tx.getTask('task-9')?.startedAt; - feed(ev({ type: 'subagent.started', subagentId: 'agent-1' })); - - expect(tx.getTask('task-9')).toMatchObject({ state: 'running', startedAt }); - }); - - it('recovers the agent → task association from a backfilled task.started', () => { - const projector = new AgentTranscriptProjector('main', TEST_SESSION_ID); - 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', () => { @@ -2797,7 +2400,7 @@ describe('AgentTranscriptProjector', () => { } }); - it('readColdSnapshot folds task/todo/goal/plan/interaction records into the cold snapshot', async () => { + it('readColdSnapshot folds todo/goal/plan/interaction records into the cold snapshot', async () => { const home = await mkdtemp(join(tmpdir(), 'transcript-cold-facts-')); try { const wireDir = join(home, 'sessions', 'ws', 's1', 'agents', 'main'); @@ -2835,31 +2438,6 @@ describe('AgentTranscriptProjector', () => { }, { type: 'goal.create', goalId: 'g1', objective: 'fix the bug', time: 4000 }, { type: 'plan_mode.enter', id: 'plan-1', time: 5000 }, - { - type: 'task.started', - info: { - taskId: 'task_1', - kind: 'process', - description: 'pnpm test', - status: 'running', - startedAt: 6000, - endedAt: null, - }, - time: 6000, - }, - { - type: 'task.terminated', - info: { - taskId: 'task_1', - kind: 'process', - description: 'pnpm test', - status: 'completed', - startedAt: 6000, - endedAt: 9000, - }, - outputTail: '42 passed', - time: 9000, - }, { type: 'interaction.request', id: 'apr-1', @@ -2913,19 +2491,6 @@ describe('AgentTranscriptProjector', () => { ]); expect(JSON.stringify(snapshot)).not.toContain('blobref:'); - expect(snapshot!.tasks).toEqual([ - { - taskId: 'task_1', - kind: 'shell', - state: 'completed', - detached: true, - description: 'pnpm test', - agentId: undefined, - outputTail: '42 passed', - startedAt: new Date(6000).toISOString(), - endedAt: new Date(9000).toISOString(), - }, - ]); expect(snapshot!.todos).toEqual([ { todoId: 'todo', @@ -2950,7 +2515,6 @@ describe('AgentTranscriptProjector', () => { expect(standalone).toEqual([ expect.objectContaining({ kind: 'marker', marker: 'goal', markerId: 'm1' }), expect.objectContaining({ kind: 'marker', marker: 'plan.enter', markerId: 'm2' }), - expect.objectContaining({ kind: 'taskref', refId: 'ref-task_1', taskId: 'task_1' }), ]); service.dropSession('s1'); } finally { @@ -3044,7 +2608,7 @@ describe('AgentTranscriptProjector', () => { } }); - it('restores interrupted swarm members and exact call associations without starting a session', async () => { + it('restores exact swarm call associations without starting a session', async () => { const home = await mkdtemp(join(tmpdir(), 'transcript-cold-swarm-')); try { const wireDir = join(home, 'sessions', 'ws', 's1', 'agents', 'main'); @@ -3067,11 +2631,6 @@ describe('AgentTranscriptProjector', () => { await writeFile(join(childDir, 'wire.jsonl'), JSON.stringify({ type: 'context.append_message', message: { role: 'assistant', content: [{ type: 'text', text: 'partial review' }], toolCalls: [] }, time: 3500 }) + '\n'); const service = coldTranscriptService(home); const snapshot = await service.readColdSnapshot('s1', 'main'); - expect(snapshot?.tasks).toEqual(expect.arrayContaining([ - expect.objectContaining({ agentId: 'child-a', state: 'completed', resultSummary: 'done' }), - expect.objectContaining({ agentId: 'child-b', state: 'lost', description: 'second' }), - expect.objectContaining({ agentId: 'child-c', state: 'lost' }), - ])); const frames = snapshot?.items.flatMap((item) => item.kind === 'turn' ? item.steps.flatMap((step) => step.frames) : []); expect(frames).toEqual(expect.arrayContaining([ expect.objectContaining({ toolCallId: 'swarm-a', agentRefs: [{ agentId: 'child-a', role: 'member' }, { agentId: 'child-b', role: 'member' }] }), @@ -3118,64 +2677,6 @@ describe('AgentTranscriptProjector', () => { expect(frames).toEqual([ expect.objectContaining({ toolCallId: 'call_spawn', state: 'done', output: 'agent_id: child-a' }), ]); - expect(snapshot?.tasks).toEqual([ - expect.objectContaining({ agentId: 'child-a', state: 'completed', resultSummary: 'done' }), - ]); - } finally { - await rm(home, { recursive: true, force: true }); - } - }); - - it('loses a member whose spawning turn ended even while the parent runs a later turn', async () => { - const home = await mkdtemp(join(tmpdir(), 'transcript-cold-stale-member-')); - try { - const wireDir = join(home, 'sessions', 'ws', 's1', 'agents', 'main'); - await mkdir(wireDir, { recursive: true }); - const records = [ - { type: 'turn.prompt', turnId: 0, origin: { kind: 'user' }, time: 1000 }, - { type: 'context.append_message', message: { role: 'user', content: [{ type: 'text', text: 'review' }], toolCalls: [], origin: { kind: 'user' } }, time: 1000 }, - { type: 'context.append_message', message: { role: 'assistant', content: [], toolCalls: [ - { type: 'function', id: 'swarm-a', name: 'AgentSwarm', arguments: '{}' }, - ] }, time: 2000 }, - { type: 'subagent.spawned', subagentId: 'child-a', subagentName: 'explore', parentAgentId: 'main', parentToolCallId: 'swarm-a', swarmIndex: 0, runInBackground: false, time: 3000 }, - { type: 'turn.ended', turnId: 0, reason: 'interrupted', time: 4000 }, - { type: 'turn.prompt', turnId: 1, origin: { kind: 'user' }, time: 5000 }, - { type: 'context.append_message', message: { role: 'user', content: [{ type: 'text', text: 'anything new?' }], toolCalls: [], origin: { kind: 'user' } }, time: 5000 }, - ]; - await writeFile(join(wireDir, 'wire.jsonl'), records.map((record) => JSON.stringify(record)).join('\n') + '\n'); - const busy = await coldTranscriptService(home, fakeLoopStates({ main: 'running' })).readColdSnapshot('s1', 'main'); - expect(busy?.meta.activity).toBe('turn'); - expect(busy?.tasks).toEqual([expect.objectContaining({ agentId: 'child-a', state: 'lost' })]); - - const stillRunning = await coldTranscriptService( - home, - fakeLoopStates({ main: 'running', 'child-a': 'running' }), - ).readColdSnapshot('s1', 'main'); - expect(stillRunning?.tasks).toEqual([expect.objectContaining({ agentId: 'child-a', state: 'running' })]); - } finally { - await rm(home, { recursive: true, force: true }); - } - }); - - it('keeps a member spawned by the parent\'s current turn running while that turn is active', async () => { - const home = await mkdtemp(join(tmpdir(), 'transcript-cold-live-member-')); - try { - const wireDir = join(home, 'sessions', 'ws', 's1', 'agents', 'main'); - await mkdir(wireDir, { recursive: true }); - const records = [ - { type: 'turn.prompt', turnId: 0, origin: { kind: 'user' }, time: 1000 }, - { type: 'context.append_message', message: { role: 'user', content: [{ type: 'text', text: 'review' }], toolCalls: [], origin: { kind: 'user' } }, time: 1000 }, - { type: 'context.append_message', message: { role: 'assistant', content: [], toolCalls: [ - { type: 'function', id: 'swarm-a', name: 'AgentSwarm', arguments: '{}' }, - ] }, time: 2000 }, - { type: 'subagent.spawned', subagentId: 'child-a', subagentName: 'explore', parentAgentId: 'main', parentToolCallId: 'swarm-a', swarmIndex: 0, runInBackground: false, time: 3000 }, - ]; - await writeFile(join(wireDir, 'wire.jsonl'), records.map((record) => JSON.stringify(record)).join('\n') + '\n'); - const busy = await coldTranscriptService(home, fakeLoopStates({ main: 'running' })).readColdSnapshot('s1', 'main'); - expect(busy?.tasks).toEqual([expect.objectContaining({ agentId: 'child-a', state: 'running' })]); - - const idle = await coldTranscriptService(home, fakeLoopStates({ main: 'idle' })).readColdSnapshot('s1', 'main'); - expect(idle?.tasks).toEqual([expect.objectContaining({ agentId: 'child-a', state: 'lost' })]); } finally { await rm(home, { recursive: true, force: true }); } @@ -3200,6 +2701,9 @@ describe('AgentTranscriptProjector', () => { await write([boundary, user, assistant]); const dangling = await coldTranscriptService(home).readColdSnapshot('s1', 'main'); expect(dangling!.meta.activity).toBe('idle'); + + const running = await coldTranscriptService(home, fakeLoopStates({ main: 'running' })).readColdSnapshot('s1', 'main'); + expect(running!.meta.activity).toBe('turn'); } finally { await rm(home, { recursive: true, force: true }); } @@ -3708,46 +3212,6 @@ describe('AgentTranscriptProjector', () => { }); }); - it('treats subagent.started/failed/suspended within the running→failed vocabulary', () => { - const projector = new AgentTranscriptProjector('main', TEST_SESSION_ID); - const tx = new AgentTranscript('main'); - const feed = (event: ProjectorBusEvent): void => void tx.apply(projector.map(event)); - - feed(ev({ type: 'subagent.started', subagentId: 'agent-1' })); - expect(tx.getTask('agent-1')).toMatchObject({ kind: 'subagent', state: 'running' }); - feed(ev({ type: 'subagent.suspended', subagentId: 'agent-1', reason: 'approval' })); - expect(tx.getTask('agent-1')).toMatchObject({ state: 'running', stateReason: 'approval' }); - feed(ev({ type: 'subagent.failed', subagentId: 'agent-1', error: 'boom' })); - expect(tx.getTask('agent-1')).toMatchObject({ state: 'failed', error: 'boom' }); - - feed( - ev({ - type: 'subagent.completed', - subagentId: 'agent-2', - resultSummary: 'found 3 files', - usage: { inputOther: 10, output: 5, inputCacheRead: 2, inputCacheCreation: 1 }, - }), - ); - expect(tx.getTask('agent-2')).toMatchObject({ - state: 'completed', - resultSummary: 'found 3 files', - usage: { inputOther: 10, output: 5, inputCacheRead: 2, inputCacheCreation: 1 }, - }); - }); -}); - -describe('AgentTranscript transcript task vocabulary', () => { - it('documents the task states used by the projector', () => { - const states: Array = [ - 'running', - 'completed', - 'failed', - 'timed_out', - 'killed', - 'lost', - ]; - expect(states).toHaveLength(6); - }); }); describe('bindSessionTranscript', () => { @@ -3797,7 +3261,7 @@ describe('bindSessionTranscript', () => { this.closeHandlers.add(cb); return { dispose: () => this.closeHandlers.delete(cb) }; } - add(id: string, opts?: { loopStatus?: { state?: 'idle' | 'running'; activeTurnId?: number }; tasks?: readonly unknown[]; activePromptId?: string }): FakeAgentHandle { + add(id: string, opts?: { loopStatus?: { state?: 'idle' | 'running'; activeTurnId?: number }; activePromptId?: string }): FakeAgentHandle { const bus = this.handles.get(id)?.bus ?? new FakeBus(); const undoParticipants = new Map(); const scope = makeAgentScopeContext({ @@ -3874,9 +3338,6 @@ describe('bindSessionTranscript', () => { promptHandle: (id: string) => (active?.id === id ? active : undefined), }; } - if (token === IAgentTaskService) { - return { list: () => opts?.tasks ?? [] }; - } return undefined; }, }, @@ -4013,46 +3474,6 @@ 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(agents), - ); - - expect(store.getAgent('main')?.getTask('task-9')).toMatchObject({ - kind: 'subagent', - state: 'running', - detached: false, - description: 'Inspect', - agentId: 'agent-1', - }); - - agents.byId('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', @@ -4753,6 +4174,39 @@ describe('bindSessionTranscript', () => { service.dropSession('s1'); }); + it('keeps task events out of the transcript and its journal', async () => { + const agents = new FakeAgents(); + const main = agents.add('main'); + const service = new TranscriptService({ + homeDir: '/nonexistent-home', + core: fakeCoreWithAgents(agents), + }); + service.forSessionLive('s1'); + await service.whenReady('s1'); + const base = service.getSeqWatermark('s1', 'main'); + + const ops: string[] = []; + service.onSessionOps('s1', (event) => ops.push(...event.ops.map((op) => op.op))); + main.bus.emit( + ev({ + type: 'task.started', + info: { + taskId: 'bash-1', + kind: 'process', + description: 'sleep 60', + status: 'running', + detached: true, + startedAt: 1_700_000_000_000, + endedAt: null, + }, + }), + ); + + expect(ops).toEqual([]); + expect(service.getSeqWatermark('s1', 'main')).toBe(base); + service.dropSession('s1'); + }); + it('marks catch-up incomplete once the bounded journal evicts old batches', async () => { const agents = new FakeAgents(); const main = agents.add('main'); diff --git a/packages/kap-server/test/transcript.test.ts b/packages/kap-server/test/transcript.test.ts index 3f2c78e8218..e543b9cd436 100644 --- a/packages/kap-server/test/transcript.test.ts +++ b/packages/kap-server/test/transcript.test.ts @@ -51,7 +51,7 @@ interface TurnContract { interface TranscriptContract { agent_id: string; - items: (TurnContract | { kind: 'marker' | 'taskref' })[]; + items: (TurnContract | { kind: 'marker' })[]; has_more: boolean; tasks: unknown[]; interactions: { diff --git a/packages/transcript/src/contract/schema.ts b/packages/transcript/src/contract/schema.ts index 569f85ba1f5..e6a3a83ebcd 100644 --- a/packages/transcript/src/contract/schema.ts +++ b/packages/transcript/src/contract/schema.ts @@ -203,37 +203,11 @@ export const transcriptMarkerSchema = z.object({ at: z.string().optional(), }); -export const transcriptTaskRefSchema = z.object({ - kind: z.literal('taskref'), - refId: z.string(), - taskId: taskIdSchema, - at: z.string().optional(), -}); - export const transcriptItemSchema = z.discriminatedUnion('kind', [ transcriptTurnSchema, transcriptMarkerSchema, - transcriptTaskRefSchema, ]); -export const transcriptTaskSchema = z.object({ - taskId: taskIdSchema, - kind: z.enum(['shell', 'subagent', 'tool', 'other']), - state: z.enum(['running', 'completed', 'failed', 'timed_out', 'killed', 'lost']), - detached: z.boolean(), - description: z.string().optional(), - agentId: agentIdSchema.optional(), - outputTail: z.string(), - startedAt: z.string().optional(), - endedAt: z.string().optional(), - resultSummary: z.string().optional(), - error: z.string().optional(), - stateReason: z.string().optional(), - usage: stepUsageSchema.optional(), - model: z.string().optional(), - thinkingEffort: z.string().optional(), -}); - export const goalMetaSchema = z.object({ objective: z.string(), status: z.enum(['active', 'paused', 'blocked', 'complete']), @@ -379,7 +353,6 @@ export const transcriptPromptSchema = z.object({ export const agentTranscriptSnapshotSchema = z.object({ items: z.array(transcriptItemSchema), - tasks: z.array(transcriptTaskSchema), interactions: z.array(interactionSchema).default([]), attachments: z.array(attachmentSchema).default([]), todos: z.array(todoSchema).default([]), @@ -391,15 +364,12 @@ export const agentTranscriptSnapshotSchema = z.object({ export const turnHeaderSchema = transcriptTurnSchema.omit({ steps: true }); export const stepHeaderSchema = transcriptStepSchema.omit({ frames: true }); -export const appendTargetSchema = z.discriminatedUnion('type', [ - z.object({ - type: z.literal('frame'), - turnId: turnIdSchema, - stepId: stepIdSchema, - frameId: frameIdSchema, - }), - z.object({ type: z.literal('task'), taskId: taskIdSchema }), -]); +export const appendTargetSchema = z.object({ + type: z.literal('frame'), + turnId: turnIdSchema, + stepId: stepIdSchema, + frameId: frameIdSchema, +}); export const transcriptOperationSchema = z.discriminatedUnion('op', [ z.object({ op: z.literal('reset'), agentId: agentIdSchema, snapshot: agentTranscriptSnapshotSchema }), @@ -422,12 +392,6 @@ export const transcriptOperationSchema = z.discriminatedUnion('op', [ item: transcriptMarkerSchema, beforeTurn: z.number().int().optional(), }), - z.object({ - op: z.literal('taskref.upsert'), - item: transcriptTaskRefSchema, - beforeTurn: z.number().int().optional(), - }), - z.object({ op: z.literal('task.upsert'), task: transcriptTaskSchema }), z.object({ op: z.literal('interaction.upsert'), interaction: interactionSchema }), z.object({ op: z.literal('attachment.upsert'), attachment: attachmentSchema }), z.object({ op: z.literal('todo.upsert'), todo: todoSchema }), @@ -492,7 +456,6 @@ export const transcriptResponseSchema = z.object({ agent_id: agentIdSchema, items: z.array(transcriptItemSchema), has_more: z.boolean(), - tasks: z.array(transcriptTaskSchema), interactions: z.array(interactionSchema).default([]), attachments: z.array(attachmentSchema).default([]), todos: z.array(todoSchema).default([]), diff --git a/packages/transcript/src/history/foldFacts.ts b/packages/transcript/src/history/foldFacts.ts index 19fe392cbdd..ae55aaf356f 100644 --- a/packages/transcript/src/history/foldFacts.ts +++ b/packages/transcript/src/history/foldFacts.ts @@ -1,11 +1,10 @@ import type { AgentRef } from '../model/frame'; import type { TranscriptInteraction } from '../model/interaction'; -import type { TranscriptItem, TranscriptMarker, TranscriptTaskRef } from '../model/item'; +import type { TranscriptItem, TranscriptMarker } from '../model/item'; import { turnId as exportTurnKey } from '../model/ids'; import type { GoalMeta, GoalStatus, TranscriptMeta } from '../model/meta'; -import type { TranscriptTask } from '../model/task'; import type { TodoItem, TranscriptTodo } from '../model/todo'; -import type { StepUsage, TranscriptStep, TranscriptTurn } from '../model/turn'; +import type { TranscriptStep, TranscriptTurn } from '../model/turn'; import type { AgentTranscriptSnapshot } from '../ops/operation'; export interface HistoryWireRecord { @@ -27,17 +26,6 @@ interface GoalPayload { readonly budgetLimits?: { readonly tokenBudget?: unknown }; } -interface TaskInfoPayload { - readonly taskId?: unknown; - readonly kind?: unknown; - readonly status?: unknown; - readonly detached?: unknown; - readonly description?: unknown; - readonly agentId?: unknown; - readonly startedAt?: unknown; - readonly endedAt?: unknown; -} - interface InteractionRequestPayload { readonly id?: unknown; readonly kind?: unknown; @@ -137,26 +125,6 @@ function turnOriginKind(origin: unknown): TranscriptTurn['origin']['kind'] { } } -function mapTaskKind(kind: unknown): TranscriptTask['kind'] { - switch (kind) { - case 'process': - return 'shell'; - case 'agent': - return 'subagent'; - default: - return 'other'; - } -} - -const TASK_STATES = new Set([ - 'running', - 'completed', - 'failed', - 'timed_out', - 'killed', - 'lost', -]); - const GOAL_STATUSES = new Set(['active', 'paused', 'blocked', 'complete']); function mapInteractionEndState( @@ -205,34 +173,6 @@ function recordTimeIso(record: HistoryWireRecord): string | undefined { return undefined; } -function readFiniteNumber(raw: unknown): number | undefined { - return typeof raw === 'number' && Number.isFinite(raw) ? raw : undefined; -} - -function readStepUsage(raw: unknown): StepUsage | undefined { - if (raw === null || typeof raw !== 'object') return undefined; - const usage = raw as Record; - const inputOther = readFiniteNumber(usage['inputOther']); - const output = readFiniteNumber(usage['output']); - const inputCacheRead = readFiniteNumber(usage['inputCacheRead']); - const inputCacheCreation = readFiniteNumber(usage['inputCacheCreation']); - if ( - inputOther === undefined || - output === undefined || - inputCacheRead === undefined || - inputCacheCreation === undefined - ) { - return undefined; - } - return { inputOther, output, inputCacheRead, inputCacheCreation }; -} - -function epochMsToIso(value: unknown): string | undefined { - return typeof value === 'number' && Number.isFinite(value) - ? new Date(value).toISOString() - : undefined; -} - function payloadOf(record: HistoryWireRecord): Record { const { type: _type, time: _time, ...payload } = record; return payload; @@ -260,7 +200,6 @@ export function foldWireRecordFacts( readonly activeWireTurnId?: number; }, ): AgentTranscriptSnapshot { - const tasks = new Map(); const interactions = new Map(); const endedTurns = new Map(); const interruptedSteps = new Map>(); @@ -292,14 +231,10 @@ export function foldWireRecordFacts( const appended: TranscriptItem[] = []; const activeCancelTurnIds = new Set(); let markerSeq = 0; - const usedRefIds = new Set(); for (const item of base.items) { - if (item.kind === 'marker') { - const match = /^m(\d+)$/.exec(item.markerId); - if (match !== null) markerSeq = Math.max(markerSeq, Number(match[1])); - } else if (item.kind === 'taskref') { - usedRefIds.add(item.refId); - } + if (item.kind !== 'marker') continue; + const match = /^m(\d+)$/.exec(item.markerId); + if (match !== null) markerSeq = Math.max(markerSeq, Number(match[1])); } const pushMarker = (marker: string, record: HistoryWireRecord): void => { markerSeq += 1; @@ -313,65 +248,8 @@ export function foldWireRecordFacts( appended.push(item); }; - const subagentTasks = new Map(); const subagentRefs = new Map>(); - const adoptSpawnPlaceholder = (agentId: string, taskId: string): TranscriptTask | undefined => { - subagentTasks.set(agentId, taskId); - if (agentId === taskId) return undefined; - const placeholder = tasks.get(agentId); - if (placeholder === undefined || placeholder.kind !== 'subagent') return undefined; - tasks.delete(agentId); - return placeholder; - }; - - const upsertTask = (record: HistoryWireRecord): void => { - const info = record['info'] as TaskInfoPayload | undefined; - if (info === undefined || typeof info.taskId !== 'string') return; - const taskId = info.taskId; - const adopted = - record.type === 'task.started' && - info.kind === 'agent' && - typeof info.agentId === 'string' && - info.agentId.length > 0 - ? adoptSpawnPlaceholder(info.agentId, taskId) - : undefined; - const prev = tasks.get(taskId) ?? adopted; - const status = info.status; - const task: TranscriptTask = { - ...prev, - taskId, - kind: mapTaskKind(info.kind), - state: - typeof status === 'string' && TASK_STATES.has(status as TranscriptTask['state']) - ? (status as TranscriptTask['state']) - : (prev?.state ?? 'running'), - detached: typeof info.detached === 'boolean' ? info.detached : (prev?.detached ?? true), - description: typeof info.description === 'string' ? info.description : prev?.description, - agentId: typeof info.agentId === 'string' ? info.agentId : prev?.agentId, - outputTail: - typeof record['outputTail'] === 'string' - ? record['outputTail'] - : (prev?.outputTail ?? ''), - startedAt: prev?.startedAt ?? epochMsToIso(info.startedAt), - endedAt: epochMsToIso(info.endedAt) ?? prev?.endedAt, - }; - tasks.set(taskId, task); - if (record.type === 'task.started') { - const refId = `ref-${taskId}`; - if (!usedRefIds.has(refId)) { - usedRefIds.add(refId); - const ref: TranscriptTaskRef = { - kind: 'taskref', - refId, - taskId, - at: recordTimeIso(record), - }; - appended.push(ref); - } - } - }; - for (const record of records) { switch (record.type) { case 'subagent.spawned': { @@ -379,50 +257,10 @@ export function foldWireRecordFacts( const agentId = record['subagentId']; const toolCallId = record['parentToolCallId']; if (typeof agentId !== 'string' || typeof toolCallId !== 'string') break; - const taskId = typeof record['taskId'] === 'string' ? record['taskId'] : agentId; - subagentTasks.set(agentId, taskId); const index = typeof record['swarmIndex'] === 'number' ? record['swarmIndex'] : undefined; const refs = subagentRefs.get(toolCallId) ?? new Map(); refs.set(agentId, { ref: { agentId, role: index === undefined ? 'child' : 'member' }, index }); subagentRefs.set(toolCallId, refs); - tasks.set(taskId, { - taskId, - kind: 'subagent', - state: 'running', - detached: record['runInBackground'] === true, - agentId, - description: typeof record['description'] === 'string' ? record['description'] : undefined, - model: typeof record['model'] === 'string' ? record['model'] : undefined, - thinkingEffort: typeof record['thinkingEffort'] === 'string' ? record['thinkingEffort'] : undefined, - startedAt: recordTimeIso(record), - outputTail: '', - }); - break; - } - case 'subagent.started': - case 'subagent.completed': - case 'subagent.failed': - case 'subagent.cancelled': { - const agentId = record['subagentId']; - if (typeof agentId !== 'string') break; - const taskId = subagentTasks.get(agentId) ?? agentId; - const state = record.type === 'subagent.completed' ? 'completed' - : record.type === 'subagent.failed' ? 'failed' - : record.type === 'subagent.cancelled' ? 'killed' : 'running'; - const terminalise = (key: string): void => { - const task = tasks.get(key); - if (task === undefined) return; - tasks.set(key, { - ...task, - state, - endedAt: state === 'running' ? undefined : recordTimeIso(record), - resultSummary: typeof record['resultSummary'] === 'string' ? record['resultSummary'] : task.resultSummary, - usage: readStepUsage(record['usage']) ?? task.usage, - error: typeof record['error'] === 'string' ? record['error'] : task.error, - }); - }; - terminalise(taskId); - if (taskId !== agentId) terminalise(agentId); break; } case 'tools.update_store': { @@ -527,11 +365,6 @@ export function foldWireRecordFacts( towerActive = false; break; } - case 'task.started': - case 'task.terminated': { - upsertTask(record); - break; - } case 'turn.cancel': { const payload = record as TurnCancelPayload; if ( @@ -943,7 +776,6 @@ export function foldWireRecordFacts( return { ...base, items: restoredItems, - tasks: [...tasks.values()], interactions: [...interactions.values()], todos: todo !== undefined ? [todo] : base.todos, meta, diff --git a/packages/transcript/src/history/groupTurns.ts b/packages/transcript/src/history/groupTurns.ts index 1a5640527ee..fd7553fb7a6 100644 --- a/packages/transcript/src/history/groupTurns.ts +++ b/packages/transcript/src/history/groupTurns.ts @@ -144,6 +144,7 @@ interface StepDraft { } const HIDDEN_USER_ORIGINS = new Set(['injection', 'system_trigger', 'retry']); +const LOST_TASK_REMINDER_VARIANT = 'task_resume_termination'; const TURN_OPENING_SYSTEM_TRIGGERS = new Set(['goal_continuation', 'subagent']); const MARKER_USER_ORIGINS: Readonly> = { skill_activation: 'skill', @@ -387,6 +388,10 @@ export function groupMessagesIntoSnapshot( for (const part of hookParts) { pushMarker('hook', userPromptSubmitHookMarkerPayload(part)); } + if (isLostTaskReminder(message)) { + pushMarker('notice', { level: 'warning', message: reminderText(textOf(message)) }); + continue; + } if (originKind !== undefined && HIDDEN_USER_ORIGINS.has(originKind)) { if (opensOwnTurn(message)) { const opening = @@ -534,7 +539,7 @@ export function groupMessagesIntoSnapshot( flushSteeredLeftovers(); - return { items, tasks: [], interactions: [], attachments, todos: [], prompts: [], meta: {} }; + return { items, interactions: [], attachments, todos: [], prompts: [], meta: {} }; } function notificationFrameText(text: string): string { @@ -672,6 +677,15 @@ function originFileAttachments(message: HistoryMessage): readonly OriginFileAtta ); } +function isLostTaskReminder(message: HistoryMessage): boolean { + const origin = message.origin as { kind?: unknown; variant?: unknown } | undefined; + return origin?.kind === 'injection' && origin.variant === LOST_TASK_REMINDER_VARIANT; +} + +function reminderText(text: string): string { + return text.replace(/^\n/, '').replace(/\n<\/system-reminder>$/, ''); +} + function textOf(message: HistoryMessage): string { return (message.content ?? []) .filter((part): part is { readonly type: 'text'; readonly text: string } => part.type === 'text' && 'text' in part) diff --git a/packages/transcript/src/index.ts b/packages/transcript/src/index.ts index affc4316c81..48bbc8be11f 100644 --- a/packages/transcript/src/index.ts +++ b/packages/transcript/src/index.ts @@ -5,7 +5,6 @@ export * from './model/interaction'; export * from './model/attachment'; export * from './model/todo'; export * from './model/item'; -export * from './model/task'; export * from './model/meta'; export * from './model/prompt'; export * from './ops/operation'; diff --git a/packages/transcript/src/model/ids.ts b/packages/transcript/src/model/ids.ts index f9c6c35bd65..e2c70cadc6d 100644 --- a/packages/transcript/src/model/ids.ts +++ b/packages/transcript/src/model/ids.ts @@ -2,7 +2,6 @@ export type TurnId = string; export type StepId = string; export type FrameId = string; export type MarkerId = string; -export type TaskRefId = string; export type TaskId = string; export type AgentId = string; export type InteractionId = string; diff --git a/packages/transcript/src/model/item.ts b/packages/transcript/src/model/item.ts index 5bead6c6198..3c272c84b08 100644 --- a/packages/transcript/src/model/item.ts +++ b/packages/transcript/src/model/item.ts @@ -1,4 +1,4 @@ -import type { MarkerId, TaskId, TaskRefId } from './ids'; +import type { MarkerId } from './ids'; import type { TranscriptTurn } from './turn'; export type MarkerKey = string; @@ -27,14 +27,7 @@ export interface TranscriptMarker { readonly at?: string; } -export interface TranscriptTaskRef { - readonly kind: 'taskref'; - readonly refId: TaskRefId; - readonly taskId: TaskId; - readonly at?: string; -} - -export type TranscriptItem = TranscriptTurn | TranscriptMarker | TranscriptTaskRef; +export type TranscriptItem = TranscriptTurn | TranscriptMarker; export function itemId(item: TranscriptItem): string { switch (item.kind) { @@ -42,7 +35,5 @@ export function itemId(item: TranscriptItem): string { return item.turnId; case 'marker': return item.markerId; - case 'taskref': - return item.refId; } } diff --git a/packages/transcript/src/model/task.ts b/packages/transcript/src/model/task.ts deleted file mode 100644 index de4caa8f778..00000000000 --- a/packages/transcript/src/model/task.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { AgentId, TaskId } from './ids'; -import type { StepUsage } from './turn'; - -export type TaskKind = 'shell' | 'subagent' | 'tool' | 'other'; - -export type TaskState = - | 'running' - | 'completed' - | 'failed' - | 'timed_out' - | 'killed' - | 'lost'; - -export interface TranscriptTask { - readonly taskId: TaskId; - readonly kind: TaskKind; - readonly state: TaskState; - readonly detached: boolean; - readonly description?: string; - readonly agentId?: AgentId; - readonly outputTail: string; - readonly startedAt?: string; - readonly endedAt?: string; - readonly resultSummary?: string; - readonly error?: string; - readonly stateReason?: string; - readonly usage?: StepUsage; - readonly model?: string; - readonly thinkingEffort?: string; -} diff --git a/packages/transcript/src/ops/apply.ts b/packages/transcript/src/ops/apply.ts index c0220739c25..fc0878e12cd 100644 --- a/packages/transcript/src/ops/apply.ts +++ b/packages/transcript/src/ops/apply.ts @@ -1,4 +1,4 @@ -import type { AttachmentId, InteractionId, PromptId, TaskId, TodoId, TurnId } from '../model/ids'; +import type { AttachmentId, InteractionId, PromptId, TodoId, TurnId } from '../model/ids'; import { turnOrdinal } from '../model/ids'; import type { TranscriptAttachment } from '../model/attachment'; import type { TranscriptFrame } from '../model/frame'; @@ -6,7 +6,6 @@ import type { TranscriptInteraction } from '../model/interaction'; import type { TranscriptItem } from '../model/item'; import type { TranscriptMeta, TranscriptMetaMerge } from '../model/meta'; import type { TranscriptPrompt } from '../model/prompt'; -import type { TranscriptTask } from '../model/task'; import type { TranscriptTodo } from '../model/todo'; import type { TranscriptStep, TranscriptTurn } from '../model/turn'; import type { @@ -18,7 +17,6 @@ import type { export interface AgentState { readonly items: readonly TranscriptItem[]; - readonly tasks: ReadonlyMap; readonly interactions: ReadonlyMap; readonly attachments: ReadonlyMap; readonly todos: ReadonlyMap; @@ -30,7 +28,6 @@ export interface AgentState { export const EMPTY_AGENT_STATE: AgentState = { items: [], - tasks: new Map(), interactions: new Map(), attachments: new Map(), todos: new Map(), @@ -60,10 +57,6 @@ export function applyOperation(state: AgentState, op: TranscriptOperation): Appl return applyAppend(state, op); case 'marker.upsert': return applyItemUpsert(state, op.item, op.item.markerId, op.beforeTurn); - case 'taskref.upsert': - return applyItemUpsert(state, op.item, op.item.refId, op.beforeTurn); - case 'task.upsert': - return applyTaskUpsert(state, op.task); case 'interaction.upsert': return applyInteractionUpsert(state, op.interaction); case 'attachment.upsert': @@ -87,7 +80,6 @@ function applyReset(state: AgentState, op: Extract [task.taskId, task])), interactions: new Map( op.snapshot.interactions.map((interaction) => [interaction.interactionId, interaction]), ), @@ -301,7 +293,6 @@ function frameEquals(a: TranscriptFrame, b: TranscriptFrame): boolean { } function applyAppend(state: AgentState, op: AppendOp): ApplyResult { - if (op.target.type === 'task') return applyTaskAppend(state, op); const { turnId, stepId, frameId } = op.target; const turn = getTurn(state, turnId); const step = turn?.steps.find((entry) => entry.stepId === stepId); @@ -327,22 +318,6 @@ function applyAppend(state: AgentState, op: AppendOp): ApplyResult { }; } -function applyTaskAppend(state: AgentState, op: AppendOp): ApplyResult { - if (op.target.type !== 'task') throw new Error('unreachable'); - const taskId = op.target.taskId; - const task = state.tasks.get(taskId); - const current = task?.outputTail ?? ''; - const merged = appendAtOffset(current, op.offset, op.text); - if (merged.gap) return { state, changed: false, gap: merged.gap }; - if (!merged.changed) return { state, changed: false }; - const nextTask: TranscriptTask = task - ? { ...task, outputTail: merged.text } - : { taskId, kind: 'other', state: 'running', detached: false, outputTail: merged.text }; - const tasks = new Map(state.tasks); - tasks.set(taskId, nextTask); - return { state: { ...state, tasks }, changed: true }; -} - export function appendAtOffset( local: string, offset: number, @@ -401,8 +376,6 @@ function itemIdOf(item: TranscriptItem): string { return item.turnId; case 'marker': return item.markerId; - case 'taskref': - return item.refId; } } @@ -442,14 +415,6 @@ function applyItemsRemove(state: AgentState, ids: readonly string[]): ApplyResul return { state: { ...state, items, interactions, pendingInteractions: pending }, changed: true }; } -function applyTaskUpsert(state: AgentState, task: TranscriptTask): ApplyResult { - const current = state.tasks.get(task.taskId); - if (current && taskEquals(current, task)) return { state, changed: false }; - const tasks = new Map(state.tasks); - tasks.set(task.taskId, task); - return { state: { ...state, tasks }, changed: true }; -} - function applyInteractionUpsert( state: AgentState, interaction: TranscriptInteraction, @@ -535,23 +500,6 @@ function promptEquals(a: TranscriptPrompt, b: TranscriptPrompt): boolean { ); } -function taskEquals(a: TranscriptTask, b: TranscriptTask): boolean { - return ( - a.kind === b.kind && - a.state === b.state && - a.detached === b.detached && - a.description === b.description && - a.agentId === b.agentId && - a.outputTail === b.outputTail && - a.startedAt === b.startedAt && - a.endedAt === b.endedAt && - a.resultSummary === b.resultSummary && - a.error === b.error && - a.stateReason === b.stateReason && - a.usage === b.usage - ); -} - function applyMetaMerge(state: AgentState, meta: TranscriptMetaMerge): ApplyResult { const modes = meta.modes !== undefined diff --git a/packages/transcript/src/ops/operation.ts b/packages/transcript/src/ops/operation.ts index b33dea199b0..82492183bbc 100644 --- a/packages/transcript/src/ops/operation.ts +++ b/packages/transcript/src/ops/operation.ts @@ -2,16 +2,14 @@ import type { AgentId, FrameId, StepId, - TaskId, TurnId, } from '../model/ids'; import type { TranscriptAttachment } from '../model/attachment'; import type { TranscriptFrame } from '../model/frame'; import type { TranscriptInteraction } from '../model/interaction'; -import type { TranscriptItem, TranscriptMarker, TranscriptTaskRef } from '../model/item'; +import type { TranscriptItem, TranscriptMarker } from '../model/item'; import type { TranscriptMeta, TranscriptMetaMerge } from '../model/meta'; import type { TranscriptPrompt } from '../model/prompt'; -import type { TranscriptTask } from '../model/task'; import type { TranscriptTodo } from '../model/todo'; import type { TranscriptStep, TranscriptTurn } from '../model/turn'; @@ -42,9 +40,12 @@ export interface FrameUpsertOp { readonly frame: TranscriptFrame; } -export type AppendTarget = - | { readonly type: 'frame'; readonly turnId: TurnId; readonly stepId: StepId; readonly frameId: FrameId } - | { readonly type: 'task'; readonly taskId: TaskId }; +export interface AppendTarget { + readonly type: 'frame'; + readonly turnId: TurnId; + readonly stepId: StepId; + readonly frameId: FrameId; +} export interface AppendOp { readonly op: 'append'; @@ -59,17 +60,6 @@ export interface MarkerUpsertOp { readonly beforeTurn?: number; } -export interface TaskRefUpsertOp { - readonly op: 'taskref.upsert'; - readonly item: TranscriptTaskRef; - readonly beforeTurn?: number; -} - -export interface TaskUpsertOp { - readonly op: 'task.upsert'; - readonly task: TranscriptTask; -} - export interface InteractionUpsertOp { readonly op: 'interaction.upsert'; readonly interaction: TranscriptInteraction; @@ -107,8 +97,6 @@ export type TranscriptOperation = | FrameUpsertOp | AppendOp | MarkerUpsertOp - | TaskRefUpsertOp - | TaskUpsertOp | InteractionUpsertOp | AttachmentUpsertOp | TodoUpsertOp @@ -123,7 +111,6 @@ export interface TranscriptOpBatch { export interface AgentTranscriptSnapshot { readonly items: readonly TranscriptItem[]; - readonly tasks: readonly TranscriptTask[]; readonly interactions: readonly TranscriptInteraction[]; readonly attachments: readonly TranscriptAttachment[]; readonly todos: readonly TranscriptTodo[]; diff --git a/packages/transcript/src/store/agentTranscript.ts b/packages/transcript/src/store/agentTranscript.ts index 0f7a28da36b..0f1ec4c2653 100644 --- a/packages/transcript/src/store/agentTranscript.ts +++ b/packages/transcript/src/store/agentTranscript.ts @@ -1,10 +1,9 @@ -import type { AgentId, AttachmentId, InteractionId, PromptId, TaskId, TodoId, TurnId } from '../model/ids'; +import type { AgentId, AttachmentId, InteractionId, PromptId, TodoId, TurnId } from '../model/ids'; import type { TranscriptAttachment } from '../model/attachment'; import type { TranscriptInteraction } from '../model/interaction'; import type { TranscriptItem } from '../model/item'; import type { TranscriptMeta } from '../model/meta'; import type { TranscriptPrompt } from '../model/prompt'; -import type { TranscriptTask } from '../model/task'; import type { TranscriptTodo } from '../model/todo'; import type { TranscriptTurn } from '../model/turn'; import { @@ -74,14 +73,6 @@ export class AgentTranscript { return item?.kind === 'turn' ? item : undefined; } - getTasks(): ReadonlyMap { - return this.#state.tasks; - } - - getTask(taskId: TaskId): TranscriptTask | undefined { - return this.#state.tasks.get(taskId); - } - getInteractions(): ReadonlyMap { return this.#state.interactions; } @@ -150,7 +141,6 @@ export class AgentTranscript { } return { items, - tasks: [...this.#state.tasks.values()], interactions: [...this.#state.interactions.values()], attachments: [...this.#state.attachments.values()], todos: [...this.#state.todos.values()], diff --git a/packages/transcript/src/view/registry.ts b/packages/transcript/src/view/registry.ts index dd280e5a8a8..6fe244c01d8 100644 --- a/packages/transcript/src/view/registry.ts +++ b/packages/transcript/src/view/registry.ts @@ -1,10 +1,8 @@ import type { ToolCallFrame } from '../model/frame'; -import type { TranscriptTask } from '../model/task'; import type { TurnOrigin } from '../model/turn'; export interface ToolViewContext { readonly frame: ToolCallFrame; - readonly task?: TranscriptTask; } export interface InputViewContext { diff --git a/packages/transcript/test/layers.test.ts b/packages/transcript/test/layers.test.ts index 2797f64f522..1c7c077dcf1 100644 --- a/packages/transcript/test/layers.test.ts +++ b/packages/transcript/test/layers.test.ts @@ -17,8 +17,7 @@ import { import type { TranscriptItem } from '#/model/item'; import type { AgentTranscriptSnapshot, TranscriptOperation } from '#/ops/operation'; -const idLabel = (i: TranscriptItem): string => - i.kind === 'turn' ? i.turnId : i.kind === 'marker' ? i.markerId : i.refId; +const idLabel = (i: TranscriptItem): string => (i.kind === 'turn' ? i.turnId : i.markerId); describe('client metadata in transcript user origins', () => { it('retains user-invoked single skill frame metadata without exposing model-triggered activations as user input', () => { @@ -203,7 +202,6 @@ describe('granularity', () => { }, { kind: 'marker', markerId: 'm1', marker: 'skill' }, ], - tasks: [], interactions: [ { interactionId: 'appr-1', @@ -311,14 +309,12 @@ describe('ViewRegistry', () => { describe('contract schemas', () => { it('roundtrips every op kind', () => { const ops: TranscriptOperation[] = [ - { op: 'reset', agentId: 'main', snapshot: { items: [], tasks: [], interactions: [], attachments: [], todos: [], prompts: [], meta: {}, hasMoreOlder: true } }, + { op: 'reset', agentId: 'main', snapshot: { items: [], interactions: [], attachments: [], todos: [], prompts: [], meta: {}, hasMoreOlder: true } }, turnOp(1), stepOp, frameOp, appendOp, { op: 'marker.upsert', item: { kind: 'marker', markerId: 'm1', marker: 'goal' } }, - { op: 'taskref.upsert', item: { kind: 'taskref', refId: 'r1', taskId: 'task1' } }, - { op: 'task.upsert', task: { taskId: 'task1', kind: 'shell', state: 'running', detached: false, outputTail: '' } }, { op: 'interaction.upsert', interaction: { interactionId: 'appr-1', interactionKind: 'approval', toolCallId: 'c1', state: 'pending' }, @@ -345,7 +341,6 @@ describe('contract schemas', () => { agentId: 'main', snapshot: { items: [], - tasks: [], interactions: [], attachments: [], todos: [], @@ -414,16 +409,6 @@ describe('contract schemas', () => { progress: { kind: 'progress', text: 'half', percent: 50, customKind: 'bar', customData: { x: 1 } }, }, }, - { - op: 'task.upsert', - task: { - taskId: 'task1', kind: 'subagent', state: 'completed', detached: false, outputTail: '', - resultSummary: 'scanned 12 files', - error: 'partial failure', - stateReason: 'waiting for input', - usage, - }, - }, { op: 'meta.merge', meta: { agent: { model: 'k2', phase: { kind: 'ended', turnId: 1, reason: 'completed', durationMs: 1500, at: 2000 } } }, @@ -1487,6 +1472,32 @@ describe('groupMessagesIntoSnapshot (cold path)', () => { expect(cronTurn?.kind === 'turn' && cronTurn.origin.kind).toBe('cron'); }); + it('shows the reminder about background tasks lost on exit as a warning notice', () => { + const snapshot = groupMessagesIntoSnapshot([ + { role: 'user', content: [{ type: 'text', text: 'hi' }], toolCalls: [], origin: { kind: 'user' } }, + { role: 'assistant', content: [{ type: 'text', text: 'started' }], toolCalls: [] }, + { + role: 'user', + content: [{ type: 'text', text: '\nThe user exited the application.\n- bash-1: npm run dev\n' }], + toolCalls: [], + origin: { kind: 'injection', variant: 'task_resume_termination' } as { kind: string }, + }, + { + role: 'user', + content: [{ type: 'text', text: 'mode reminder' }], + toolCalls: [], + origin: { kind: 'injection', variant: 'permission_mode' } as { kind: string }, + }, + ]); + + const markers = snapshot.items.filter((item) => item.kind === 'marker'); + expect(markers).toHaveLength(1); + expect(markers[0]).toMatchObject({ + marker: 'notice', + payload: { level: 'warning', message: 'The user exited the application.\n- bash-1: npm run dev' }, + }); + }); + it('opens a task turn for a notification with a task-origin turn.started in the wire', () => { const snapshot = groupMessagesIntoSnapshot( [ @@ -1540,75 +1551,6 @@ describe('foldWireRecordFacts (cold facts)', () => { }, ]); - it('folds terminal subagent outcomes, background promotion, and a reused member generation', () => { - const spawned = (subagentId: string, time: number): HistoryWireRecord => ({ - type: 'subagent.spawned', subagentId, subagentName: 'explore', parentAgentId: 'main', - parentToolCallId: 'swarm', runInBackground: false, time, - }); - const folded = foldWireRecordFacts([ - spawned('failed', 1000), - { type: 'subagent.failed', subagentId: 'failed', error: 'offline', time: 2000 }, - spawned('cancelled', 1000), - { type: 'subagent.cancelled', subagentId: 'cancelled', time: 2000 }, - spawned('reused', 1000), - { type: 'subagent.completed', subagentId: 'reused', resultSummary: 'old', time: 2000 }, - spawned('reused', 3000), - { ...spawned('background', 1000), taskId: 'task-1', runInBackground: true }, - { type: 'subagent.completed', subagentId: 'background', resultSummary: 'done', usage: { inputOther: 5, output: 7, inputCacheRead: 1, inputCacheCreation: 2 }, time: 2000 }, - { type: 'task.terminated', info: { taskId: 'task-1', kind: 'agent', status: 'completed', agentId: 'background', detached: true, startedAt: 1000, endedAt: 2000 } }, - { ...spawned('foreign', 1000), parentAgentId: 'other' }, - ], baseWithMarker(), { agentId: 'main' }); - expect(folded.tasks).toEqual(expect.arrayContaining([ - expect.objectContaining({ taskId: 'failed', state: 'failed', error: 'offline' }), - expect.objectContaining({ taskId: 'cancelled', state: 'killed' }), - expect.objectContaining({ taskId: 'reused', state: 'running', startedAt: new Date(3000).toISOString() }), - expect.objectContaining({ taskId: 'task-1', state: 'completed', resultSummary: 'done', detached: true, usage: { inputOther: 5, output: 7, inputCacheRead: 1, inputCacheCreation: 2 } }), - ])); - expect(folded.tasks).toHaveLength(4); - expect(folded.tasks.find((task) => task.taskId === 'reused')?.resultSummary).toBeUndefined(); - expect(folded.tasks.find((task) => task.taskId === 'failed')?.usage).toBeUndefined(); - }); - - it('adopts the spawn placeholder into the task registered afterwards', () => { - const folded = foldWireRecordFacts([ - { type: 'subagent.spawned', subagentId: 'child', subagentName: 'explore', parentAgentId: 'main', parentToolCallId: 'tower', runInBackground: false, description: 'review the diff', model: 'k2', time: 1000 }, - { type: 'task.started', info: { taskId: 'task-9', kind: 'agent', status: 'running', agentId: 'child', detached: false, startedAt: 1100 }, time: 1100 }, - { type: 'subagent.completed', subagentId: 'child', resultSummary: 'done', usage: { inputOther: 3, output: 4, inputCacheRead: 0, inputCacheCreation: 0 }, time: 2000 }, - ], baseWithMarker(), { agentId: 'main' }); - expect(folded.tasks).toEqual([ - expect.objectContaining({ - taskId: 'task-9', agentId: 'child', state: 'completed', resultSummary: 'done', - description: 'review the diff', model: 'k2', - startedAt: new Date(1000).toISOString(), - usage: { inputOther: 3, output: 4, inputCacheRead: 0, inputCacheCreation: 0 }, - }), - ]); - }); - - it('terminalises a background member spawned before its task was registered', () => { - const folded = foldWireRecordFacts([ - { type: 'subagent.spawned', subagentId: 'worker', subagentName: 'tower-worker', parentAgentId: 'main', parentToolCallId: 'tower-spawn', runInBackground: true, description: 'build the feature', time: 1000 }, - { type: 'task.started', info: { taskId: 'task-7', kind: 'agent', status: 'running', agentId: 'worker', detached: true, startedAt: 1100 }, time: 1100 }, - { type: 'task.terminated', info: { taskId: 'task-7', kind: 'agent', status: 'lost', agentId: 'worker', detached: true, startedAt: 1100, endedAt: 3000 }, time: 3000 }, - ], baseWithMarker(), { agentId: 'main' }); - expect(folded.tasks).toEqual([ - expect.objectContaining({ taskId: 'task-7', agentId: 'worker', state: 'lost', detached: true, description: 'build the feature' }), - ]); - }); - - it('keeps a terminal subagent record in sync with a placeholder left by an earlier generation', () => { - const folded = foldWireRecordFacts([ - { type: 'subagent.spawned', subagentId: 'worker', subagentName: 'explore', parentAgentId: 'main', parentToolCallId: 'agent-a', runInBackground: false, time: 1000 }, - { type: 'subagent.spawned', subagentId: 'worker', subagentName: 'explore', parentAgentId: 'main', parentToolCallId: 'agent-b', runInBackground: false, taskId: 'task-7', time: 2000 }, - { type: 'subagent.failed', subagentId: 'worker', error: 'offline', time: 3000 }, - ], baseWithMarker(), { agentId: 'main' }); - expect(folded.tasks).toEqual(expect.arrayContaining([ - expect.objectContaining({ taskId: 'task-7', state: 'failed', error: 'offline' }), - expect.objectContaining({ taskId: 'worker', state: 'failed', error: 'offline' }), - ])); - expect(folded.tasks).toHaveLength(2); - }); - it('returns the base snapshot unchanged when no fact records exist (old sessions)', () => { const base = baseWithMarker(); const folded = foldWireRecordFacts( @@ -1905,102 +1847,6 @@ describe('foldWireRecordFacts (cold facts)', () => { ]); }); - it('folds task records into task entities and timeline taskrefs', () => { - const base = baseWithMarker(); - const folded = foldWireRecordFacts( - [ - { - type: 'task.started', - info: { - taskId: 'task_1', - kind: 'process', - description: 'pnpm test', - status: 'running', - startedAt: 1000, - endedAt: null, - }, - time: 1000, - }, - { - type: 'task.started', - info: { - taskId: 'task_2', - kind: 'agent', - description: 'scan the repo', - status: 'running', - detached: false, - agentId: 'sub-1', - startedAt: 2000, - endedAt: null, - }, - time: 2000, - }, - { - type: 'task.terminated', - info: { - taskId: 'task_1', - kind: 'process', - description: 'pnpm test', - status: 'completed', - startedAt: 1000, - endedAt: 5000, - }, - outputTail: '42 passed', - time: 5000, - }, - ], - base, - ); - - expect(folded.tasks).toEqual([ - { - taskId: 'task_1', - kind: 'shell', - state: 'completed', - detached: true, - description: 'pnpm test', - agentId: undefined, - outputTail: '42 passed', - startedAt: new Date(1000).toISOString(), - endedAt: new Date(5000).toISOString(), - }, - { - taskId: 'task_2', - kind: 'subagent', - state: 'running', - detached: false, - description: 'scan the repo', - agentId: 'sub-1', - outputTail: '', - startedAt: new Date(2000).toISOString(), - endedAt: undefined, - }, - ]); - const refs = folded.items.filter((item) => item.kind === 'taskref'); - expect(refs).toEqual([ - { kind: 'taskref', refId: 'ref-task_1', taskId: 'task_1', at: new Date(1000).toISOString() }, - { kind: 'taskref', refId: 'ref-task_2', taskId: 'task_2', at: new Date(2000).toISOString() }, - ]); - }); - - it('maps unknown task kinds to other and survives malformed task records', () => { - const base = baseWithMarker(); - const folded = foldWireRecordFacts( - [ - { - type: 'task.started', - info: { taskId: 'task_q', kind: 'question', status: 'running', startedAt: 1000, endedAt: null }, - time: 1000, - }, - { type: 'task.started', time: 2000 }, - { type: 'task.terminated', info: { kind: 'process' }, time: 3000 }, - ], - base, - ); - expect(folded.tasks).toHaveLength(1); - expect(folded.tasks[0]).toMatchObject({ taskId: 'task_q', kind: 'other', state: 'running' }); - }); - it('folds interaction request/resolved into entities with terminal states', () => { const base = baseWithMarker(); const folded = foldWireRecordFacts( diff --git a/packages/transcript/test/store.test.ts b/packages/transcript/test/store.test.ts index 2034bad33ab..87cec77e760 100644 --- a/packages/transcript/test/store.test.ts +++ b/packages/transcript/test/store.test.ts @@ -13,9 +13,7 @@ import type { TranscriptInteraction } from '#/model/interaction'; import type { TranscriptItem } from '#/model/item'; function itemLabel(item: TranscriptItem): string { - if (item.kind === 'turn') return item.turnId; - if (item.kind === 'marker') return item.markerId; - return item.refId; + return item.kind === 'turn' ? item.turnId : item.markerId; } const turn1: TurnUpsertOp = { @@ -303,27 +301,6 @@ describe('AgentTranscript', () => { expect(frame?.kind === 'tool' && frame.progress).toEqual({ kind: 'progress', percent: 50 }); }); - it('task upserts carry resultSummary/error/stateReason/usage', () => { - const tx = new AgentTranscript('main'); - tx.apply([ - { op: 'task.upsert', task: { taskId: 'task1', kind: 'subagent', state: 'running', detached: false, outputTail: '' } }, - ]); - const done = tx.apply([ - { - op: 'task.upsert', - task: { - taskId: 'task1', kind: 'subagent', state: 'completed', detached: false, outputTail: '', - resultSummary: 'scanned 12 files', - usage: { inputOther: 100, output: 40, inputCacheRead: 10, inputCacheCreation: 5 }, - }, - }, - ]); - expect(done.accepted).toHaveLength(1); - const task = tx.getTask('task1'); - expect(task?.resultSummary).toBe('scanned 12 files'); - expect(task?.usage?.inputOther).toBe(100); - }); - it('items.remove clears anchored interactions and their pending entries', () => { const tx = new AgentTranscript('main'); tx.apply([ @@ -389,18 +366,6 @@ describe('AgentTranscript', () => { expect(seen).toEqual(['turn.upsert']); }); - it('task upsert + append keeps output tail globally, detached flips freely', () => { - const tx = new AgentTranscript('main'); - tx.apply([ - { op: 'task.upsert', task: { taskId: 'task1', kind: 'shell', state: 'running', detached: false, outputTail: '' } }, - { op: 'append', target: { type: 'task', taskId: 'task1' }, offset: 0, text: 'line1\n' }, - { op: 'task.upsert', task: { taskId: 'task1', kind: 'shell', state: 'running', detached: true, outputTail: 'line1\n' } }, - ]); - const task = tx.getTask('task1'); - expect(task?.detached).toBe(true); - expect(task?.outputTail).toBe('line1\n'); - }); - it('meta.merge merges goal/modes shallowly', () => { const tx = new AgentTranscript('main'); tx.apply([ @@ -477,12 +442,12 @@ describe('AgentTranscript', () => { turn: { kind: 'turn', turnId: 't1', ordinal: 1, state: 'completed', origin: { kind: 'user' } }, }, { - op: 'taskref.upsert', - item: { kind: 'taskref', refId: 'r1', taskId: 'bash-1' }, + op: 'marker.upsert', + item: { kind: 'marker', markerId: 'm2', marker: 'notice' }, beforeTurn: 2, }, ]); - expect(tx.getItems().map(itemLabel)).toEqual(['t0', 'm1', 't1', 'r1', 't2']); + expect(tx.getItems().map(itemLabel)).toEqual(['t0', 'm1', 't1', 'm2', 't2']); }); it('anchors a standalone item before the very first turn; re-applies stay in place', () => { From 70764101bd30772c0dba93da3245cf146f9df9dd Mon Sep 17 00:00:00 2001 From: qer Date: Fri, 30 Oct 2026 03:05:14 +0800 Subject: [PATCH 03/13] feat(kap-server): sync live tasks over WebSocket and drop the REST task API A connection that starts receiving an agent (its grade in subscribe_v2 is not off) gets one task.list frame with every task that agent is running right now, foreground commands and swarm members included, and then a task frame on every change straight from the engine's task service. Ended tasks are not resent: the last task frame of a task reports its outcome, the outcome stays in the session history, and task.watch replies with the task's state. The per connection list state no longer borrows the transcript's seeded flag, so a re-subscribe that raises one agent's grade neither drops nor resends the other agents' tasks. task.stop and task.background join task.watch / task.unwatch as flat requests on the same per-task queue, and every reply can carry the task's current state. They replace the REST task endpoints, which are removed together with their schemas: list, detail, :cancel and :detach. task.watch keeps the fixes made during review: queued watches are skipped after the connection closes, setup errors reply 50001 and release the watch, success is sent only once the first chunk is ready, and a chunk the tail read caught mid-write is forwarded instead of dropped. --- docs/en/reference/server-api.md | 99 +-- docs/zh/reference/server-api.md | 99 +-- packages/kap-server/src/protocol/rest-task.ts | 38 -- packages/kap-server/src/protocol/task.ts | 35 - .../kap-server/src/protocol/ws-control.ts | 74 ++ packages/kap-server/src/protocol/ws-task.ts | 93 +++ .../src/routes/registerApiV1Routes.ts | 2 - packages/kap-server/src/routes/tasks.ts | 341 ---------- .../src/transport/ws/v1/protocol.ts | 11 + .../src/transport/ws/v1/registerWsV1.ts | 4 +- .../ws/v1/sessionEventBroadcaster.ts | 70 +- .../src/transport/ws/v1/sessionTaskFeed.ts | 51 ++ .../src/transport/ws/v1/taskRequests.ts | 276 ++++++++ .../src/transport/ws/v1/taskState.ts | 46 ++ .../src/transport/ws/v1/wsConnectionV1.ts | 33 +- .../apiSurface.snapshot.test.ts.snap | 12 - .../test/sessionEventBroadcaster.test.ts | 144 +++- packages/kap-server/test/tasks.test.ts | 445 ------------ .../test/transcriptContract.e2e.test.ts | 174 ++++- .../kap-server/test/wsConnectionV1.test.ts | 633 ++++++++++++++++++ 20 files changed, 1607 insertions(+), 1073 deletions(-) delete mode 100644 packages/kap-server/src/protocol/rest-task.ts delete mode 100644 packages/kap-server/src/protocol/task.ts create mode 100644 packages/kap-server/src/protocol/ws-task.ts delete mode 100644 packages/kap-server/src/routes/tasks.ts create mode 100644 packages/kap-server/src/transport/ws/v1/sessionTaskFeed.ts create mode 100644 packages/kap-server/src/transport/ws/v1/taskRequests.ts create mode 100644 packages/kap-server/src/transport/ws/v1/taskState.ts delete mode 100644 packages/kap-server/test/tasks.test.ts diff --git a/docs/en/reference/server-api.md b/docs/en/reference/server-api.md index a6485706639..a5456cc2b81 100644 --- a/docs/en/reference/server-api.md +++ b/docs/en/reference/server-api.md @@ -973,7 +973,7 @@ Downloads a prompt media file (an image or other attachment referenced by the se ### Messages and transcript -The `messages` endpoints page the main agent's flattened message history, while the `transcript` endpoints serve the structured per-agent transcript — turns, tasks, interactions, attachments — that the WebSocket [Transcript protocol](#transcript-protocol) streams live. Use these endpoints for history paging and catch-up, and the WebSocket subscription for the live tail. +The `messages` endpoints page the main agent's flattened message history, while the `transcript` endpoints serve the structured per-agent transcript — turns, interactions, attachments — that the WebSocket [Transcript protocol](#transcript-protocol) streams live. Use these endpoints for history paging and catch-up, and the WebSocket subscription for the live tail. | Method and path | Description | | --- | --- | @@ -1017,7 +1017,7 @@ On success, `data` is the message object in the item shape documented under `GET #### `GET /api/v1/sessions/{session_id}/transcript` -Returns one page of an agent's structured transcript: turns (with their steps and frames) plus the markers and task references between them. Live sessions answer from the in-memory store (the requested agent's persisted history is backfilled first); cold sessions rebuild the agent from the persisted wire records. This is the history half of the transcript surface — the live streaming half is the [Transcript protocol](#transcript-protocol) subscription. +Returns one page of an agent's structured transcript: turns (with their steps and frames) plus the markers between them. Live sessions answer from the in-memory store (the requested agent's persisted history is backfilled first); cold sessions rebuild the agent from the persisted wire records. This is the history half of the transcript surface — the live streaming half is the [Transcript protocol](#transcript-protocol) subscription. | Parameter | In | Type | Description | | --- | --- | --- | --- | @@ -1027,7 +1027,7 @@ Returns one page of an agent's structured transcript: turns (with their steps an | `after_turn` | query | string | Only turns newer than this turn id; mutually exclusive with `before_turn` | | `page_size` | query | integer | 1–100 turns. Default `20` | -The page unit is the turn: without a cursor the newest page is returned, and `has_more` reports that older turns remain. On success, `data` is `{ agent_id, items, has_more, tasks, interactions, attachments, todos, meta, agents, pending_interactions, seq? }` — `items` is the paged turn slice, `tasks` / `interactions` / `attachments` / `todos` / `meta` / `agents` / `pending_interactions` are global agent state that ships unpaginated with every response, and `seq` is the agent's op-batch watermark for resuming the stream (live sessions only). +The page unit is the turn: without a cursor the newest page is returned, and `has_more` reports that older turns remain. On success, `data` is `{ agent_id, items, has_more, interactions, attachments, todos, meta, agents, pending_interactions, seq? }` — `items` is the paged turn slice, `interactions` / `attachments` / `todos` / `meta` / `agents` / `pending_interactions` are global agent state that ships unpaginated with every response, and `seq` is the agent's op-batch watermark for resuming the stream (live sessions only). Tasks are not part of the transcript; they arrive as the WebSocket `task` frame (see [Events](#events)). - `40001`: validation failure — `before_turn` combined with `after_turn`, or a non-plain `agent_id` - `40401`: session not found @@ -1290,79 +1290,6 @@ On success the envelope `code` is `40909` (`question dismissed`) rather than `0` - `40405`: no pending question with that id - `40902`: the question was already resolved; `data` carries `{ resolved: false }` -### Background tasks - -Background tasks are the session's asynchronous units — background shells, subagents, and long-running tool tasks. The registry is live-only: a session not loaded in this server process reports an empty list. - -| Method and path | Description | -| --- | --- | -| `GET /api/v1/sessions/{session_id}/tasks` | List background tasks | -| `GET /api/v1/sessions/{session_id}/tasks/{task_id}` | Read a task (optional output preview) | -| `POST /api/v1/sessions/{session_id}/tasks/{task_id}:cancel` | Cancel a task | -| `POST /api/v1/sessions/{session_id}/tasks/{task_id}:detach` | Move a foreground task to the background | - -#### `GET /api/v1/sessions/{session_id}/tasks` - -Lists the session's background tasks. - -| Parameter | In | Type | Description | -| --- | --- | --- | --- | -| `session_id` | path | string | **Required.** Session id | -| `status` | query | string | Keep only one status: `running` / `completed` / `failed` / `cancelled` | - -On success, `data` is `{ items }` where each item is a task object `{ id, session_id, kind, description, status, created_at, started_at?, completed_at?, command?, model?, thinking_effort?, agent_id?, subagent_type?, parent_tool_call_id?, output_preview?, output_bytes? }`. `kind` is `bash` / `subagent` / `tool`; `command` is set for `bash` tasks, the model and agent fields for `subagent` tasks, and the output fields only when a task is read with `with_output`. Timed-out and lost tasks report `failed`; killed tasks report `cancelled`. - -- `40001`: validation failure — an unknown `status` -- `40401`: session not found - -#### `GET /api/v1/sessions/{session_id}/tasks/{task_id}` - -Reads one background task, optionally with a tail of its output. - -| Parameter | In | Type | Description | -| --- | --- | --- | --- | -| `session_id` | path | string | **Required.** Session id | -| `task_id` | path | string | **Required.** Task id | -| `with_output` | query | boolean | Include an output tail in the response. Default `false` | -| `output_bytes` | query | integer | Size of the requested output tail in bytes, minimum `0`. Default `32768` | - -On success, `data` is the task object documented under `GET /api/v1/sessions/{session_id}/tasks` above; with `with_output=true` and non-empty output, `output_preview` carries the tail text and `output_bytes` its byte length. - -- `40001`: validation failure -- `40401`: session not found -- `40406`: no task with that id (a cold session has no live tasks at all) - -#### `POST /api/v1/sessions/{session_id}/tasks/{task_id}:cancel` - -Cancels a running task. It dispatches through `POST /api/v1/sessions/{session_id}/tasks/{tail}` with `cancel` / `detach` as the supported actions — a bare task id or an unknown action fails `40001`. - -| Parameter | In | Type | Description | -| --- | --- | --- | --- | -| `session_id` | path | string | **Required.** Session id | -| `task_id` | path | string | **Required.** Task id | - -On success, `data` is `{ cancelled: true }`. - -- `40001`: missing or unknown action suffix -- `40401`: session not found -- `40406`: no task with that id -- `40904`: the task already finished; `data` carries `{ cancelled: false }` and `details.current_status` the terminal status - -#### `POST /api/v1/sessions/{session_id}/tasks/{task_id}:detach` - -Moves a running foreground task to the background without stopping it: the tool call waiting on the task returns immediately with a background-task result, the turn continues, and the task keeps running under the background task registry (its output is persisted, and its completion arrives as a task notification). Already-background or finished tasks are an idempotent no-op. It dispatches through `POST /api/v1/sessions/{session_id}/tasks/{tail}` with `cancel` / `detach` as the supported actions — a bare task id or an unknown action fails `40001`. - -| Parameter | In | Type | Description | -| --- | --- | --- | --- | -| `session_id` | path | string | **Required.** Session id | -| `task_id` | path | string | **Required.** Task id | - -On success, `data` is `{ detached, status }`: `detached` is `true` when the call moved a running foreground task to the background and `false` for the idempotent no-op; `status` is the task's status after the call. - -- `40001`: missing or unknown action suffix -- `40401`: session not found -- `40406`: no task with that id - ### Skills, tools, and MCP These endpoints expose the skill catalogs a session or workspace sees, the effective agent's tool list, and its MCP servers. Skill activation and MCP restart use the `:{action}` convention; activation is the REST analogue of the `/` slash command. @@ -2416,18 +2343,22 @@ Clients send JSON frames `{ "type", "id"?, "payload" }`; every request frame get | Frame | payload | Description | | --- | --- | --- | -| `subscribe` | `{ session_ids, cursors?, agent_filter? }` | Subscribe to session events; with `cursors` (per-session `{seq, epoch}`) the server replays missed durable events | -| `unsubscribe` | `{ session_ids }` | Drop session subscriptions | -| `subscribe_v2` | `{ session_id, transcript, transcript_since? }` | Subscribe to transcript streams (the only transcript channel); `transcript` sets per-agent grades | -| `unsubscribe_v2` | `{ session_id, agent_ids? }` | Detach transcript streams; omitting `agent_ids` means the whole session | -| `client_hello` | `{ client_id }` | Handshake frame; the remaining fields are legacy compatibility | +| `subscribe_v2` | `{ session_id, transcript, transcript_since? }` | Subscribe to a session (the only session subscription): its transcript streams and interactions; `transcript` sets per-agent grades | +| `unsubscribe_v2` | `{ session_id, agent_ids? }` | Detach transcript streams; omitting `agent_ids`, or detaching the last agent, drops the whole session subscription | +| `client_hello` | `{ client_id }` | Handshake frame; other fields are ignored | +| `task.watch` | flat: `{ request_id, session_id, agent_id, task_id, since_offset?, tail_bytes? }` | Stream one task's output to this connection, no session subscription needed; `agent_id` is the agent that owns the task (the `agent_id` of its `task` frame), `since_offset` resumes after output the client already has, and `tail_bytes` caps the size of the first chunk | +| `task.unwatch` | flat: `{ request_id, session_id, agent_id, task_id }` | Stop streaming that task's output | +| `task.stop` | flat: `{ request_id, session_id, agent_id, task_id }` | Stop a task; an ended task just replies with its state | +| `task.background` | flat: `{ request_id, session_id, agent_id, task_id }` | Stop the starting tool call from waiting so the task keeps running in the background; a task already in the background or ended just replies with its state | + +`task.watch`, `task.unwatch`, `task.stop`, and `task.background` are flat: their fields sit next to `type` instead of under `payload`, `request_id` replaces `id`, and the reply is `{ "type": "response", "request_id", "code", "msg"?, "task"? }` instead of an ack; a frame without a readable `request_id` closes the socket with code 1008. On success, `task.watch`, `task.stop`, and `task.background` carry the task's current state in `task` (the `task` frame shape below, without `type`, `session_id`, `agent_id`, and `event_created_at`). Requests for the same task are handled one at a time in arrival order, so a `task.unwatch` sent while a `task.watch` is still pending takes effect after it; different tasks, and other control frames, never wait on each other. A successful `task.watch` replies with code 0, then sends the last `tail_bytes` of output (32 KiB by default, at most 1 MiB; only the part after `since_offset` when it falls inside that window) as one `task.output` frame, with empty `text` when there is nothing to send, followed by live output; ended tasks can be watched too. Errors: `40001` malformed frame (including an out-of-range `tail_bytes`), `40401` session not found, `40406` the agent has no such task, `50001` internal error, `50003` output unreadable. ### Events Event frames look like `{ "type", "seq", "epoch"?, "volatile"?, "offset"?, "session_id"?, "timestamp", "payload" }`, where `type` is the event type itself. Two delivery scopes: - **Global events**: sent to every established connection, no subscription needed — `session.meta.updated`, `event.session.created`, `event.session.archived`, `event.session.work_changed`, `event.session.status_changed`, `event.workspace.*`, `event.config.*`, `event.model_catalog.*`. -- **Session events**: sent only to connections subscribed to that session, subject to `agent_filter`. Main families: +- **Session frames**: sent only to connections subscribed to that session with `subscribe_v2` — `transcript.reset` / `transcript.ops` (see [Transcript protocol](#transcript-protocol)), `task.list`, `task`, and `interaction`. `task.output` goes only to connections watching that task with `task.watch`. | Family | Main events | | --- | --- | @@ -2443,9 +2374,11 @@ Three global lifecycle events keep a cross-workspace overview fresh without poll Events also split into durable and volatile: durable events carry a strictly increasing `seq`, are journaled, and can be replayed; volatile events (the `*.delta` family, `tool.progress`, `shell.*`, and similar) are marked `volatile: true` and never replayed. When consuming a volatile text stream, compare `offset` (the cumulative character offset within the turn) against your locally accumulated text: below the local length means a duplicate frame; above means a gap that needs snapshot recovery. +Each agent's running tasks — shell commands in the foreground and background, subagents (swarm members included), and other asynchronous work — are synced by two flat frames, `task.list` and `task`, never replayed like `interaction`. When a connection starts receiving an agent's tasks (an agent whose grade in `subscribe_v2` is not `off` and that it was not receiving yet), it first gets `{ type: "task.list", session_id, agent_id, tasks }`, where `tasks` is every task the agent is running right now and replaces the client's list; after that it gets `{ type: "task", session_id, agent_id, event_created_at, ...task state }` on every change. An agent that joins the session later starts directly with `task` frames. The task state is `{ task_id, kind: "shell" | "subagent" | "other", status: "running" | "ended", outcome?, phase?, detached, description?, child_agent_id?, parent_tool_call_id?, swarm_index?, command?, started_at?, ended_at?, result_summary?, error?, state_reason?, model?, thinking? }`, and every frame carries the full state. `detached` means the tool call that started the task no longer waits for it, so it runs in the background; `outcome` (ended tasks only) is `completed` / `failed` / `timed_out` / `killed` / `lost`; `phase` (running subagents only) is `working` / `suspended`, with the reason in `state_reason`; `child_agent_id` is a subagent task's agent id; `swarm_index` is a swarm member's position in its swarm; `command` is a shell task's command. When a task ends, its last `task` frame (`status: "ended"`) is sent and it no longer appears in `task.list`: the outcome of an ended task lives in the session history (tool results and task notifications), and `task.watch` on it replies with its exact final state. Background tasks lost to a server restart show up in the transcript as a `notice` marker with `level: "warning"`. Tasks are independent of the transcript: a `reset` op inside `transcript.ops` after an undo does not touch tasks. Task output streams separately as `{ type: "task.output", session_id, agent_id, task_id, offset, text }`, where `offset` is the UTF-8 byte offset of `text` in the task's output. + ### Reconnect and recovery -After reconnecting, pass each session's last applied `{seq, epoch}` in `subscribe`'s `cursors`; the server replays the gap. If you fall more than the buffer (1000 events) behind, or the cursor is no longer valid, you get `resync_required` instead. In that case, call `GET /api/v1/sessions/{session_id}/snapshot` for a full snapshot (with `as_of_seq` and `epoch`), then subscribe again with the fresh cursor. +Subscriptions do not survive a disconnect. After reconnecting, send `subscribe_v2` again for each session: pass `transcript_since` to resume the transcript streams, or omit it to start from a fresh `transcript.reset`; pending interactions are resent after the ack, and every agent's `task.list` is sent again. Task output watches are dropped as well: send `task.watch` again with `since_offset` set to the end of the output you already have. Global events missed while disconnected are not replayed, so refresh the lists you keep (for example `GET /api/v1/sessions`) over REST. ### Transcript protocol diff --git a/docs/zh/reference/server-api.md b/docs/zh/reference/server-api.md index b58b13a5bb4..3b1577068e7 100644 --- a/docs/zh/reference/server-api.md +++ b/docs/zh/reference/server-api.md @@ -973,7 +973,7 @@ main agent 的实时状态汇总;读取它会在会话为冷态时将其恢复 ### 消息与转录 -`messages` 端点分页返回 main agent 的扁平化消息历史,`transcript` 端点则提供按 Agent 组织的结构化转录——轮次、任务、交互、附件——即 WebSocket [转录协议](#转录协议) 实时流式推送的内容。历史分页与补漏用这些端点,实时尾部用 WebSocket 订阅。 +`messages` 端点分页返回 main agent 的扁平化消息历史,`transcript` 端点则提供按 Agent 组织的结构化转录——轮次、交互、附件——即 WebSocket [转录协议](#转录协议) 实时流式推送的内容。历史分页与补漏用这些端点,实时尾部用 WebSocket 订阅。 | 方法与路径 | 说明 | | --- | --- | @@ -1017,7 +1017,7 @@ main agent 的实时状态汇总;读取它会在会话为冷态时将其恢复 #### `GET /api/v1/sessions/{session_id}/transcript` -返回某个 Agent 的结构化转录中的一页:轮次(含其步骤与帧)以及轮次之间的标记与任务引用。活跃会话从内存存储应答(先回填所请求 Agent 的持久化历史);冷会话则从持久化的线上记录重建 Agent。这是转录能力的历史半边——实时流式半边是 [转录协议](#转录协议) 订阅。 +返回某个 Agent 的结构化转录中的一页:轮次(含其步骤与帧)以及轮次之间的标记。活跃会话从内存存储应答(先回填所请求 Agent 的持久化历史);冷会话则从持久化的线上记录重建 Agent。这是转录能力的历史半边——实时流式半边是 [转录协议](#转录协议) 订阅。 | 参数 | 位置 | 类型 | 说明 | | --- | --- | --- | --- | @@ -1027,7 +1027,7 @@ main agent 的实时状态汇总;读取它会在会话为冷态时将其恢复 | `after_turn` | query | string | 只保留晚于该轮次 id 的轮次;与 `before_turn` 互斥 | | `page_size` | query | integer | 1–100 个轮次。默认 `20` | -分页单位是轮次:不带游标时返回最新的一页,`has_more` 表示还有更早的轮次。成功时,`data` 为 `{ agent_id, items, has_more, tasks, interactions, attachments, todos, meta, agents, pending_interactions, seq? }`——`items` 是本次分页的轮次切片,`tasks` / `interactions` / `attachments` / `todos` / `meta` / `agents` / `pending_interactions` 是不分页、随每次响应一起返回的全局 Agent 状态,`seq` 是该 Agent 用于恢复流的 op 批次水位(仅活跃会话)。 +分页单位是轮次:不带游标时返回最新的一页,`has_more` 表示还有更早的轮次。成功时,`data` 为 `{ agent_id, items, has_more, interactions, attachments, todos, meta, agents, pending_interactions, seq? }`——`items` 是本次分页的轮次切片,`interactions` / `attachments` / `todos` / `meta` / `agents` / `pending_interactions` 是不分页、随每次响应一起返回的全局 Agent 状态,`seq` 是该 Agent 用于恢复流的 op 批次水位(仅活跃会话)。任务不属于转录,通过 WebSocket 的 `task` 帧到达(见 [事件](#事件))。 - `40001`:校验失败——`before_turn` 与 `after_turn` 同用,或 `agent_id` 不是纯文本形式 - `40401`:会话不存在 @@ -1290,79 +1290,6 @@ schema 还接受共享消息格式中的 `tool_use`、`tool_result` 和 `thinkin - `40405`:没有该 id 的待处理提问 - `40902`:提问已被答复;`data` 携带 `{ resolved: false }` -### 后台任务 - -后台任务是会话的异步单元——后台 Shell、subagent 与长时间运行的工具任务。注册表仅包含实时数据:未加载到本服务进程中的会话会返回空列表。 - -| 方法与路径 | 说明 | -| --- | --- | -| `GET /api/v1/sessions/{session_id}/tasks` | 列出后台任务 | -| `GET /api/v1/sessions/{session_id}/tasks/{task_id}` | 读取任务(可选输出预览) | -| `POST /api/v1/sessions/{session_id}/tasks/{task_id}:cancel` | 取消任务 | -| `POST /api/v1/sessions/{session_id}/tasks/{task_id}:detach` | 将前台任务转入后台 | - -#### `GET /api/v1/sessions/{session_id}/tasks` - -列出会话的后台任务。 - -| 参数 | 位置 | 类型 | 说明 | -| --- | --- | --- | --- | -| `session_id` | path | string | **必填。** 会话 id | -| `status` | query | string | 只保留单一状态:`running` / `completed` / `failed` / `cancelled` | - -成功时,`data` 为 `{ items }`,每个元素是任务对象 `{ id, session_id, kind, description, status, created_at, started_at?, completed_at?, command?, model?, thinking_effort?, agent_id?, subagent_type?, parent_tool_call_id?, output_preview?, output_bytes? }`。`kind` 为 `bash` / `subagent` / `tool`;`command` 仅在 `bash` 任务时设置,模型与 Agent 字段仅在 `subagent` 任务时设置,输出字段仅在以 `with_output` 读取任务时设置。超时与丢失的任务上报为 `failed`;被杀死的任务上报为 `cancelled`。 - -- `40001`:校验失败——未知的 `status` -- `40401`:会话不存在 - -#### `GET /api/v1/sessions/{session_id}/tasks/{task_id}` - -读取单个后台任务,可选携带输出的末尾片段。 - -| 参数 | 位置 | 类型 | 说明 | -| --- | --- | --- | --- | -| `session_id` | path | string | **必填。** 会话 id | -| `task_id` | path | string | **必填。** 任务 id | -| `with_output` | query | boolean | 在响应中包含输出末尾片段。默认 `false` | -| `output_bytes` | query | integer | 请求的输出末尾片段的字节大小,最小 `0`。默认 `32768` | - -成功时,`data` 为上文 `GET /api/v1/sessions/{session_id}/tasks` 中说明的任务对象;当 `with_output=true` 且输出非空时,`output_preview` 携带末尾片段文本,`output_bytes` 为其字节长度。 - -- `40001`:校验失败 -- `40401`:会话不存在 -- `40406`:没有该 id 的任务(冷会话完全没有实时任务) - -#### `POST /api/v1/sessions/{session_id}/tasks/{task_id}:cancel` - -取消运行中的任务。它通过 `POST /api/v1/sessions/{session_id}/tasks/{tail}` 分发,支持 `cancel` / `detach` 两个动作——单独的任务 id 或未知动作返回 `40001`。 - -| 参数 | 位置 | 类型 | 说明 | -| --- | --- | --- | --- | -| `session_id` | path | string | **必填。** 会话 id | -| `task_id` | path | string | **必填。** 任务 id | - -成功时,`data` 为 `{ cancelled: true }`。 - -- `40001`:动作后缀缺失或未知 -- `40401`:会话不存在 -- `40406`:没有该 id 的任务 -- `40904`:任务已结束;`data` 携带 `{ cancelled: false }`,`details.current_status` 为最终状态 - -#### `POST /api/v1/sessions/{session_id}/tasks/{task_id}:detach` - -将运行中的前台任务转入后台而不终止它:等待该任务的工具调用会立即以后台任务结果返回,轮次继续推进,任务则在后台任务注册表下继续运行(输出持久化,完成时以任务通知投递)。已在后台或已结束的任务为幂等空操作。它通过 `POST /api/v1/sessions/{session_id}/tasks/{tail}` 分发,支持 `cancel` / `detach` 两个动作——单独的任务 id 或未知动作返回 `40001`。 - -| 参数 | 位置 | 类型 | 说明 | -| --- | --- | --- | --- | -| `session_id` | path | string | **必填。** 会话 id | -| `task_id` | path | string | **必填。** 任务 id | - -成功时,`data` 为 `{ detached, status }`:本次调用确实将运行中的前台任务转入后台时 `detached` 为 `true`,幂等空操作时为 `false`;`status` 为调用后的任务状态。 - -- `40001`:动作后缀缺失或未知 -- `40401`:会话不存在 -- `40406`:没有该 id 的任务 - ### 技能、工具与 MCP 这组端点暴露会话或工作区可见的技能目录、当前生效 agent 的工具列表及其 MCP 服务。技能激活与 MCP 重启使用 `:{action}` 约定;激活即斜杠命令 `/` 的 REST 等价形式。 @@ -2419,18 +2346,22 @@ locator 寻址的目录(脱敏配置),外加对每个 OAuth 候选的批 | 帧 | payload | 说明 | | --- | --- | --- | -| `subscribe` | `{ session_ids, cursors?, agent_filter? }` | 订阅会话事件;带 `cursors`(每会话 `{seq, epoch}`)时回放错过的持久事件 | -| `unsubscribe` | `{ session_ids }` | 取消会话订阅 | -| `subscribe_v2` | `{ session_id, transcript, transcript_since? }` | 订阅转录流(唯一的转录订阅通道),`transcript` 按 agent 指定粒度 | -| `unsubscribe_v2` | `{ session_id, agent_ids? }` | 退订转录流;省略 `agent_ids` 表示整个会话 | -| `client_hello` | `{ client_id }` | 握手帧,其余字段为遗留兼容 | +| `subscribe_v2` | `{ session_id, transcript, transcript_since? }` | 订阅会话(唯一的会话订阅方式):转录流与交互,`transcript` 按 agent 指定粒度 | +| `unsubscribe_v2` | `{ session_id, agent_ids? }` | 退订转录流;省略 `agent_ids` 或退订最后一个 agent 时取消整个会话订阅 | +| `client_hello` | `{ client_id }` | 握手帧,其余字段会被忽略 | +| `task.watch` | 平铺:`{ request_id, session_id, agent_id, task_id, since_offset?, tail_bytes? }` | 把某个任务的输出流式推送到本连接,无需订阅会话;`agent_id` 是任务所属的 agent(即该任务 `task` 帧上的 `agent_id`),`since_offset` 从客户端已有输出之后续传,`tail_bytes` 是首段输出最多包含的字节数 | +| `task.unwatch` | 平铺:`{ request_id, session_id, agent_id, task_id }` | 停止推送该任务的输出 | +| `task.stop` | 平铺:`{ request_id, session_id, agent_id, task_id }` | 停止任务;已结束的任务直接应答其状态 | +| `task.background` | 平铺:`{ request_id, session_id, agent_id, task_id }` | 让发起任务的工具调用不再等它,任务转入后台继续运行;已在后台或已结束时直接应答其状态 | + +`task.watch`、`task.unwatch`、`task.stop` 与 `task.background` 是平铺帧:字段与 `type` 并列而不放在 `payload` 下,用 `request_id` 代替 `id`,应答为 `{ "type": "response", "request_id", "code", "msg"?, "task"? }` 而不是 ack;缺少可读的 `request_id` 时服务端以关闭码 1008 断开连接。`task.watch`、`task.stop` 与 `task.background` 成功时,`task` 是任务此刻的状态(形状见下文 `task` 帧,不含 `type`、`session_id`、`agent_id` 与 `event_created_at`)。同一个任务的请求按到达顺序逐个处理,因此在 `task.watch` 尚未应答时发出的 `task.unwatch` 会在它之后生效;不同任务之间、以及它们与其他控制帧之间互不等待。`task.watch` 成功时先应答 `code` 0,再用一个 `task.output` 帧发送最后 `tail_bytes` 字节的输出(默认 32 KiB,最大 1 MiB;`since_offset` 落在这段范围内时只发它之后的部分;没有可发送的输出时 `text` 为空),之后推送实时输出;已结束的任务同样可以 watch。错误:`40001` 帧格式错误(含 `tail_bytes` 超出范围),`40401` 会话不存在,`40406` 该 agent 下没有这个任务,`50001` 内部错误,`50003` 输出无法读取。 ### 事件 事件帧形状为 `{ "type", "seq", "epoch"?, "volatile"?, "offset"?, "session_id"?, "timestamp", "payload" }`,`type` 即事件类型。按投递范围分两类: - **全局事件**:发送到每个已建立连接,无需订阅——`session.meta.updated`、`event.session.created`、`event.session.archived`、`event.session.work_changed`、`event.session.status_changed`、`event.workspace.*`、`event.config.*`、`event.model_catalog.*`。 -- **会话事件**:只发给订阅了该会话的连接,受 `agent_filter` 过滤。主要事件族: +- **会话帧**:只发给用 `subscribe_v2` 订阅了该会话的连接——`transcript.reset` / `transcript.ops`(见 [转录协议](#转录协议))、`task.list`、`task` 与 `interaction`。`task.output` 只发给用 `task.watch` 订阅了该任务输出的连接。 | 事件族 | 主要事件 | | --- | --- | @@ -2446,9 +2377,11 @@ locator 寻址的目录(脱敏配置),外加对每个 OAuth 候选的批 事件另分持久与易失两种:持久事件带严格递增的 `seq`,落盘并可回放;易失事件(各 `*.delta`、`tool.progress`、`shell.*` 等)标 `volatile: true`,不回放。消费易失文本流时用 `offset`(该轮次内的累计字符偏移)与本地已累积文本比对:小于本地长度说明是重复帧,大于说明有缺漏、需走快照恢复。 +每个 agent 正在跑的任务——Shell 命令(前台与后台)、subagent(含 swarm 成员)及其他异步工作——由 `task.list` 与 `task` 两种平铺帧同步,和 `interaction` 一样不回放。连接开始接收某个 agent 的任务时(`subscribe_v2` 中粒度非 `off` 且此前未接收的 agent),先收到一帧 `{ type: "task.list", session_id, agent_id, tasks }`,`tasks` 是该 agent 此刻正在跑的全部任务,客户端用它整份替换本地列表;之后每次变化收到一帧 `{ type: "task", session_id, agent_id, event_created_at, ...任务状态 }`,之后才加入会话的 agent 直接从 `task` 帧开始。任务状态为 `{ task_id, kind: "shell" | "subagent" | "other", status: "running" | "ended", outcome?, phase?, detached, description?, child_agent_id?, parent_tool_call_id?, swarm_index?, command?, started_at?, ended_at?, result_summary?, error?, state_reason?, model?, thinking? }`,每帧都携带完整状态。`detached` 表示发起它的工具调用已不再等它,即后台任务;`outcome`(仅已结束的任务)为 `completed` / `failed` / `timed_out` / `killed` / `lost`;`phase`(仅运行中的 subagent)为 `working` / `suspended`,挂起原因在 `state_reason`;`child_agent_id` 是 subagent 任务的 agent id;`swarm_index` 是 swarm 成员在其 swarm 中的序号;`command` 是 Shell 任务的命令。任务结束时推送它最后一帧 `task`(`status: "ended"`),之后不再出现在 `task.list` 里:已结束任务的结果在会话历史中(工具结果与完成通知),需要精确的最终状态时对它发送 `task.watch`,应答带有其状态。服务端重启后失联的后台任务会以 `level: "warning"` 的 `notice` marker 出现在转录里。任务与转录互相独立:`transcript.ops` 里因撤销产生的 `reset` 操作不影响任务。任务输出单独推送,形状为 `{ type: "task.output", session_id, agent_id, task_id, offset, text }`,`offset` 是 `text` 在任务输出中的 UTF-8 字节偏移。 + ### 断线恢复 -重连后在 `subscribe` 的 `cursors` 里带上每个会话最后应用事件的 `{seq, epoch}`,服务端会回放缺口;落后超过缓冲(1000 条)或游标失效时改为收到 `resync_required`。此时调用 `GET /api/v1/sessions/{session_id}/snapshot` 拿全量快照(含 `as_of_seq` 与 `epoch`),再以新游标重新订阅。 +订阅不会跨断线保留。重连后对每个会话重新发送 `subscribe_v2`:带 `transcript_since` 可续传转录流,省略则从新的 `transcript.reset` 开始;确认后会补发待处理的交互,并重新收到各 agent 的 `task.list`。任务输出的订阅同样会丢失:重新发送 `task.watch`,并把 `since_offset` 设为已有输出的末尾。断线期间错过的全局事件不会回放,请通过 REST(例如 `GET /api/v1/sessions`)刷新本地维护的列表。 ### 转录协议 diff --git a/packages/kap-server/src/protocol/rest-task.ts b/packages/kap-server/src/protocol/rest-task.ts deleted file mode 100644 index 45947538302..00000000000 --- a/packages/kap-server/src/protocol/rest-task.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { z } from 'zod'; - -import { taskSchema, taskStatusSchema } from './task'; - -export const listTasksQuerySchema = z.object({ - status: taskStatusSchema.optional(), -}); -export type ListTasksQuery = z.infer; - -export const listTasksResponseSchema = z.object({ - items: z.array(taskSchema), -}); -export type ListTasksResponse = z.infer; - -export const getTaskQuerySchema = z.object({ - with_output: z.coerce.boolean().optional(), - output_bytes: z.coerce.number().int().nonnegative().optional(), -}); -export type GetTaskQuery = z.infer; - -export const getTaskResponseSchema = taskSchema; -export type GetTaskResponse = z.infer; - -export const cancelTaskResultSchema = z.object({ - cancelled: z.literal(true), -}); -export type CancelTaskResult = z.infer; - -export const detachTaskResultSchema = z.object({ - detached: z.boolean(), - status: taskStatusSchema, -}); -export type DetachTaskResult = z.infer; - -export const taskAlreadyFinishedDataSchema = z.object({ - cancelled: z.literal(false), -}); -export type TaskAlreadyFinishedData = z.infer; diff --git a/packages/kap-server/src/protocol/task.ts b/packages/kap-server/src/protocol/task.ts deleted file mode 100644 index 02904390940..00000000000 --- a/packages/kap-server/src/protocol/task.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { z } from 'zod'; - -import { isoDateTimeSchema } from '@moonshot-ai/agent-core-v2/_base/utils/isoDateTime'; - -export const taskKindSchema = z.enum(['subagent', 'bash', 'tool']); -export type TaskKind = z.infer; - -export const taskStatusSchema = z.enum([ - 'running', - 'completed', - 'failed', - 'cancelled', -]); -export type TaskStatus = z.infer; - -export const taskSchema = z.object({ - id: z.string().min(1), - session_id: z.string().min(1), - kind: taskKindSchema, - description: z.string(), - status: taskStatusSchema, - command: z.string().optional(), - created_at: isoDateTimeSchema, - started_at: isoDateTimeSchema.optional(), - completed_at: isoDateTimeSchema.optional(), - output_preview: z.string().optional(), - output_bytes: z.number().int().nonnegative().optional(), - model: z.string().optional(), - thinking_effort: z.string().optional(), - agent_id: z.string().optional(), - subagent_type: z.string().optional(), - parent_tool_call_id: z.string().optional(), - run_in_background: z.boolean(), -}); -export type Task = z.infer; diff --git a/packages/kap-server/src/protocol/ws-control.ts b/packages/kap-server/src/protocol/ws-control.ts index ae64c68be7c..c7a7e530d9e 100644 --- a/packages/kap-server/src/protocol/ws-control.ts +++ b/packages/kap-server/src/protocol/ws-control.ts @@ -5,6 +5,16 @@ import { transcriptGradeSpecSchema, transcriptSeqSchema } from '@moonshot-ai/tra import { pushEventSchema } from './events-zod'; import { interactionEventMessageSchema } from './interaction'; +import { + taskBackgroundMessageSchema, + taskEventMessageSchema, + taskListMessageSchema, + taskOutputMessageSchema, + taskResponseMessageSchema, + taskStopMessageSchema, + taskUnwatchMessageSchema, + taskWatchMessageSchema, +} from './ws-task'; export const WS_PROTOCOL_VERSION = 2; @@ -228,6 +238,40 @@ export const clientControlOperations = [ ackSchema: abortAckMessageSchema, description: 'Abort a running prompt in a session.', }, + { + type: 'task.watch', + direction: 'client_to_server', + kind: 'control', + messageSchema: taskWatchMessageSchema, + ackSchema: taskResponseMessageSchema, + description: + 'Stream the output of one task owned by agent_id to this connection without a session subscription. The first chunk covers at most tail_bytes; since_offset resumes after output the client already has.', + }, + { + type: 'task.unwatch', + direction: 'client_to_server', + kind: 'control', + messageSchema: taskUnwatchMessageSchema, + ackSchema: taskResponseMessageSchema, + description: "Stop streaming one task's output to this connection.", + }, + { + type: 'task.stop', + direction: 'client_to_server', + kind: 'control', + messageSchema: taskStopMessageSchema, + ackSchema: taskResponseMessageSchema, + description: 'Stop one task; the reply carries its state, and an ended task just replies with it.', + }, + { + type: 'task.background', + direction: 'client_to_server', + kind: 'control', + messageSchema: taskBackgroundMessageSchema, + ackSchema: taskResponseMessageSchema, + description: + 'Stop the tool call that started a task from waiting for it, so the task keeps running in the background; the reply carries its state.', + }, { type: 'pong', direction: 'client_to_server', @@ -279,11 +323,41 @@ export const interactionEventOperation = { 'Flat, unsequenced approval or question state for a subscribed session; pending interactions are re-sent after every accepted subscription.', } as const satisfies WsOperationDefinition; +export const taskListEventOperation = { + type: 'task.list', + direction: 'server_to_client', + kind: 'event', + messageSchema: taskListMessageSchema, + description: + 'Every task an agent is running right now, sent when a connection starts receiving that agent; it replaces the client list.', +} as const satisfies WsOperationDefinition; + +export const taskEventOperation = { + type: 'task', + direction: 'server_to_client', + kind: 'event', + messageSchema: taskEventMessageSchema, + description: + 'Full state of one task of a subscribed agent, sent on every change; the frame for an ended task is its last one.', +} as const satisfies WsOperationDefinition; + +export const taskOutputEventOperation = { + type: 'task.output', + direction: 'server_to_client', + kind: 'event', + messageSchema: taskOutputMessageSchema, + description: + 'Task output starting at a UTF-8 byte offset, sent only to connections watching the task.', +} as const satisfies WsOperationDefinition; + export const wsOperations = [ ...clientControlOperations, ...serverSystemOperations, sessionEventOperation, interactionEventOperation, + taskListEventOperation, + taskEventOperation, + taskOutputEventOperation, ] as const satisfies readonly WsOperationDefinition[]; export function getClientControlOperation( diff --git a/packages/kap-server/src/protocol/ws-task.ts b/packages/kap-server/src/protocol/ws-task.ts new file mode 100644 index 00000000000..326baea2a55 --- /dev/null +++ b/packages/kap-server/src/protocol/ws-task.ts @@ -0,0 +1,93 @@ +import { z } from 'zod'; + +import { isoDateTimeSchema } from '@moonshot-ai/agent-core-v2/_base/utils/isoDateTime'; + +export const taskStateSchema = z.object({ + task_id: z.string().min(1), + kind: z.enum(['shell', 'subagent', 'other']), + status: z.enum(['running', 'ended']), + outcome: z.enum(['completed', 'failed', 'timed_out', 'killed', 'lost']).optional(), + phase: z.enum(['working', 'suspended']).optional(), + detached: z.boolean(), + description: z.string().optional(), + child_agent_id: z.string().optional(), + parent_tool_call_id: z.string().optional(), + swarm_index: z.number().int().nonnegative().optional(), + command: z.string().optional(), + started_at: isoDateTimeSchema.optional(), + ended_at: isoDateTimeSchema.optional(), + result_summary: z.string().optional(), + error: z.string().optional(), + state_reason: z.string().optional(), + model: z.string().optional(), + thinking: z.string().optional(), +}); +export type TaskState = z.infer; + +export const taskEventMessageSchema = taskStateSchema.extend({ + type: z.literal('task'), + session_id: z.string().min(1), + agent_id: z.string().min(1), + event_created_at: isoDateTimeSchema, +}); +export type TaskEventMessage = z.infer; + +export const taskListMessageSchema = z.object({ + type: z.literal('task.list'), + session_id: z.string().min(1), + agent_id: z.string().min(1), + tasks: z.array(taskStateSchema), +}); +export type TaskListMessage = z.infer; + +export const TASK_OUTPUT_DEFAULT_TAIL_BYTES = 32 * 1024; +export const TASK_OUTPUT_MAX_TAIL_BYTES = 1024 * 1024; + +const taskTargetFields = { + request_id: z.string().min(1), + session_id: z.string().min(1), + agent_id: z.string().min(1), + task_id: z.string().min(1), +}; + +export const taskWatchMessageSchema = z.object({ + type: z.literal('task.watch'), + ...taskTargetFields, + since_offset: z.number().int().nonnegative().optional(), + tail_bytes: z.number().int().min(1).max(TASK_OUTPUT_MAX_TAIL_BYTES).optional(), +}); +export type TaskWatchMessage = z.infer; + +export const taskUnwatchMessageSchema = z.object({ + type: z.literal('task.unwatch'), + ...taskTargetFields, +}); + +export const taskStopMessageSchema = z.object({ + type: z.literal('task.stop'), + ...taskTargetFields, +}); + +export const taskBackgroundMessageSchema = z.object({ + type: z.literal('task.background'), + ...taskTargetFields, +}); + +export const taskOutputMessageSchema = z.object({ + type: z.literal('task.output'), + session_id: z.string().min(1), + agent_id: z.string().min(1), + task_id: z.string().min(1), + offset: z.number().int().nonnegative(), + text: z.string(), +}); +export type TaskOutputMessage = z.infer; + +export const taskResponseMessageSchema = z.object({ + type: z.literal('response'), + request_id: z.string(), + code: z.number().int(), + msg: z.string().optional(), + task: taskStateSchema.optional(), +}); +export type TaskResponseMessage = z.infer; diff --git a/packages/kap-server/src/routes/registerApiV1Routes.ts b/packages/kap-server/src/routes/registerApiV1Routes.ts index 07145d374c2..21511e3924f 100644 --- a/packages/kap-server/src/routes/registerApiV1Routes.ts +++ b/packages/kap-server/src/routes/registerApiV1Routes.ts @@ -37,7 +37,6 @@ import { registerSessionExportRoute } from './sessionExport'; import { registerSessionsRoutes } from './sessions'; import { registerShutdownRoutes } from './shutdown'; import { registerSkillsRoutes } from './skills'; -import { registerTasksRoutes } from './tasks'; import { registerToolsRoutes } from './tools'; import { registerTranscriptRoutes } from './transcript'; import { registerWorkspaceFsRoutes } from './workspaceFs'; @@ -156,7 +155,6 @@ export async function registerApiV1Routes( core, ); registerSearchRoutes(apiV1 as unknown as Parameters[0], core); - registerTasksRoutes(apiV1 as unknown as Parameters[0], core); registerInteractionsRoutes( apiV1 as unknown as Parameters[0], core, diff --git a/packages/kap-server/src/routes/tasks.ts b/packages/kap-server/src/routes/tasks.ts deleted file mode 100644 index 65c4bc88847..00000000000 --- a/packages/kap-server/src/routes/tasks.ts +++ /dev/null @@ -1,341 +0,0 @@ -import { - IAgentTaskService, - resumeSessionById, - type AgentTaskInfo, - type Scope, -} from '@moonshot-ai/agent-core-v2'; -import { ErrorCode } from '../protocol/error-codes'; -import { - cancelTaskResultSchema, - detachTaskResultSchema, - getTaskQuerySchema, - getTaskResponseSchema, - listTasksQuerySchema, - listTasksResponseSchema, -} from '../protocol/rest-task'; -import type { Task, TaskKind, TaskStatus } from '../protocol/task'; -import { z } from 'zod'; - -import { errEnvelope, okEnvelope } from '../envelope'; -import { requestLog } from '../lib/requestLog'; -import { defineRoute } from '../middleware/defineRoute'; -import { ensureMainAgent } from '../transport/mainAgent'; -import { parseActionSuffix } from './action-suffix'; - -const DEFAULT_TASK_OUTPUT_PREVIEW_BYTES = 32 * 1024; - -interface TasksRouteHost { - get( - path: string, - options: { preHandler: unknown[]; schema?: Record } | undefined, - handler: ( - req: { id: string; query: unknown; params: unknown }, - reply: { send(payload: unknown): unknown }, - ) => Promise | void, - ): unknown; - post( - path: string, - options: { preHandler: unknown[]; schema?: Record }, - handler: ( - req: { id: string; body: unknown; params: unknown }, - reply: { send(payload: unknown): unknown }, - ) => Promise | void, - ): unknown; -} - -const sessionIdParamSchema = z.object({ - session_id: z.string().min(1), -}); - -const sessionAndTaskIdParamSchema = z.object({ - session_id: z.string().min(1), - task_id: z.string().min(1), -}); - -const detailsSchema = z.array(z.object({ path: z.string(), message: z.string() })); - -export function registerTasksRoutes(app: TasksRouteHost, core: Scope): void { - const listRoute = defineRoute( - { - method: 'GET', - path: '/sessions/{session_id}/tasks', - params: sessionIdParamSchema, - querystring: listTasksQuerySchema, - success: { data: listTasksResponseSchema }, - errors: { - [ErrorCode.VALIDATION_FAILED]: { detailsSchema }, - [ErrorCode.SESSION_NOT_FOUND]: {}, - }, - description: 'List tasks for a session', - tags: ['tasks'], - }, - async (req, reply) => { - const { session_id } = req.params; - const resolved = await resolveSessionTasks(core, session_id); - if (resolved.kind === 'not_found') { - reply.send(sessionNotFound(session_id, req.id)); - return; - } - - const all = resolved.tasks.list(false).map((info) => - toWireTask(session_id, info), - ); - const query = req.query as { status?: TaskStatus }; - const items = - query.status !== undefined ? all.filter((t) => t.status === query.status) : all; - reply.send(okEnvelope({ items }, req.id)); - }, - ); - app.get(listRoute.path, listRoute.options, listRoute.handler as Parameters[2]); - - const getRoute = defineRoute( - { - method: 'GET', - path: '/sessions/{session_id}/tasks/{task_id}', - params: sessionAndTaskIdParamSchema, - querystring: getTaskQuerySchema, - success: { data: getTaskResponseSchema }, - errors: { - [ErrorCode.VALIDATION_FAILED]: { detailsSchema }, - [ErrorCode.SESSION_NOT_FOUND]: {}, - [ErrorCode.TASK_NOT_FOUND]: {}, - }, - description: 'Get a task by ID', - tags: ['tasks'], - }, - async (req, reply) => { - const { session_id, task_id } = req.params; - const resolved = await resolveSessionTasks(core, session_id); - if (resolved.kind === 'not_found') { - reply.send(sessionNotFound(session_id, req.id)); - return; - } - - const found = resolved.tasks.getTask(task_id); - if (found === undefined) { - reply.send(taskNotFound(session_id, task_id, req.id)); - return; - } - - const query = req.query as { with_output?: boolean; output_bytes?: number }; - let output: { preview: string; bytes: number } | undefined; - if (query.with_output === true) { - const tailBytes = query.output_bytes ?? DEFAULT_TASK_OUTPUT_PREVIEW_BYTES; - try { - const preview = await resolved.tasks.readOutput(task_id, tailBytes); - if (preview.length > 0) { - output = { preview, bytes: Buffer.byteLength(preview, 'utf-8') }; - } - } catch { - } - } - - reply.send(okEnvelope(toWireTask(session_id, found, output), req.id)); - }, - ); - app.get(getRoute.path, getRoute.options, getRoute.handler as Parameters[2]); - - const taskActionRoute = defineRoute( - { - method: 'POST', - path: '/sessions/{session_id}/tasks/{tail}', - success: { data: z.union([cancelTaskResultSchema, detachTaskResultSchema]) }, - errors: { - [ErrorCode.VALIDATION_FAILED]: { detailsSchema }, - [ErrorCode.SESSION_NOT_FOUND]: {}, - [ErrorCode.TASK_NOT_FOUND]: {}, - [ErrorCode.TASK_ALREADY_FINISHED]: { - dataSchema: z.object({ cancelled: z.literal(false) }), - detailsSchema: z.object({ current_status: z.string() }), - }, - }, - description: 'Run a task action', - tags: ['tasks'], - operationId: 'runTaskAction', - }, - async (req, reply) => { - const { session_id, tail } = req.params as { - session_id: string; - tail: string; - }; - const parsed = parseActionSuffix({ - tail, - allowedActions: ['cancel', 'detach'] as const, - resourceLabel: 'task', - }); - if (parsed.kind === 'invalid') { - reply.send(errEnvelope(ErrorCode.VALIDATION_FAILED, parsed.reason, req.id)); - return; - } - if (parsed.kind === 'bare') { - reply.send( - errEnvelope(ErrorCode.VALIDATION_FAILED, `unsupported action: ${tail}`, req.id), - ); - return; - } - const task_id = parsed.id; - if (!session_id || !task_id) { - reply.send(errEnvelope(ErrorCode.VALIDATION_FAILED, 'invalid path params', req.id)); - return; - } - - const resolved = await resolveSessionTasks(core, session_id); - if (resolved.kind === 'not_found') { - reply.send(sessionNotFound(session_id, req.id)); - return; - } - - const found = resolved.tasks.getTask(task_id); - if (found === undefined) { - reply.send(taskNotFound(session_id, task_id, req.id)); - return; - } - - if (parsed.action === 'cancel') { - const wireStatus = toWireTask(session_id, found).status; - if (isTerminalStatus(wireStatus)) { - reply.send(taskAlreadyFinished(session_id, task_id, wireStatus, req.id)); - return; - } - - await resolved.tasks.stopByUser(task_id); - requestLog(req)?.info({ session_id, task_id }, 'task cancelled'); - reply.send(okEnvelope({ cancelled: true as const }, req.id)); - return; - } - - const detached = found.status === 'running' && found.detached === false; - const info = resolved.tasks.detach(task_id) ?? found; - if (detached) { - requestLog(req)?.info({ session_id, task_id }, 'task detached'); - } - reply.send(okEnvelope({ detached, status: mapStatus(info.status) }, req.id)); - }, - ); - app.post(taskActionRoute.path, taskActionRoute.options, taskActionRoute.handler as Parameters[2]); -} - -type ResolvedTasks = - | { readonly kind: 'not_found' } - | { readonly kind: 'resolved'; readonly tasks: IAgentTaskService }; - -async function resolveSessionTasks(core: Scope, sid: string): Promise { - const session = await resumeSessionById(core.accessor, sid); - if (session === undefined) return { kind: 'not_found' }; - const agent = await ensureMainAgent(session); - const tasks = agent.accessor.get(IAgentTaskService); - return { kind: 'resolved', tasks }; -} - -function mapKind(k: AgentTaskInfo['kind']): TaskKind { - switch (k) { - case 'process': - return 'bash'; - case 'agent': - return 'subagent'; - case 'question': - return 'tool'; - } -} - -function mapStatus(s: AgentTaskInfo['status']): TaskStatus { - switch (s) { - case 'running': - return 'running'; - case 'completed': - return 'completed'; - case 'failed': - return 'failed'; - case 'timed_out': - return 'failed'; - case 'killed': - return 'cancelled'; - case 'lost': - return 'failed'; - } -} - -const TERMINAL_WIRE_STATUSES: ReadonlySet = new Set([ - 'completed', - 'failed', - 'cancelled', -]); - -function isTerminalStatus(status: TaskStatus): boolean { - return TERMINAL_WIRE_STATUSES.has(status); -} - -function toWireTask( - sessionId: string, - info: AgentTaskInfo, - output?: { preview: string; bytes: number }, -): Task { - const status = mapStatus(info.status); - const createdIso = new Date(info.startedAt).toISOString(); - const base: Task = { - id: info.taskId, - session_id: sessionId, - kind: mapKind(info.kind), - description: info.description, - status, - created_at: createdIso, - started_at: createdIso, - run_in_background: info.detached ?? true, - }; - if (info.endedAt !== null && info.endedAt !== undefined) { - base.completed_at = new Date(info.endedAt).toISOString(); - } - if (info.kind === 'process' && 'command' in info && typeof info.command === 'string') { - base.command = info.command; - } - if (info.kind === 'agent' && info.model !== undefined) { - base.model = info.model; - } - if (info.kind === 'agent' && info.thinkingEffort !== undefined) { - base.thinking_effort = info.thinkingEffort; - } - if (info.kind === 'agent' && info.agentId !== undefined) { - base.agent_id = info.agentId; - } - if (info.kind === 'agent' && info.subagentType !== undefined) { - base.subagent_type = info.subagentType; - } - if ( - (info.kind === 'agent' || info.kind === 'process') && - info.parentToolCallId !== undefined - ) { - base.parent_tool_call_id = info.parentToolCallId; - } - if (output !== undefined) { - base.output_preview = output.preview; - base.output_bytes = output.bytes; - } - return base; -} - -function sessionNotFound(sid: string, requestId: string): unknown { - return errEnvelope(ErrorCode.SESSION_NOT_FOUND, `session ${sid} does not exist`, requestId); -} - -function taskNotFound(sid: string, tid: string, requestId: string): unknown { - return errEnvelope( - ErrorCode.TASK_NOT_FOUND, - `task ${tid} does not exist in session ${sid}`, - requestId, - ); -} - -function taskAlreadyFinished( - sid: string, - tid: string, - currentStatus: TaskStatus, - requestId: string, -): unknown { - return { - code: ErrorCode.TASK_ALREADY_FINISHED, - msg: `task ${tid} already finished (status: ${currentStatus})`, - data: { cancelled: false }, - request_id: requestId, - details: { current_status: currentStatus }, - }; -} diff --git a/packages/kap-server/src/transport/ws/v1/protocol.ts b/packages/kap-server/src/transport/ws/v1/protocol.ts index aead37d9964..6d350834b71 100644 --- a/packages/kap-server/src/transport/ws/v1/protocol.ts +++ b/packages/kap-server/src/transport/ws/v1/protocol.ts @@ -1,3 +1,5 @@ +import type { TaskResponseMessage } from '../../../protocol/ws-task'; + export interface ServerHelloPayload { ws_connection_id: string; protocol_version: number; @@ -39,3 +41,12 @@ export interface AckFrame

{ export function buildAck

(id: string, code: number, msg: string, payload: P): AckFrame

{ return { type: 'ack', id, code, msg, payload }; } + +export function buildResponse( + requestId: string, + code: number, + msg?: string, + task?: TaskResponseMessage['task'], +): TaskResponseMessage { + return { type: 'response', request_id: requestId, code, msg, task }; +} diff --git a/packages/kap-server/src/transport/ws/v1/registerWsV1.ts b/packages/kap-server/src/transport/ws/v1/registerWsV1.ts index a1cba587dc0..a140f643ccc 100644 --- a/packages/kap-server/src/transport/ws/v1/registerWsV1.ts +++ b/packages/kap-server/src/transport/ws/v1/registerWsV1.ts @@ -4,6 +4,7 @@ import { WebSocketServer } from 'ws'; import type { CredentialValidator } from '../../../services/auth/credentials'; import { type IConnectionRegistry } from '../connectionRegistry'; import type { SessionEventBroadcaster } from './sessionEventBroadcaster'; +import { createTaskSource } from './taskRequests'; import { WsConnectionV1 } from './wsConnectionV1'; import { selectWsBearerProtocol } from '../bearerProtocol'; @@ -20,14 +21,15 @@ export interface RegisterWsV1Options { } export function registerWsV1(core: Scope, opts: RegisterWsV1Options): WebSocketServer { - void core; const wss = new WebSocketServer({ noServer: true, handleProtocols: selectWsBearerProtocol }); const { registry, broadcaster } = opts; + const taskSource = createTaskSource(core); wss.on('connection', (socket, req) => { const conn = new WsConnectionV1({ socket, broadcaster, + taskSource, connectionRegistry: registry, validateCredential: opts.validateCredential, remoteAddress: req.socket.remoteAddress ?? null, diff --git a/packages/kap-server/src/transport/ws/v1/sessionEventBroadcaster.ts b/packages/kap-server/src/transport/ws/v1/sessionEventBroadcaster.ts index fae0d64e0d7..b0629d0eba4 100644 --- a/packages/kap-server/src/transport/ws/v1/sessionEventBroadcaster.ts +++ b/packages/kap-server/src/transport/ws/v1/sessionEventBroadcaster.ts @@ -11,7 +11,9 @@ import type { SessionActivityState, Workspace, } from '@moonshot-ai/agent-core-v2'; +import type { AgentTaskInfo } from '@moonshot-ai/agent-core-v2/agent/task/types'; import { + IAgentLifecycleService, IEventService, INTERACTION_TAG_SESSION_ID, INTERACTION_TAG_TOOL_CALL_ID, @@ -58,8 +60,11 @@ import { interactionOutcome, } from '../../../protocol/interaction-wire'; import { toWireQuestion } from '../../../protocol/question-wire'; +import type { TaskEventMessage, TaskListMessage } from '../../../protocol/ws-task'; import { toWireWorkspace } from '../../../routes/workspaces'; import type { TranscriptService } from '../../../services/transcript/transcriptService'; +import { SessionTaskFeed } from './sessionTaskFeed'; +import { toTaskMessage } from './taskState'; export interface EventEnvelope { readonly type: string; @@ -75,7 +80,11 @@ export interface BroadcastLogger { export type BroadcastDelivery = 'subscription' | 'immediate'; -export type BroadcastFrame = EventEnvelope | InteractionEventMessage; +export type BroadcastFrame = + | EventEnvelope + | InteractionEventMessage + | TaskEventMessage + | TaskListMessage; export interface BroadcastTarget { send(frame: BroadcastFrame, delivery?: BroadcastDelivery): void; @@ -94,6 +103,8 @@ interface SessionState { readonly knownInteractions: Map; transcriptStream?: TranscriptStream; readonly transcriptSeeded: Set; + tasks?: SessionTaskFeed; + readonly listedTaskAgents: Map>; } interface WorkTracker { @@ -195,6 +206,7 @@ export class SessionEventBroadcaster { if (state === undefined || this.sessions.get(sessionId) !== state) return false; const prev = state.targets.get(target); state.targets.set(target, transcriptGrades); + this.sendTaskLists(state, target, transcriptGrades); if (this.willSendTranscriptReset(state, transcriptGrades, prev)) { state.transcriptSeeded.delete(target); } @@ -208,6 +220,7 @@ export class SessionEventBroadcaster { if (state === undefined) return; state.targets.delete(target); state.transcriptSeeded.delete(target); + state.listedTaskAgents.delete(target); this.evictDetachedSession(sessionId); } @@ -219,6 +232,7 @@ export class SessionEventBroadcaster { const state = this.sessions.get(sessionId); if (state === undefined || !state.targets.has(target)) return; state.targets.set(target, transcriptGrades); + this.sendTaskLists(state, target, transcriptGrades); } private willSendTranscriptReset( @@ -423,6 +437,9 @@ export class SessionEventBroadcaster { if (getLiveSessionById(this.opts.core.accessor, sessionId) === undefined) return; state.detached = undefined; this.attachInteractions(sessionId, state); + this.attachTasks(sessionId, state); + state.listedTaskAgents.clear(); + for (const [target, spec] of state.targets) this.sendTaskLists(state, target, spec); for (const [target, spec] of state.targets) { await this.subscribeTranscript(state, target, spec, undefined); if (state.targets.has(target)) state.transcriptSeeded.add(target); @@ -444,12 +461,63 @@ export class SessionEventBroadcaster { lifecycleDisposables: [], knownInteractions: new Map(), transcriptSeeded: new Set(), + listedTaskAgents: new Map(), }; this.sessions.set(sessionId, state); this.attachInteractions(sessionId, state); + this.attachTasks(sessionId, state); return state; } + private attachTasks(sessionId: string, state: SessionState): void { + const agents = getLiveSessionById(this.opts.core.accessor, sessionId)?.accessor.get( + IAgentLifecycleService, + ); + if (agents === undefined) return; + const feed = new SessionTaskFeed(agents, (agentId, info) => this.sendTask(state, agentId, info)); + state.tasks = feed; + state.lifecycleDisposables.push(feed); + } + + private sendTaskLists(state: SessionState, target: BroadcastTarget, spec: TranscriptGradeSpec): void { + const feed = state.tasks; + if (feed === undefined) return; + let listed = state.listedTaskAgents.get(target); + if (listed === undefined) { + listed = new Set(); + state.listedTaskAgents.set(target, listed); + } + const agentIds = new Set([...feed.agentIds(), ...Object.keys(spec).filter((id) => id !== '*')]); + for (const agentId of agentIds) { + if (gradeFor(spec, agentId) === 'off') { + listed.delete(agentId); + continue; + } + if (listed.has(agentId)) continue; + listed.add(agentId); + try { + target.send({ + type: 'task.list', + session_id: state.sessionId, + agent_id: agentId, + tasks: feed.running(agentId), + }); + } catch { + } + } + } + + private sendTask(state: SessionState, agentId: string, info: AgentTaskInfo): void { + const message = toTaskMessage(state.sessionId, agentId, info); + for (const [target, spec] of state.targets) { + if (gradeFor(spec, agentId) === 'off') continue; + try { + target.send(message); + } catch { + } + } + } + private trackWork(sessionId: string, session: ISessionScopeHandle): void { if (this.closed) return; this.untrackWork(sessionId); diff --git a/packages/kap-server/src/transport/ws/v1/sessionTaskFeed.ts b/packages/kap-server/src/transport/ws/v1/sessionTaskFeed.ts new file mode 100644 index 00000000000..849d77e002d --- /dev/null +++ b/packages/kap-server/src/transport/ws/v1/sessionTaskFeed.ts @@ -0,0 +1,51 @@ +import { IAgentLifecycleService, IAgentTaskService, type IDisposable } from '@moonshot-ai/agent-core-v2'; +import type { AgentTaskInfo } from '@moonshot-ai/agent-core-v2/agent/task/types'; + +import type { TaskState } from '../../../protocol/ws-task'; +import { toTaskState } from './taskState'; + +export class SessionTaskFeed implements IDisposable { + private readonly agentWatches = new Map(); + private readonly lifecycleWatches: readonly IDisposable[]; + + constructor( + private readonly agents: IAgentLifecycleService, + private readonly onChange: (agentId: string, info: AgentTaskInfo) => void, + ) { + for (const agent of agents.list()) this.watch(agent.agentId); + this.lifecycleWatches = [ + agents.onDidCreate((agent) => this.watch(agent.agentId)), + agents.onDidClose((agent) => this.unwatch(agent.agentId)), + ]; + } + + agentIds(): readonly string[] { + return this.agents.list().map((agent) => agent.agentId); + } + + running(agentId: string): TaskState[] { + const tasks = this.agents.handleOf(agentId)?.accessor.get(IAgentTaskService); + return (tasks?.list(true) ?? []).map(toTaskState); + } + + dispose(): void { + for (const watch of this.lifecycleWatches) watch.dispose(); + for (const watch of this.agentWatches.values()) watch.dispose(); + this.agentWatches.clear(); + } + + private watch(agentId: string): void { + if (this.agentWatches.has(agentId)) return; + const tasks = this.agents.handleOf(agentId)?.accessor.get(IAgentTaskService); + if (tasks === undefined) return; + this.agentWatches.set( + agentId, + tasks.onDidChangeTask((info) => this.onChange(agentId, info)), + ); + } + + private unwatch(agentId: string): void { + this.agentWatches.get(agentId)?.dispose(); + this.agentWatches.delete(agentId); + } +} diff --git a/packages/kap-server/src/transport/ws/v1/taskRequests.ts b/packages/kap-server/src/transport/ws/v1/taskRequests.ts new file mode 100644 index 00000000000..a04e51f3184 --- /dev/null +++ b/packages/kap-server/src/transport/ws/v1/taskRequests.ts @@ -0,0 +1,276 @@ +import { + getOrRestoreAgent, + IAgentTaskService, + resumeSessionById, + type AgentTaskOutputChunk, + type AgentTaskOutputTail, + type IDisposable, + type Scope, +} from '@moonshot-ai/agent-core-v2'; +import type { AgentTaskInfo } from '@moonshot-ai/agent-core-v2/agent/task/types'; + +import { ErrorCode } from '../../../protocol/error-codes'; +import { + TASK_OUTPUT_DEFAULT_TAIL_BYTES, + taskBackgroundMessageSchema, + taskStopMessageSchema, + taskUnwatchMessageSchema, + taskWatchMessageSchema, + type TaskOutputMessage, + type TaskState, + type TaskWatchMessage, +} from '../../../protocol/ws-task'; +import { ensureMainAgent, MAIN_AGENT_ID } from '../../mainAgent'; +import { toTaskState } from './taskState'; + +export type TaskSourceResult = + | { readonly kind: 'session_not_found' } + | { readonly kind: 'task_not_found' } + | { readonly kind: 'found'; readonly tasks: IAgentTaskService }; + +export type TaskSource = ( + sessionId: string, + agentId: string, + taskId: string, +) => Promise; + +export function createTaskSource(core: Scope): TaskSource { + return async (sessionId, agentId, taskId) => { + const session = await resumeSessionById(core.accessor, sessionId); + if (session === undefined) return { kind: 'session_not_found' }; + const agent = + agentId === MAIN_AGENT_ID + ? await ensureMainAgent(session) + : await getOrRestoreAgent(session, agentId); + const tasks = agent?.accessor.get(IAgentTaskService); + if (tasks?.getTask(taskId) === undefined) return { kind: 'task_not_found' }; + return { kind: 'found', tasks }; + }; +} + +export interface TaskRequestSink { + respond(requestId: string, code: number, msg?: string, task?: TaskState): void; + output(frame: TaskOutputMessage): void; +} + +type TaskAddress = Pick; + +export class TaskRequests { + private readonly watches = new Map(); + private readonly queues = new Map>(); + private disposed = false; + + constructor( + private readonly source: TaskSource, + private readonly sink: TaskRequestSink, + ) {} + + handle(requestId: string, frame: unknown): void { + const watch = taskWatchMessageSchema.safeParse(frame); + if (watch.success) { + this.enqueue(watchKey(watch.data), () => this.watch(watch.data)); + return; + } + const unwatch = taskUnwatchMessageSchema.safeParse(frame); + if (unwatch.success) { + const key = watchKey(unwatch.data); + this.enqueue(key, async () => { + this.unwatch(key); + this.sink.respond(requestId, ErrorCode.SUCCESS); + }); + return; + } + const stop = taskStopMessageSchema.safeParse(frame); + if (stop.success) { + this.enqueue(watchKey(stop.data), () => + this.command(requestId, stop.data, async (tasks, taskId) => tasks.stopByUser(taskId)), + ); + return; + } + const background = taskBackgroundMessageSchema.safeParse(frame); + if (background.success) { + this.enqueue(watchKey(background.data), () => + this.command(requestId, background.data, async (tasks, taskId) => tasks.detach(taskId)), + ); + return; + } + this.sink.respond(requestId, ErrorCode.VALIDATION_FAILED, 'invalid task message'); + } + + dispose(): void { + this.disposed = true; + for (const watch of this.watches.values()) watch.dispose(); + this.watches.clear(); + } + + private enqueue(key: string, run: () => Promise): void { + const next = (this.queues.get(key) ?? Promise.resolve()).then(run).catch(() => undefined); + this.queues.set(key, next); + void next.then(() => { + if (this.queues.get(key) === next) this.queues.delete(key); + }); + } + + private async watch(request: TaskWatchMessage): Promise { + if (this.disposed) return; + const { request_id: requestId, session_id: sessionId, agent_id: agentId, task_id: taskId } = request; + const key = watchKey(request); + this.unwatch(key); + const fail = (code: number, msg: string): void => { + this.unwatch(key); + this.sink.respond(requestId, code, msg); + }; + + let resolved: TaskSourceResult; + try { + resolved = await this.source(sessionId, agentId, taskId); + } catch { + fail(ErrorCode.INTERNAL_ERROR, 'internal error'); + return; + } + if (resolved.kind === 'session_not_found') { + fail(ErrorCode.SESSION_NOT_FOUND, 'session not found'); + return; + } + if (resolved.kind === 'task_not_found') { + fail(ErrorCode.TASK_NOT_FOUND, 'task not found'); + return; + } + if (this.disposed) return; + + try { + const pending: AgentTaskOutputChunk[] = []; + let live = false; + const forward = (chunk: { readonly offset: number; readonly text: string }): void => { + this.sink.output({ + type: 'task.output', + session_id: sessionId, + agent_id: agentId, + task_id: taskId, + offset: chunk.offset, + text: chunk.text, + }); + }; + this.watches.set( + key, + resolved.tasks.onDidAppendOutput((chunk) => { + if (chunk.taskId !== taskId) return; + if (live) forward(chunk); + else pending.push(chunk); + }), + ); + + let tail: AgentTaskOutputTail; + try { + tail = await resolved.tasks.readOutputTail( + taskId, + request.tail_bytes ?? TASK_OUTPUT_DEFAULT_TAIL_BYTES, + ); + } catch { + fail(ErrorCode.PERSISTENCE_FAILURE, 'task output unavailable'); + return; + } + if (this.disposed) return; + + const first = firstChunk(tail.offset, tail.data, request.since_offset); + const info = resolved.tasks.getTask(taskId); + this.sink.respond(requestId, ErrorCode.SUCCESS, undefined, info === undefined ? undefined : toTaskState(info)); + forward(first); + for (const chunk of pending) { + const rest = chunkAfter(chunk, first.end); + if (rest !== undefined) forward(rest); + } + live = true; + } catch { + fail(ErrorCode.INTERNAL_ERROR, 'internal error'); + } + } + + private async command( + requestId: string, + request: TaskAddress, + run: (tasks: IAgentTaskService, taskId: string) => Promise, + ): Promise { + if (this.disposed) return; + try { + const resolved = await this.source(request.session_id, request.agent_id, request.task_id); + if (resolved.kind === 'session_not_found') { + this.sink.respond(requestId, ErrorCode.SESSION_NOT_FOUND, 'session not found'); + return; + } + if (resolved.kind === 'task_not_found') { + this.sink.respond(requestId, ErrorCode.TASK_NOT_FOUND, 'task not found'); + return; + } + const info = (await run(resolved.tasks, request.task_id)) ?? resolved.tasks.getTask(request.task_id); + this.sink.respond(requestId, ErrorCode.SUCCESS, undefined, info === undefined ? undefined : toTaskState(info)); + } catch { + this.sink.respond(requestId, ErrorCode.INTERNAL_ERROR, 'internal error'); + } + } + + private unwatch(key: string): void { + this.watches.get(key)?.dispose(); + this.watches.delete(key); + } +} + +function firstChunk( + tailOffset: number, + data: Uint8Array, + sinceOffset: number | undefined, +): { readonly offset: number; readonly text: string; readonly end: number } { + const stop = completeUtf8Length(data); + let start = 0; + if ( + sinceOffset !== undefined && + sinceOffset >= tailOffset && + sinceOffset <= tailOffset + stop && + isCharBoundary(data, sinceOffset - tailOffset) + ) { + start = sinceOffset - tailOffset; + } else { + while (start < stop && isUtf8Continuation(data[start]!)) start += 1; + } + return { + offset: tailOffset + start, + text: Buffer.from(data.buffer, data.byteOffset + start, stop - start).toString('utf-8'), + end: tailOffset + stop, + }; +} + +function chunkAfter( + chunk: AgentTaskOutputChunk, + end: number, +): { readonly offset: number; readonly text: string } | undefined { + if (chunk.offset >= end) return chunk; + const bytes = Buffer.from(chunk.text, 'utf-8'); + if (chunk.offset + bytes.byteLength <= end) return undefined; + return { offset: end, text: bytes.subarray(end - chunk.offset).toString('utf-8') }; +} + +function completeUtf8Length(data: Uint8Array): number { + let lead = data.byteLength - 1; + while (lead >= 0 && data.byteLength - lead < 4 && isUtf8Continuation(data[lead]!)) lead -= 1; + if (lead < 0) return data.byteLength; + return lead + utf8SequenceLength(data[lead]!) <= data.byteLength ? data.byteLength : lead; +} + +function utf8SequenceLength(leadByte: number): number { + if (leadByte >= 0xf0) return 4; + if (leadByte >= 0xe0) return 3; + if (leadByte >= 0xc0) return 2; + return 1; +} + +function isCharBoundary(data: Uint8Array, index: number): boolean { + return index >= data.byteLength || !isUtf8Continuation(data[index]!); +} + +function isUtf8Continuation(byte: number): boolean { + return (byte & 0xc0) === 0x80; +} + +function watchKey(address: TaskAddress): string { + return `${address.session_id}\u0000${address.agent_id}\u0000${address.task_id}`; +} diff --git a/packages/kap-server/src/transport/ws/v1/taskState.ts b/packages/kap-server/src/transport/ws/v1/taskState.ts new file mode 100644 index 00000000000..796502a9a64 --- /dev/null +++ b/packages/kap-server/src/transport/ws/v1/taskState.ts @@ -0,0 +1,46 @@ +import { TERMINAL_STATUSES, type AgentTaskInfo } from '@moonshot-ai/agent-core-v2/agent/task/types'; + +import type { TaskEventMessage, TaskState } from '../../../protocol/ws-task'; + +type TaskOutcome = NonNullable; + +export function toTaskState(info: AgentTaskInfo): TaskState { + const ended = TERMINAL_STATUSES.has(info.status); + const subagent = info.kind === 'agent' ? info : undefined; + const shell = info.kind === 'process' ? info : undefined; + const stateReason = ended ? undefined : subagent?.stateReason; + return { + task_id: info.taskId, + kind: shell !== undefined ? 'shell' : subagent !== undefined ? 'subagent' : 'other', + status: ended ? 'ended' : 'running', + outcome: ended ? (info.status as TaskOutcome) : undefined, + phase: subagent === undefined || ended ? undefined : stateReason === undefined ? 'working' : 'suspended', + detached: info.detached !== false, + description: text(info.description), + child_agent_id: text(subagent?.agentId), + parent_tool_call_id: text(subagent?.parentToolCallId ?? shell?.parentToolCallId), + swarm_index: subagent?.swarmIndex, + command: text(shell?.command), + started_at: new Date(info.startedAt).toISOString(), + ended_at: info.endedAt === null ? undefined : new Date(info.endedAt).toISOString(), + result_summary: text(subagent?.resultSummary), + error: info.status === 'failed' ? text(info.stopReason) : undefined, + state_reason: text(stateReason), + model: text(subagent?.model), + thinking: text(subagent?.thinkingEffort), + }; +} + +export function toTaskMessage(sessionId: string, agentId: string, info: AgentTaskInfo): TaskEventMessage { + return { + type: 'task', + session_id: sessionId, + agent_id: agentId, + event_created_at: new Date().toISOString(), + ...toTaskState(info), + }; +} + +function text(value: string | undefined): string | undefined { + return value === undefined || value.length === 0 ? undefined : value; +} diff --git a/packages/kap-server/src/transport/ws/v1/wsConnectionV1.ts b/packages/kap-server/src/transport/ws/v1/wsConnectionV1.ts index 272db03cafa..64dc24e502b 100644 --- a/packages/kap-server/src/transport/ws/v1/wsConnectionV1.ts +++ b/packages/kap-server/src/transport/ws/v1/wsConnectionV1.ts @@ -9,14 +9,21 @@ import type { RawData, WebSocket } from 'ws'; import type { CredentialValidator } from '../../../services/auth/credentials'; import type { IConnectionRegistry } from '../connectionRegistry'; -import { buildAck, buildPing, buildServerHello } from './protocol'; +import { buildAck, buildPing, buildResponse, buildServerHello } from './protocol'; import type { BroadcastDelivery, BroadcastFrame, BroadcastTarget, SessionEventBroadcaster, } from './sessionEventBroadcaster'; - +import { TaskRequests, type TaskSource } from './taskRequests'; + +const TASK_REQUEST_TYPES: ReadonlySet = new Set([ + 'task.watch', + 'task.unwatch', + 'task.stop', + 'task.background', +]); const DEFAULT_HEARTBEAT_INTERVAL_MS = 10_000; const HEARTBEAT_MISS_LIMIT = 2; @@ -29,12 +36,14 @@ const DEFAULT_BACKPRESSURE_MAX_DELAY_MS = 100; interface InboundFrame { type: string; id?: string; + request_id?: unknown; payload?: Record; } export interface WsConnectionV1Options { readonly socket: WebSocket; readonly broadcaster: SessionEventBroadcaster; + readonly taskSource: TaskSource; readonly connectionRegistry: IConnectionRegistry; readonly validateCredential?: CredentialValidator; readonly remoteAddress: string | null; @@ -58,6 +67,7 @@ export class WsConnectionV1 implements BroadcastTarget { private readonly maxBatchSize: number; private readonly highWaterMarkBytes: number; private readonly heartbeatIntervalMs: number; + private readonly taskRequests: TaskRequests; private closed = false; private gotClientHello = false; @@ -84,6 +94,14 @@ export class WsConnectionV1 implements BroadcastTarget { this.maxBatchSize = opts.maxBatchSize ?? DEFAULT_MAX_BATCH_SIZE; this.highWaterMarkBytes = opts.highWaterMarkBytes ?? DEFAULT_HIGH_WATER_MARK_BYTES; this.heartbeatIntervalMs = opts.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS; + this.taskRequests = new TaskRequests(opts.taskSource, { + respond: (requestId, code, msg, task) => { + this.sendImmediateFrame(buildResponse(requestId, code, msg, task)); + }, + output: (frame) => { + this.sendSubscribedFrame(frame); + }, + }); this.socket.on('message', (data: RawData) => this.onMessage(data)); this.socket.on('close', () => this.onClose()); @@ -129,6 +147,16 @@ export class WsConnectionV1 implements BroadcastTarget { if (typeof frame?.type !== 'string') return; this.lastInboundAt = Date.now(); + if (TASK_REQUEST_TYPES.has(frame.type)) { + const requestId = frame.request_id; + if (typeof requestId !== 'string' || requestId.length === 0) { + this.close(1008, 'invalid message'); + return; + } + this.taskRequests.handle(requestId, frame); + return; + } + switch (frame.type) { case 'pong': return; @@ -312,6 +340,7 @@ export class WsConnectionV1 implements BroadcastTarget { if (this.backpressureRetryTimer !== undefined) clearTimeout(this.backpressureRetryTimer); if (this.heartbeatTimer !== undefined) clearInterval(this.heartbeatTimer); this.outbound = []; + this.taskRequests.dispose(); this.broadcaster.removeGlobalTarget(this); for (const sid of this.subscriptions.keys()) this.broadcaster.unsubscribe(sid, this); } diff --git a/packages/kap-server/test/__snapshots__/apiSurface.snapshot.test.ts.snap b/packages/kap-server/test/__snapshots__/apiSurface.snapshot.test.ts.snap index adc7e74bcbf..f004f8ffe0c 100644 --- a/packages/kap-server/test/__snapshots__/apiSurface.snapshot.test.ts.snap +++ b/packages/kap-server/test/__snapshots__/apiSurface.snapshot.test.ts.snap @@ -248,14 +248,6 @@ exports[`API surface snapshot > matches the documented v2 route table and meta e "GET", "/api/v1/sessions/{session_id}/status", ], - [ - "GET", - "/api/v1/sessions/{session_id}/tasks", - ], - [ - "GET", - "/api/v1/sessions/{session_id}/tasks/{task_id}", - ], [ "GET", "/api/v1/sessions/{session_id}/transcript", @@ -484,10 +476,6 @@ exports[`API surface snapshot > matches the documented v2 route table and meta e "POST", "/api/v1/sessions/{session_id}/skills/{tail}", ], - [ - "POST", - "/api/v1/sessions/{session_id}/tasks/{tail}", - ], [ "POST", "/api/v1/sessions/{session_id}/title/generate", diff --git a/packages/kap-server/test/sessionEventBroadcaster.test.ts b/packages/kap-server/test/sessionEventBroadcaster.test.ts index 959e65bb671..0f595c1826f 100644 --- a/packages/kap-server/test/sessionEventBroadcaster.test.ts +++ b/packages/kap-server/test/sessionEventBroadcaster.test.ts @@ -18,6 +18,7 @@ import { IAgentLoopService, IAgentProfileService, IAgentScopeContext, + IAgentTaskService, IEventBus, IEventService, IModelCatalog, @@ -40,6 +41,7 @@ import { EventBusService, } from '@moonshot-ai/agent-core-v2/app/event/eventBusService'; import type { AgentActivitySnapshot } from '@moonshot-ai/agent-core-v2/agent/loop/loop'; +import type { AgentTaskInfo } from '@moonshot-ai/agent-core-v2/agent/task/types'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { @@ -160,9 +162,29 @@ class FakeManagerEvents { } +class FakeAgentTasks { + private readonly listeners = new Set<(info: AgentTaskInfo) => void>(); + private readonly tasks = new Map(); + + readonly onDidChangeTask = (listener: (info: AgentTaskInfo) => void) => { + this.listeners.add(listener); + return { dispose: () => this.listeners.delete(listener) }; + }; + + list(activeOnly = true): AgentTaskInfo[] { + return [...this.tasks.values()].filter((info) => !activeOnly || info.status === 'running'); + } + + change(info: AgentTaskInfo): void { + this.tasks.set(info.taskId, info); + for (const listener of this.listeners) listener(info); + } +} + class FakeAgentHandle { readonly kind = LifecycleScope.Agent; readonly bus = new FakeAgentBus(); + readonly tasks = new FakeAgentTasks(); readonly accessor; readonly context: AgentContext; activity: AgentActivitySnapshot = {}; @@ -176,6 +198,7 @@ class FakeAgentHandle { this.context = scope.agentContext; this.services.set(IAgentScopeContext, scope); this.services.set(IEventBus, this.bus); + this.services.set(IAgentTaskService, this.tasks); this.services.set(IAgentConversationUndoParticipantRegistry, { register: () => ({ dispose: () => {} }), }); @@ -1562,7 +1585,7 @@ describe('SessionEventBroadcaster', () => { session_id: 's1', payload: { agent_id: 'main', has_more_older: false, snapshot: { items: [] } }, }); - expect(view.deliveries).toEqual(['subscription']); + expect(view.deliveries).toEqual(['subscription', 'subscription']); } main.bus.emit(agentEvent('turn.started', { turnId: 1, origin: { kind: 'user' } })); @@ -1642,6 +1665,123 @@ describe('SessionEventBroadcaster', () => { expect((ops.payload as OpsPayload).agent_id).toBe('agent-0'); }); + function shellTask(taskId: string, status: AgentTaskInfo['status']): AgentTaskInfo { + return { + taskId, + kind: 'process', + description: 'dev server', + status, + detached: true, + startedAt: 1_700_000_000_000, + endedAt: status === 'running' ? null : 1_700_000_005_000, + command: 'npm run dev', + pid: 1, + exitCode: null, + parentToolCallId: 'call_bash', + }; + } + + type TaskFrame = { type: string; agent_id: string; task_id?: string; status?: string }; + const taskFramesOf = (view: ReturnType): TaskFrame[] => + view.envelopes.filter( + (frame) => frame.type === 'task' || frame.type === 'task.list', + ) as unknown as TaskFrame[]; + const summarize = (frames: TaskFrame[]): string[] => + frames.map((frame) => + frame.type === 'task.list' + ? `list:${frame.agent_id}` + : `task:${frame.agent_id}:${frame.task_id}:${frame.status}`, + ); + + it('lists the running tasks of every subscribed agent, then streams each change as a task frame', async () => { + const lc = new FakeLifecycle(); + const main = lc.addAgent('main'); + main.tasks.change(shellTask('bash-1', 'running')); + main.tasks.change(shellTask('bash-0', 'completed')); + sessions.set('s1', lc); + bc = makeBroadcasterWithTranscript(); + + const view = collectingTarget(); + const offView = collectingTarget(); + await bc.subscribe('s1', view.target, { '*': 'turn' }); + await bc.subscribe('s1', offView.target, { '*': 'off' }); + main.tasks.change(shellTask('bash-1', 'killed')); + + expect(taskFramesOf(view)).toEqual([ + { + type: 'task.list', + session_id: 's1', + agent_id: 'main', + tasks: [ + { + task_id: 'bash-1', + kind: 'shell', + status: 'running', + detached: true, + description: 'dev server', + parent_tool_call_id: 'call_bash', + command: 'npm run dev', + started_at: new Date(1_700_000_000_000).toISOString(), + }, + ], + }, + expect.objectContaining({ + type: 'task', + agent_id: 'main', + task_id: 'bash-1', + status: 'ended', + outcome: 'killed', + ended_at: new Date(1_700_000_005_000).toISOString(), + }), + ]); + expect(taskFramesOf(offView)).toEqual([]); + }); + + it('lists only newly subscribed agents on a re-subscribe and keeps streaming changes meanwhile', async () => { + const lc = new FakeLifecycle(); + const main = lc.addAgent('main'); + const sub = lc.addAgent('agent-0'); + main.tasks.change(shellTask('bash-1', 'running')); + sub.tasks.change(shellTask('bash-2', 'running')); + sessions.set('s1', lc); + bc = makeBroadcasterWithTranscript(); + + const view = collectingTarget(); + let ended = false; + const target: BroadcastTarget = { + send: (frame, delivery) => { + view.target.send(frame, delivery); + if (ended || frame.type !== 'transcript.reset') return; + if ((frame.payload as { agent_id?: string }).agent_id !== 'agent-0') return; + ended = true; + main.tasks.change(shellTask('bash-1', 'completed')); + }, + }; + await bc.subscribe('s1', target, { main: 'turn', 'agent-0': 'off' }); + await bc.subscribe('s1', target, { main: 'turn', 'agent-0': 'turn' }); + + expect(ended).toBe(true); + expect(summarize(taskFramesOf(view))).toEqual([ + 'list:main', + 'list:agent-0', + 'task:main:bash-1:ended', + ]); + }); + + it('streams the task changes of an agent created after the subscription', async () => { + const lc = new FakeLifecycle(); + lc.addAgent('main'); + sessions.set('s1', lc); + bc = makeBroadcasterWithTranscript(); + + const view = collectingTarget(); + await bc.subscribe('s1', view.target, { '*': 'turn' }); + const late = lc.addAgent('agent-1'); + late.tasks.change(shellTask('bash-3', 'running')); + + expect(summarize(taskFramesOf(view))).toEqual(['list:main', 'task:agent-1:bash-3:running']); + }); + it('streams for a client subscribed before any agent exists', async () => { const lc = new FakeLifecycle(); sessions.set('s1', lc); @@ -1819,8 +1959,8 @@ describe('SessionEventBroadcaster', () => { expect(payload.snapshot.items).toEqual([]); expect(payload.has_more_older).toBe(true); expect(payload.seq).toBeTypeOf('number'); + expect(payload.snapshot).not.toHaveProperty('tasks'); expect(payload.snapshot).toMatchObject({ - tasks: [], interactions: [], attachments: [], todos: [], diff --git a/packages/kap-server/test/tasks.test.ts b/packages/kap-server/test/tasks.test.ts deleted file mode 100644 index 59662569f77..00000000000 --- a/packages/kap-server/test/tasks.test.ts +++ /dev/null @@ -1,445 +0,0 @@ -import { mkdtemp, rm } from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; - -import { - IAgentLifecycleService, - IAgentTaskService, - getLiveSessionById, - IModelCatalog, - type AgentTask, -} from '@moonshot-ai/agent-core-v2'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; - -import { type RunningServer, startServer } from '../src/start'; -import { TEST_HOST_IDENTITY } from './helpers/hostIdentity'; -import { authHeaders } from './helpers/auth'; - -interface Envelope { - code: number; - msg: string; - data: T; - request_id: string; - details?: unknown; -} - -interface TaskWire { - id: string; - session_id: string; - kind: string; - description: string; - status: string; - command?: string; - created_at: string; - started_at?: string; - completed_at?: string; - output_preview?: string; - output_bytes?: number; - agent_id?: string; - subagent_type?: string; - parent_tool_call_id?: string; - run_in_background?: boolean; -} - -interface ListWire { - items: TaskWire[]; -} - -describe('server-v2 /api/v1/sessions/{sid}/tasks', () => { - let server: RunningServer | undefined; - let home: string | undefined; - let base: string; - - beforeAll(async () => { - home = await mkdtemp(join(tmpdir(), 'kimi-server-v2-tasks-')); - const modelCatalog: IModelCatalog = { - _serviceBrand: undefined, - get: () => { - throw new Error('modelCatalog.get not exercised in this test'); - }, - getRequester: () => { - throw new Error('modelCatalog.getRequester not exercised in this test'); - }, - generate: () => { - throw new Error('modelCatalog.generate not exercised in this test'); - }, - ping: () => { - throw new Error('modelCatalog.ping not exercised in this test'); - }, - findByName: () => [], - listModels: async () => [], - listProviders: async () => [], - getProvider: async () => { - throw new Error('modelCatalog.getProvider not exercised in this test'); - }, - setDefaultModel: async () => { - throw new Error('modelCatalog.setDefaultModel not exercised in this test'); - }, - }; - server = await startServer({ - hostIdentity: TEST_HOST_IDENTITY, - host: '127.0.0.1', - port: 0, - homeDir: home, - logLevel: 'silent', - seeds: [[IModelCatalog, modelCatalog]], - }); - base = `http://127.0.0.1:${server.port}`; - }); - - afterAll(async () => { - if (server !== undefined) { - await server.close(); - server = undefined; - } - if (home !== undefined) { - await rm(home, { recursive: true, force: true, maxRetries: 3, retryDelay: 25 } as never); - home = undefined; - } - }); - - async function getJson(path: string): Promise<{ status: number; body: Envelope }> { - const res = await fetch(`${base}${path}`, { - headers: authHeaders(server as RunningServer), - } as never); - return { status: res.status, body: (await res.json()) as Envelope }; - } - - async function postJson(path: string): Promise<{ status: number; body: Envelope }> { - const res = await fetch(`${base}${path}`, { - method: 'POST', - headers: authHeaders(server as RunningServer), - } as never); - return { status: res.status, body: (await res.json()) as Envelope }; - } - - async function createSession(): Promise { - const res = await fetch(`${base}/api/v1/sessions`, { - method: 'POST', - headers: authHeaders(server as RunningServer, { 'content-type': 'application/json' }), - body: JSON.stringify({ metadata: { cwd: home as string } }), - } as never); - const body = (await res.json()) as Envelope<{ id: string }>; - expect(body.code).toBe(0); - return body.data.id; - } - - async function mainAgentTasks(sessionId: string): Promise { - const session = getLiveSessionById(server!.core.accessor, sessionId); - if (session === undefined) throw new Error(`session ${sessionId} not found`); - let agent = session.accessor.get(IAgentLifecycleService).handleOf('main'); - if (agent === undefined) { - await session.accessor.get(IAgentLifecycleService).create({ agentId: 'main' }); - agent = session.accessor.get(IAgentLifecycleService).handleOf('main')!; - } - return agent.accessor.get(IAgentTaskService); - } - - async function flush(): Promise { - await new Promise((resolve) => setTimeout(resolve, 10)); - } - - function fakeTask(kind: 'process' | 'agent' | 'question', output?: string): AgentTask { - return { - idPrefix: 'test', - kind, - description: `fake ${kind} task`, - start: (sink) => { - if (output !== undefined) sink.appendOutput(output); - }, - toInfo: (base) => { - switch (kind) { - case 'process': - return { ...base, kind: 'process', command: 'echo hi', pid: 0, exitCode: null }; - case 'agent': - return { - ...base, - kind: 'agent', - agentId: 'sub-1', - subagentType: 'explore', - parentToolCallId: 'call-parent-1', - model: 'provider/secondary', - thinkingEffort: 'low', - }; - case 'question': - return { ...base, kind: 'question', questionCount: 1 }; - } - }, - }; - } - - it('returns an empty list when the session has no main agent (gap G10)', async () => { - const id = await createSession(); - const { body } = await getJson(`/api/v1/sessions/${id}/tasks`); - expect(body.code).toBe(0); - expect(body.data.items).toEqual([]); - }); - - it('returns an empty list when the main agent has no tasks yet', async () => { - const id = await createSession(); - await mainAgentTasks(id); - const { body } = await getJson(`/api/v1/sessions/${id}/tasks`); - expect(body.code).toBe(0); - expect(body.data.items).toEqual([]); - }); - - it('lists registered tasks with mapped kind/status and wire-shaped fields', async () => { - const id = await createSession(); - const tasks = await mainAgentTasks(id); - const processId = tasks.registerTask(fakeTask('process')); - const agentId = tasks.registerTask(fakeTask('agent')); - const questionId = tasks.registerTask(fakeTask('question')); - await flush(); - - const { body } = await getJson(`/api/v1/sessions/${id}/tasks`); - expect(body.code).toBe(0); - const byId = new Map(body.data.items.map((t) => [t.id, t])); - expect(byId.size).toBe(3); - - const process = byId.get(processId); - expect(process).toMatchObject({ - id: processId, - session_id: id, - kind: 'bash', - status: 'running', - description: 'fake process task', - command: 'echo hi', - }); - expect(typeof process?.created_at).toBe('string'); - - expect(byId.get(agentId)).toMatchObject({ - id: agentId, - session_id: id, - kind: 'subagent', - status: 'running', - model: 'provider/secondary', - thinking_effort: 'low', - agent_id: 'sub-1', - subagent_type: 'explore', - parent_tool_call_id: 'call-parent-1', - }); - expect(byId.get(agentId)?.command).toBeUndefined(); - - expect(byId.get(questionId)).toMatchObject({ - id: questionId, - session_id: id, - kind: 'tool', - status: 'running', - }); - expect(byId.get(processId)?.agent_id).toBeUndefined(); - expect(byId.get(questionId)?.agent_id).toBeUndefined(); - expect(byId.get(processId)?.subagent_type).toBeUndefined(); - expect(byId.get(questionId)?.subagent_type).toBeUndefined(); - expect(byId.get(processId)?.parent_tool_call_id).toBeUndefined(); - expect(byId.get(questionId)?.parent_tool_call_id).toBeUndefined(); - }); - - it('reports run_in_background from the task detached flag', async () => { - const id = await createSession(); - const tasks = await mainAgentTasks(id); - const backgroundId = tasks.registerTask(fakeTask('agent')); - const foregroundId = tasks.registerTask(fakeTask('agent'), { detached: false }); - await flush(); - - const { body } = await getJson(`/api/v1/sessions/${id}/tasks`); - expect(body.code).toBe(0); - const byId = new Map(body.data.items.map((t) => [t.id, t])); - expect(byId.get(backgroundId)?.run_in_background).toBe(true); - expect(byId.get(foregroundId)?.run_in_background).toBe(false); - - const single = await getJson(`/api/v1/sessions/${id}/tasks/${foregroundId}`); - expect(single.body.data.run_in_background).toBe(false); - }); - - it('filters the list by wire status', async () => { - const id = await createSession(); - const tasks = await mainAgentTasks(id); - tasks.registerTask(fakeTask('process')); - await flush(); - - const running = await getJson(`/api/v1/sessions/${id}/tasks?status=running`); - expect(running.body.code).toBe(0); - expect(running.body.data.items).toHaveLength(1); - expect(running.body.data.items[0]?.status).toBe('running'); - - const completed = await getJson(`/api/v1/sessions/${id}/tasks?status=completed`); - expect(completed.body.code).toBe(0); - expect(completed.body.data.items).toEqual([]); - }); - - it('gets a single task by id and 40406 for an unknown task', async () => { - const id = await createSession(); - const tasks = await mainAgentTasks(id); - const taskId = tasks.registerTask(fakeTask('process')); - const subagentId = tasks.registerTask(fakeTask('agent')); - await flush(); - - const got = await getJson(`/api/v1/sessions/${id}/tasks/${taskId}`); - expect(got.body.code).toBe(0); - expect(got.body.data).toMatchObject({ id: taskId, session_id: id, kind: 'bash' }); - expect(got.body.data.agent_id).toBeUndefined(); - - const gotSubagent = await getJson(`/api/v1/sessions/${id}/tasks/${subagentId}`); - expect(gotSubagent.body.code).toBe(0); - expect(gotSubagent.body.data).toMatchObject({ - id: subagentId, - session_id: id, - kind: 'subagent', - agent_id: 'sub-1', - subagent_type: 'explore', - parent_tool_call_id: 'call-parent-1', - }); - - const missing = await getJson(`/api/v1/sessions/${id}/tasks/nope`); - expect(missing.body.code).toBe(40406); - }); - - it('includes output_preview / output_bytes when with_output is set', async () => { - const id = await createSession(); - const tasks = await mainAgentTasks(id); - const taskId = tasks.registerTask(fakeTask('process', 'hello world')); - await flush(); - - const got = await getJson( - `/api/v1/sessions/${id}/tasks/${taskId}?with_output=true`, - ); - expect(got.body.code).toBe(0); - expect(got.body.data.output_preview).toBe('hello world'); - expect(got.body.data.output_bytes).toBe(Buffer.byteLength('hello world', 'utf-8')); - - const plain = await getJson(`/api/v1/sessions/${id}/tasks/${taskId}`); - expect(plain.body.code).toBe(0); - expect(plain.body.data.output_preview).toBeUndefined(); - expect(plain.body.data.output_bytes).toBeUndefined(); - }); - - it('cancels a running task and reports 40904 on a second cancel', async () => { - const id = await createSession(); - const tasks = await mainAgentTasks(id); - const taskId = tasks.registerTask(fakeTask('process')); - await flush(); - - const cancelled = await postJson<{ cancelled: boolean }>( - `/api/v1/sessions/${id}/tasks/${taskId}:cancel`, - ); - expect(cancelled.body.code).toBe(0); - expect(cancelled.body.data).toEqual({ cancelled: true }); - expect(tasks.getTask(taskId)?.stopReason).toBe('Aborted by the user'); - - const again = await postJson<{ cancelled: boolean }>( - `/api/v1/sessions/${id}/tasks/${taskId}:cancel`, - ); - expect(again.body.code).toBe(40904); - expect(again.body.data).toEqual({ cancelled: false }); - expect(again.body.details).toEqual({ current_status: 'cancelled' }); - }); - - it('cancelling an unknown task returns 40406', async () => { - const id = await createSession(); - await mainAgentTasks(id); - const { body } = await postJson(`/api/v1/sessions/${id}/tasks/nope:cancel`); - expect(body.code).toBe(40406); - }); - - it('rejects a bare POST without the :cancel suffix (40001)', async () => { - const id = await createSession(); - const tasks = await mainAgentTasks(id); - const taskId = tasks.registerTask(fakeTask('process')); - await flush(); - - const { body } = await postJson(`/api/v1/sessions/${id}/tasks/${taskId}`); - expect(body.code).toBe(40001); - }); - - it('detaches a running foreground task and flips run_in_background', async () => { - const id = await createSession(); - const tasks = await mainAgentTasks(id); - const taskId = tasks.registerTask(fakeTask('process'), { detached: false }); - await flush(); - - const detached = await postJson<{ detached: boolean; status: string }>( - `/api/v1/sessions/${id}/tasks/${taskId}:detach`, - ); - expect(detached.body.code).toBe(0); - expect(detached.body.data).toEqual({ detached: true, status: 'running' }); - expect(tasks.getTask(taskId)?.detached).toBe(true); - - const got = await getJson(`/api/v1/sessions/${id}/tasks/${taskId}`); - expect(got.body.data.run_in_background).toBe(true); - }); - - it('answers concurrent detach requests idempotently', async () => { - const id = await createSession(); - const tasks = await mainAgentTasks(id); - const taskId = tasks.registerTask(fakeTask('process'), { detached: false }); - await flush(); - - const [first, second] = await Promise.all([ - postJson<{ detached: boolean; status: string }>( - `/api/v1/sessions/${id}/tasks/${taskId}:detach`, - ), - postJson<{ detached: boolean; status: string }>( - `/api/v1/sessions/${id}/tasks/${taskId}:detach`, - ), - ]); - expect(first.body.code).toBe(0); - expect(second.body.code).toBe(0); - const detachedFlags = [first.body.data.detached, second.body.data.detached].toSorted(); - expect(detachedFlags).toEqual([false, true]); - expect(first.body.data.status).toBe('running'); - expect(second.body.data.status).toBe('running'); - }); - - it('reports detached: false for an already-background task', async () => { - const id = await createSession(); - const tasks = await mainAgentTasks(id); - const taskId = tasks.registerTask(fakeTask('process')); - await flush(); - - const { body } = await postJson<{ detached: boolean; status: string }>( - `/api/v1/sessions/${id}/tasks/${taskId}:detach`, - ); - expect(body.code).toBe(0); - expect(body.data).toEqual({ detached: false, status: 'running' }); - }); - - it('reports detached: false with the current status for a finished task', async () => { - const id = await createSession(); - const tasks = await mainAgentTasks(id); - const taskId = tasks.registerTask({ - ...fakeTask('process'), - start: (sink) => { - void sink.settle({ status: 'completed' }); - }, - }); - await flush(); - expect(tasks.getTask(taskId)?.status).toBe('completed'); - - const { body } = await postJson<{ detached: boolean; status: string }>( - `/api/v1/sessions/${id}/tasks/${taskId}:detach`, - ); - expect(body.code).toBe(0); - expect(body.data).toEqual({ detached: false, status: 'completed' }); - }); - - it('detaching an unknown task returns 40406', async () => { - const id = await createSession(); - await mainAgentTasks(id); - const { body } = await postJson(`/api/v1/sessions/${id}/tasks/nope:detach`); - expect(body.code).toBe(40406); - }); - - it('returns 40401 for an unknown session on all three endpoints', async () => { - const list = await getJson('/api/v1/sessions/nope/tasks'); - expect(list.body.code).toBe(40401); - - const got = await getJson('/api/v1/sessions/nope/tasks/tid'); - expect(got.body.code).toBe(40401); - - const cancelled = await postJson('/api/v1/sessions/nope/tasks/tid:cancel'); - expect(cancelled.body.code).toBe(40401); - - const detached = await postJson('/api/v1/sessions/nope/tasks/tid:detach'); - expect(detached.body.code).toBe(40401); - }); -}); diff --git a/packages/kap-server/test/transcriptContract.e2e.test.ts b/packages/kap-server/test/transcriptContract.e2e.test.ts index 9ce030fce11..1bcc32e2244 100644 --- a/packages/kap-server/test/transcriptContract.e2e.test.ts +++ b/packages/kap-server/test/transcriptContract.e2e.test.ts @@ -9,12 +9,14 @@ import { WebSocket, type RawData } from 'ws'; import { IAgentLifecycleService, IAgentPlanService, + IAgentTaskService, IConfigService, closeSessionById, MAIN_AGENT_ID, getLiveSessionById, interactions, resumeSessionById, + type AgentTask, type ApprovalRequest, } from '@moonshot-ai/agent-core-v2'; @@ -194,6 +196,7 @@ async function until(label: string, fn: () => Promise | boolean, timeou interface TranscriptChannel { readonly frames: any[]; readonly ops: any[]; + readonly tasks: any[]; reset(): any; close(): void; } @@ -209,6 +212,7 @@ async function subscribeTranscript(server: RunningServer, sid: string): Promise< const ws = new WebSocket(`ws://127.0.0.1:${server.port}/api/v1/ws`, [`kimi-code.bearer.${bearerToken(server)}`]); const frames: any[] = []; const ops: any[] = []; + const tasks: any[] = []; let resetFrame: any; ws.on('message', (data) => { let frame: any; @@ -223,6 +227,7 @@ async function subscribeTranscript(server: RunningServer, sid: string): Promise< resetFrame = frame; } if (frame.type === 'transcript.ops' && payload?.agent_id === 'main') ops.push(...(payload.ops ?? [])); + if (frame.type === 'task') tasks.push(frame); }); await new Promise((resolve, reject) => { ws.once('open', () => { @@ -235,11 +240,55 @@ async function subscribeTranscript(server: RunningServer, sid: string): Promise< return { frames, ops, + tasks, reset: () => resetFrame?.payload, close: () => ws.close(), }; } +interface FrameSocket { + send(frame: unknown): void; + next(match: (frame: any) => boolean): Promise; + close(): Promise; +} + +async function openFrameSocket(server: RunningServer): Promise { + const ws = new WebSocket(`ws://127.0.0.1:${server.port}/api/v1/ws`, [`kimi-code.bearer.${bearerToken(server)}`]); + const frames: any[] = []; + ws.on('message', (data) => { + try { + frames.push(JSON.parse(rawToString(data))); + } catch { + } + }); + const closed = new Promise((resolve) => { + ws.once('close', () => resolve()); + }); + await new Promise((resolve, reject) => { + ws.once('open', () => { + resolve(); + }); + ws.once('error', reject); + }); + return { + send: (frame) => ws.send(JSON.stringify(frame)), + next: async (match) => { + let found: any; + await until('ws frame', () => { + const index = frames.findIndex(match); + if (index < 0) return false; + found = frames.splice(index, 1)[0]; + return true; + }, 10000, 20); + return found; + }, + close: async () => { + ws.close(); + await closed; + }, + }; +} + describe('transcript contract e2e', () => { let home: string | undefined; @@ -519,7 +568,7 @@ describe('transcript contract e2e', () => { expect(String(toolFrame.output)).toContain(output); }); - it('S4: a background subagent produces task entities and a task-origin notification turn', async () => { + it('S4: a background subagent streams task events and a task-origin notification turn', async () => { await boot([ { match: (body) => body.includes('spawn-bg') && !body.includes('"role":"tool"'), @@ -530,32 +579,27 @@ describe('transcript contract e2e', () => { ]); const sid = await createSession(server!, base); await submitPrompt(server!, base, sid, 'spawn-bg one background agent'); + const channel = await subscribeTranscript(server!, sid); - await until('background task running', async () => { - const tx = await getTranscript(server!, base, sid); - return tx.tasks.some((t: any) => t.kind === 'subagent' && t.state === 'running'); + await until('background task running', () => + channel.tasks.some((t) => t.kind === 'subagent' && t.status === 'running'), + ); + const task = channel.tasks.find((t) => t.kind === 'subagent'); + expect(task).toMatchObject({ + session_id: sid, + agent_id: MAIN_AGENT_ID, + status: 'running', + phase: 'working', + detached: true, }); - const mid = await getTranscript(server!, base, sid); - const task = mid.tasks.find((t: any) => t.kind === 'subagent'); - expect(task).toMatchObject({ state: 'running', detached: true }); - expect(typeof task.agentId).toBe('string'); - - await until('task completed', async () => { - const tx = await getTranscript(server!, base, sid); - return tx.tasks.some((t: any) => t.taskId === task.taskId && t.state === 'completed'); - }, 45000).catch(async (error) => { - const tx = await getTranscript(server!, base, sid); - const opsData = await rest<{ batches: { seq: number; ops: any[] }[] }>( - server!, - base, - `/api/v1/sessions/${encodeURIComponent(sid)}/transcript/ops?agent_id=main&since_seq=0`, - ); - const taskOps = opsData.batches.flatMap((b) => - b.ops - .filter((o: any) => o.op === 'task.upsert') - .map((o: any) => `${b.seq}:${o.task.taskId}:${o.task.state}`), - ); - throw new Error(`${(error as Error).message}\ntasks: ${JSON.stringify(tx.tasks)}\ntaskOps: ${JSON.stringify(taskOps)}`, { cause: error }); + expect(typeof task.child_agent_id).toBe('string'); + + await until( + 'task completed', + () => channel.tasks.some((t) => t.task_id === task.task_id && t.outcome === 'completed'), + 45000, + ).catch((error: unknown) => { + throw new Error(`${(error as Error).message}\ntasks: ${JSON.stringify(channel.tasks)}`, { cause: error }); }); await until('notification turn exists', async () => { const tx = await getTranscript(server!, base, sid); @@ -566,8 +610,11 @@ describe('transcript contract e2e', () => { const taskTurn = end.items.find((i: any) => i.kind === 'turn' && i.origin?.kind === 'task'); expect(taskTurn).toBeDefined(); expect(JSON.stringify(taskTurn)).toContain('notification'); + expect(end).not.toHaveProperty('tasks'); + expect(channel.ops.some((op) => op.op === 'task.upsert')).toBe(false); await idleOrDump(server!, base, sid); expect((await getTranscript(server!, base, sid)).meta.activity).toBe('idle'); + channel.close(); }); it('S5: late attach backfills liveness and the prompt queue from the live loop', async () => { @@ -601,11 +648,10 @@ describe('transcript contract e2e', () => { const reset = channel.reset().snapshot; expect(reset.meta).toEqual(snapshot.meta); + expect(reset).not.toHaveProperty('tasks'); + expect(snapshot).not.toHaveProperty('tasks'); const byId = (xs: any[], key: string): Record => Object.fromEntries(xs.map((x) => [x[key], x])); - expect(Object.keys(byId(reset.tasks ?? [], 'taskId')).toSorted()).toEqual( - Object.keys(byId(snapshot.tasks, 'taskId')).toSorted(), - ); expect(Object.keys(byId(reset.interactions ?? [], 'interactionId')).toSorted()).toEqual( Object.keys(byId(snapshot.interactions, 'interactionId')).toSorted(), ); @@ -618,6 +664,78 @@ describe('transcript contract e2e', () => { channel.close(); }); + it('S9: task.watch streams a running task output and resumes from since_offset', async () => { + const sid = await createSession(server!, base); + const agents = getLiveSessionById(server!.core.accessor, sid)!.accessor.get(IAgentLifecycleService); + if (agents.handleOf(MAIN_AGENT_ID) === undefined) await agents.create({ agentId: MAIN_AGENT_ID }); + let sink: Parameters[0] | undefined; + const taskId = agents.handleOf(MAIN_AGENT_ID)!.accessor.get(IAgentTaskService).registerTask({ + idPrefix: 'bash', + kind: 'process', + description: 'npm run dev', + start: (s) => { + sink = s; + s.appendOutput(' VITE ready\n'); + }, + toInfo: (base) => ({ ...base, kind: 'process', command: 'npm run dev', pid: 0, exitCode: null }), + }); + const isOutput = (frame: any): boolean => frame.type === 'task.output'; + + const first = await openFrameSocket(server!); + first.send({ type: 'task.watch', request_id: 'r1', session_id: sid, agent_id: MAIN_AGENT_ID, task_id: taskId }); + expect(await first.next((f) => f.type === 'response')).toMatchObject({ request_id: 'r1', code: 0 }); + expect(await first.next(isOutput)).toMatchObject({ + agent_id: MAIN_AGENT_ID, + task_id: taskId, + offset: 0, + text: ' VITE ready\n', + }); + sink!.appendOutput(' ➜ Local: http://localhost:5173/\n'); + expect(await first.next(isOutput)).toMatchObject({ offset: 13, text: ' ➜ Local: http://localhost:5173/\n' }); + await first.close(); + + sink!.appendOutput('4:42:10 PM [vite] hmr update /src/App.vue\n'); + const since = 13 + Buffer.byteLength(' ➜ Local: http://localhost:5173/\n', 'utf-8'); + const second = await openFrameSocket(server!); + second.send({ + type: 'task.watch', + request_id: 'r2', + session_id: sid, + agent_id: MAIN_AGENT_ID, + task_id: taskId, + since_offset: since, + }); + expect(await second.next((f) => f.type === 'response')).toMatchObject({ request_id: 'r2', code: 0 }); + expect(await second.next(isOutput)).toMatchObject({ + offset: since, + text: '4:42:10 PM [vite] hmr update /src/App.vue\n', + }); + second.send({ type: 'task.watch', request_id: 'r3', session_id: sid, agent_id: MAIN_AGENT_ID, task_id: 'bash-00000000' }); + expect(await second.next((f) => f.type === 'response')).toMatchObject({ request_id: 'r3', code: 40406 }); + + const child = await agents.create({ agentId: 'agent-s9-child' }); + const childTaskId = agents.handleOf(child.agentId)!.accessor.get(IAgentTaskService).registerTask({ + idPrefix: 'bash', + kind: 'process', + description: 'pnpm test --watch', + start: (s) => { + s.appendOutput(' RUN v3.2.4\n'); + }, + toInfo: (base) => ({ ...base, kind: 'process', command: 'pnpm test --watch', pid: 0, exitCode: null }), + }); + second.send({ type: 'task.watch', request_id: 'r4', session_id: sid, agent_id: MAIN_AGENT_ID, task_id: childTaskId }); + expect(await second.next((f) => f.type === 'response')).toMatchObject({ request_id: 'r4', code: 40406 }); + second.send({ type: 'task.watch', request_id: 'r5', session_id: sid, agent_id: child.agentId, task_id: childTaskId }); + expect(await second.next((f) => f.type === 'response')).toMatchObject({ request_id: 'r5', code: 0 }); + expect(await second.next(isOutput)).toMatchObject({ + agent_id: child.agentId, + task_id: childTaskId, + offset: 0, + text: ' RUN v3.2.4\n', + }); + await second.close(); + }); + it('S7: a foreground subagent resumes with its prior context after a server restart', async () => { let childAgentId: string | undefined; let resumedChildRequest: string | undefined; diff --git a/packages/kap-server/test/wsConnectionV1.test.ts b/packages/kap-server/test/wsConnectionV1.test.ts index b2aacc84716..c6ea5c57149 100644 --- a/packages/kap-server/test/wsConnectionV1.test.ts +++ b/packages/kap-server/test/wsConnectionV1.test.ts @@ -1,8 +1,13 @@ import type { WebSocket } from 'ws'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import type { AgentTaskOutputChunk, IAgentTaskService } from '@moonshot-ai/agent-core-v2'; +import type { AgentTaskInfo } from '@moonshot-ai/agent-core-v2/agent/task/types'; + +import { TASK_OUTPUT_DEFAULT_TAIL_BYTES, TASK_OUTPUT_MAX_TAIL_BYTES } from '../src/protocol/ws-task'; import type { IConnectionRegistry } from '../src/transport/ws/connectionRegistry'; import type { SessionEventBroadcaster } from '../src/transport/ws/v1/sessionEventBroadcaster'; +import type { TaskSource } from '../src/transport/ws/v1/taskRequests'; import { type WsConnectionV1Options, WsConnectionV1 } from '../src/transport/ws/v1/wsConnectionV1'; class FakeSocket { @@ -73,6 +78,7 @@ function makeConn(socket: FakeSocket, opts: Partial = {}) return new WsConnectionV1({ socket: socket as unknown as WebSocket, broadcaster: makeBroadcaster(), + taskSource: async () => ({ kind: 'session_not_found' }), connectionRegistry: makeRegistry(), remoteAddress: null, userAgent: null, @@ -639,3 +645,630 @@ describe('WsConnectionV1 global target registration', () => { expect(removed).toEqual([conn]); }); }); + +describe('WsConnectionV1 task output', () => { + interface FakeTaskOutput { + readonly source: TaskSource; + write(text: string): void; + readonly listenerCount: () => number; + readonly sourceCalls: () => number; + holdTailRead(): () => void; + holdSource(): () => void; + failTailRead(): void; + corruptTailRead(): void; + withholdTailBytes(count: number): void; + } + + function makeTaskOutput(taskId: string, initial = '', ownerAgentId = 'main'): FakeTaskOutput { + let bytes = Buffer.from(initial, 'utf-8'); + const listeners = new Set<(chunk: AgentTaskOutputChunk) => void>(); + let gate: Promise | undefined; + let sourceGate: Promise | undefined; + let sourceCalls = 0; + let tailReadFails = false; + let tailReadCorrupt = false; + let withheld = 0; + let info: AgentTaskInfo = { + taskId, + kind: 'process', + description: 'dev server', + status: 'running', + detached: false, + startedAt: 1_700_000_000_000, + endedAt: null, + command: 'npm run dev', + pid: 1, + exitCode: null, + parentToolCallId: 'call_bash', + }; + const tasks = { + getTask: (id: string) => (id === taskId ? info : undefined), + stopByUser: async (id: string) => { + if (id !== taskId) return undefined; + info = { ...info, status: 'killed', endedAt: 1_700_000_005_000 }; + return info; + }, + detach: (id: string) => { + if (id !== taskId) return undefined; + info = { ...info, detached: true }; + return info; + }, + onDidAppendOutput: (listener: (chunk: AgentTaskOutputChunk) => void) => { + listeners.add(listener); + return { dispose: () => listeners.delete(listener) }; + }, + readOutputTail: async (_id: string, maxBytes: number) => { + await gate; + if (tailReadFails) throw new Error('EACCES: permission denied'); + if (tailReadCorrupt) return { offset: 0, data: undefined }; + const visible = bytes.subarray(0, bytes.byteLength - withheld); + const offset = Math.max(0, visible.byteLength - maxBytes); + return { offset, data: new Uint8Array(visible.subarray(offset)) }; + }, + } as unknown as IAgentTaskService; + return { + source: async (sessionId, agentId, id) => { + sourceCalls += 1; + await sourceGate; + if (sessionId !== 's1') return { kind: 'session_not_found' }; + if (agentId !== ownerAgentId || id !== taskId) return { kind: 'task_not_found' }; + return { kind: 'found', tasks }; + }, + sourceCalls: () => sourceCalls, + holdSource: () => { + let release!: () => void; + sourceGate = new Promise((resolve) => { + release = resolve; + }); + return () => { + sourceGate = undefined; + release(); + }; + }, + corruptTailRead: () => { + tailReadCorrupt = true; + }, + withholdTailBytes: (count) => { + withheld = count; + }, + write: (text) => { + const offset = bytes.byteLength; + bytes = Buffer.concat([bytes, Buffer.from(text, 'utf-8')]); + for (const listener of listeners) listener({ taskId, offset, text }); + }, + listenerCount: () => listeners.size, + holdTailRead: () => { + let release!: () => void; + gate = new Promise((resolve) => { + release = resolve; + }); + return () => { + gate = undefined; + release(); + }; + }, + failTailRead: () => { + tailReadFails = true; + }, + }; + } + + function send(socket: FakeSocket, frame: Record): void { + socket.emit('message', JSON.stringify(frame)); + } + + function taskFrames(socket: FakeSocket): unknown[] { + return socket.frames().filter((frame) => (frame as { type: string }).type !== 'server_hello'); + } + + const ok = (requestId: string): unknown => + expect.objectContaining({ type: 'response', request_id: requestId, code: 0 }); + + const startLines = + '\n> web@0.1.0 dev\n> vite\n\n VITE v6.3.5 ready in 412 ms\n\n ➜ Local: http://localhost:5173/\n'; + const hmrLine = '4:42:10 PM [vite] hmr update /src/App.vue\n'; + + it('replies, sends the current output from offset 0, then streams new output', async () => { + const output = makeTaskOutput('bash-k3f9x2ab', startLines); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + + send(socket, { type: 'task.watch', request_id: 'r1', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(2)); + output.write(hmrLine); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(3)); + + expect(taskFrames(socket)).toEqual([ + ok('r1'), + { type: 'task.output', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab', offset: 0, text: startLines }, + { + type: 'task.output', + session_id: 's1', + agent_id: 'main', + task_id: 'bash-k3f9x2ab', + offset: Buffer.byteLength(startLines, 'utf-8'), + text: hmrLine, + }, + ]); + conn.close(); + }); + + it('sends only the output after since_offset when it is within the tail', async () => { + const output = makeTaskOutput('bash-k3f9x2ab', startLines + hmrLine); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + const since = Buffer.byteLength(startLines, 'utf-8'); + + send(socket, { + type: 'task.watch', + request_id: 'r1', + session_id: 's1', + agent_id: 'main', + task_id: 'bash-k3f9x2ab', + since_offset: since, + }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(2)); + + expect(taskFrames(socket)[1]).toEqual({ + type: 'task.output', + session_id: 's1', + agent_id: 'main', + task_id: 'bash-k3f9x2ab', + offset: since, + text: hmrLine, + }); + conn.close(); + }); + + it('sends an empty first chunk when since_offset is already at the end', async () => { + const output = makeTaskOutput('bash-k3f9x2ab', startLines); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + const end = Buffer.byteLength(startLines, 'utf-8'); + + send(socket, { + type: 'task.watch', + request_id: 'r1', + session_id: 's1', + agent_id: 'main', + task_id: 'bash-k3f9x2ab', + since_offset: end, + }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(2)); + await new Promise((resolve) => setTimeout(resolve, 30)); + + expect(taskFrames(socket)).toEqual([ + ok('r1'), + { type: 'task.output', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab', offset: end, text: '' }, + ]); + conn.close(); + }); + + it('sends an empty first chunk when the task has no output yet', async () => { + const output = makeTaskOutput('bash-k3f9x2ab'); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + + send(socket, { type: 'task.watch', request_id: 'r1', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(2)); + + expect(taskFrames(socket)[1]).toEqual({ + type: 'task.output', + session_id: 's1', + agent_id: 'main', + task_id: 'bash-k3f9x2ab', + offset: 0, + text: '', + }); + conn.close(); + }); + + it('falls back to the last tail bytes aligned to a character boundary when since_offset is too old', async () => { + const filler = 'x'.repeat(TASK_OUTPUT_DEFAULT_TAIL_BYTES * 2); + const total = filler + '➜' + 'y'.repeat(TASK_OUTPUT_DEFAULT_TAIL_BYTES - 2); + const output = makeTaskOutput('bash-k3f9x2ab', total); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + + send(socket, { + type: 'task.watch', + request_id: 'r1', + session_id: 's1', + agent_id: 'main', + task_id: 'bash-k3f9x2ab', + since_offset: 12, + }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(2)); + + const totalBytes = Buffer.byteLength(total, 'utf-8'); + const first = taskFrames(socket)[1] as { offset: number; text: string }; + expect(first.offset).toBe(totalBytes - TASK_OUTPUT_DEFAULT_TAIL_BYTES + 2); + expect(first.text).toBe('y'.repeat(TASK_OUTPUT_DEFAULT_TAIL_BYTES - 2)); + expect(first.text).not.toContain('\uFFFD'); + conn.close(); + }); + + it('falls back to the tail start when since_offset splits a character', async () => { + const output = makeTaskOutput('bash-k3f9x2ab', startLines); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + const arrowStart = Buffer.from(startLines, 'utf-8').indexOf(Buffer.from('➜', 'utf-8')); + + send(socket, { + type: 'task.watch', + request_id: 'r1', + session_id: 's1', + agent_id: 'main', + task_id: 'bash-k3f9x2ab', + since_offset: arrowStart + 1, + }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(2)); + + expect(taskFrames(socket)[1]).toEqual({ + type: 'task.output', + session_id: 's1', + agent_id: 'main', + task_id: 'bash-k3f9x2ab', + offset: 0, + text: startLines, + }); + conn.close(); + }); + + it('drops output already covered by the first chunk and forwards output written during the read', async () => { + const output = makeTaskOutput('bash-k3f9x2ab', startLines); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + const release = output.holdTailRead(); + + send(socket, { type: 'task.watch', request_id: 'r1', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + await vi.waitFor(() => expect(output.listenerCount()).toBe(1)); + output.write(hmrLine); + release(); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(2)); + await new Promise((resolve) => setTimeout(resolve, 30)); + + expect(taskFrames(socket)).toEqual([ + ok('r1'), + { + type: 'task.output', + session_id: 's1', + agent_id: 'main', + task_id: 'bash-k3f9x2ab', + offset: 0, + text: startLines + hmrLine, + }, + ]); + conn.close(); + }); + + it('stops streaming after task.unwatch and replies to it', async () => { + const output = makeTaskOutput('bash-k3f9x2ab', startLines); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + + send(socket, { type: 'task.watch', request_id: 'r1', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(2)); + send(socket, { type: 'task.unwatch', request_id: 'r2', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(3)); + output.write(hmrLine); + await new Promise((resolve) => setTimeout(resolve, 30)); + + expect(taskFrames(socket)[2]).toEqual(ok('r2')); + expect(taskFrames(socket)).toHaveLength(3); + expect(output.listenerCount()).toBe(0); + conn.close(); + }); + + it('applies an unwatch sent during a pending watch after that watch completes', async () => { + const output = makeTaskOutput('bash-k3f9x2ab', startLines); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + const release = output.holdTailRead(); + + send(socket, { type: 'task.watch', request_id: 'r1', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + await vi.waitFor(() => expect(output.listenerCount()).toBe(1)); + send(socket, { type: 'task.unwatch', request_id: 'r2', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + await new Promise((resolve) => setTimeout(resolve, 30)); + expect(taskFrames(socket)).toEqual([]); + release(); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(3)); + + expect(taskFrames(socket)).toEqual([ + ok('r1'), + { type: 'task.output', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab', offset: 0, text: startLines }, + ok('r2'), + ]); + expect(output.listenerCount()).toBe(0); + conn.close(); + }); + + it('does not hold a watch behind a slow watch of another task', async () => { + const slow = makeTaskOutput('bash-slow0001', startLines); + const fast = makeTaskOutput('bash-fast0001', hmrLine); + const socket = new FakeSocket(); + const conn = makeConn(socket, { + taskSource: (sessionId, agentId, taskId) => + (taskId === 'bash-slow0001' ? slow : fast).source(sessionId, agentId, taskId), + }); + const release = slow.holdTailRead(); + + send(socket, { type: 'task.watch', request_id: 'r1', session_id: 's1', agent_id: 'main', task_id: 'bash-slow0001' }); + send(socket, { type: 'task.watch', request_id: 'r2', session_id: 's1', agent_id: 'main', task_id: 'bash-fast0001' }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(2)); + expect(taskFrames(socket)[0]).toEqual(ok('r2')); + + release(); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(4)); + expect(taskFrames(socket)[2]).toEqual(ok('r1')); + conn.close(); + }); + + it('limits the first chunk to tail_bytes', async () => { + const output = makeTaskOutput('bash-k3f9x2ab', startLines + hmrLine); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + + send(socket, { + type: 'task.watch', + request_id: 'r1', + session_id: 's1', + agent_id: 'main', + task_id: 'bash-k3f9x2ab', + tail_bytes: Buffer.byteLength(hmrLine, 'utf-8'), + }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(2)); + + expect(taskFrames(socket)[1]).toMatchObject({ + offset: Buffer.byteLength(startLines, 'utf-8'), + text: hmrLine, + }); + conn.close(); + }); + + it('streams a task owned by a subagent under that agent id', async () => { + const output = makeTaskOutput('bash-k3f9x2ab', startLines, 'agent-sub1'); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + + send(socket, { type: 'task.watch', request_id: 'r1', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + send(socket, { type: 'task.watch', request_id: 'r2', session_id: 's1', agent_id: 'agent-sub1', task_id: 'bash-k3f9x2ab' }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(3)); + + expect(taskFrames(socket)).toEqual([ + { type: 'response', request_id: 'r1', code: 40406, msg: 'task not found' }, + ok('r2'), + { type: 'task.output', session_id: 's1', agent_id: 'agent-sub1', task_id: 'bash-k3f9x2ab', offset: 0, text: startLines }, + ]); + conn.close(); + }); + + it('replies with error codes for unknown sessions, unknown tasks, and invalid messages', async () => { + const output = makeTaskOutput('bash-k3f9x2ab'); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + + send(socket, { type: 'task.watch', request_id: 'r1', session_id: 's2', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + send(socket, { type: 'task.watch', request_id: 'r2', session_id: 's1', agent_id: 'main', task_id: 'bash-00000000' }); + send(socket, { type: 'task.watch', request_id: 'r3', session_id: 's1', agent_id: 'main' }); + send(socket, { type: 'task.watch', request_id: 'r4', session_id: 's1', agent_id: 'main', task_id: '' }); + send(socket, { type: 'task.unwatch', request_id: 'r5', session_id: '', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + send(socket, { type: 'task.watch', request_id: 'r6', session_id: 's1', task_id: 'bash-k3f9x2ab' }); + send(socket, { + type: 'task.watch', + request_id: 'r7', + session_id: 's1', + agent_id: 'main', + task_id: 'bash-k3f9x2ab', + tail_bytes: TASK_OUTPUT_MAX_TAIL_BYTES + 1, + }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(7)); + + const codes = Object.fromEntries( + (taskFrames(socket) as Array<{ request_id: string; code: number }>).map((frame) => [ + frame.request_id, + frame.code, + ]), + ); + expect(codes).toEqual({ + r1: 40401, + r2: 40406, + r3: 40001, + r4: 40001, + r5: 40001, + r6: 40001, + r7: 40001, + }); + conn.close(); + }); + + it('replies with an internal error when the task lookup throws', async () => { + const socket = new FakeSocket(); + const conn = makeConn(socket, { + taskSource: async () => { + throw new Error('session restore failed'); + }, + }); + + send(socket, { type: 'task.watch', request_id: 'r1', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(1)); + + expect(taskFrames(socket)).toEqual([ + { type: 'response', request_id: 'r1', code: 50001, msg: 'internal error' }, + ]); + conn.close(); + }); + + it('replies with a persistence error and releases the watch when the output cannot be read', async () => { + const output = makeTaskOutput('bash-k3f9x2ab', startLines); + output.failTailRead(); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + + send(socket, { type: 'task.watch', request_id: 'r1', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(1)); + output.write(hmrLine); + await new Promise((resolve) => setTimeout(resolve, 30)); + + expect(taskFrames(socket)).toEqual([ + { type: 'response', request_id: 'r1', code: 50003, msg: 'task output unavailable' }, + ]); + expect(output.listenerCount()).toBe(0); + conn.close(); + }); + + it('closes the connection with 1008 when a task output message has no readable request_id', async () => { + const output = makeTaskOutput('bash-k3f9x2ab'); + const socket = new FakeSocket(); + makeConn(socket, { taskSource: output.source }); + + send(socket, { type: 'task.watch', session_id: 's1', task_id: 'bash-k3f9x2ab' }); + await vi.waitFor(() => expect(socket.closeCalls).toHaveLength(1)); + + expect(socket.closeCalls[0]).toEqual({ code: 1008, reason: 'invalid message' }); + expect(taskFrames(socket)).toEqual([]); + }); + + it('releases watches when the connection closes', async () => { + const output = makeTaskOutput('bash-k3f9x2ab', startLines); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + + send(socket, { type: 'task.watch', request_id: 'r1', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(2)); + conn.close(); + + expect(output.listenerCount()).toBe(0); + }); + + it('does not resolve watches still queued when the connection closes', async () => { + const output = makeTaskOutput('bash-k3f9x2ab', startLines); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + const release = output.holdSource(); + + send(socket, { type: 'task.watch', request_id: 'r1', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + send(socket, { type: 'task.watch', request_id: 'r2', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + await vi.waitFor(() => expect(output.sourceCalls()).toBe(1)); + conn.close(); + release(); + await new Promise((resolve) => setTimeout(resolve, 30)); + + expect(output.sourceCalls()).toBe(1); + expect(output.listenerCount()).toBe(0); + }); + + it('replies with an internal error and releases the watch when streaming setup throws', async () => { + const output = makeTaskOutput('bash-k3f9x2ab', startLines); + output.corruptTailRead(); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + + send(socket, { type: 'task.watch', request_id: 'r1', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(1)); + + expect(taskFrames(socket)).toEqual([ + { type: 'response', request_id: 'r1', code: 50001, msg: 'internal error' }, + ]); + expect(output.listenerCount()).toBe(0); + conn.close(); + }); + + it('forwards the unread rest of a chunk the tail read caught mid-write', async () => { + const output = makeTaskOutput('bash-k3f9x2ab', startLines); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + const release = output.holdTailRead(); + const line = 'héllo\n'; + + send(socket, { type: 'task.watch', request_id: 'r1', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + await vi.waitFor(() => expect(output.listenerCount()).toBe(1)); + output.write(line); + output.withholdTailBytes(Buffer.byteLength(line, 'utf-8') - 2); + release(); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(3)); + + const start = Buffer.byteLength(startLines, 'utf-8'); + expect(taskFrames(socket).slice(1)).toEqual([ + { + type: 'task.output', + session_id: 's1', + agent_id: 'main', + task_id: 'bash-k3f9x2ab', + offset: 0, + text: `${startLines}h`, + }, + { + type: 'task.output', + session_id: 's1', + agent_id: 'main', + task_id: 'bash-k3f9x2ab', + offset: start + 1, + text: 'éllo\n', + }, + ]); + conn.close(); + }); + + it('replies to a watch with the current state of the task', async () => { + const output = makeTaskOutput('bash-k3f9x2ab', startLines); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + + send(socket, { type: 'task.watch', request_id: 'r1', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(2)); + + expect(taskFrames(socket)[0]).toEqual({ + type: 'response', + request_id: 'r1', + code: 0, + task: { + task_id: 'bash-k3f9x2ab', + kind: 'shell', + status: 'running', + detached: false, + description: 'dev server', + parent_tool_call_id: 'call_bash', + command: 'npm run dev', + started_at: new Date(1_700_000_000_000).toISOString(), + }, + }); + conn.close(); + }); + + it('stops a task and moves a task to the background, replying with its new state', async () => { + const output = makeTaskOutput('bash-k3f9x2ab'); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + + send(socket, { type: 'task.background', request_id: 'r1', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + send(socket, { type: 'task.stop', request_id: 'r2', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + send(socket, { type: 'task.stop', request_id: 'r3', session_id: 's1', agent_id: 'main', task_id: 'bash-00000000' }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(3)); + + const responses = new Map( + (taskFrames(socket) as Array<{ request_id: string; code: number; task?: unknown }>).map((frame) => [ + frame.request_id, + frame, + ]), + ); + expect(responses.get('r1')).toMatchObject({ code: 0, task: { status: 'running', detached: true } }); + expect(responses.get('r2')).toMatchObject({ + code: 0, + task: { status: 'ended', outcome: 'killed', detached: true }, + }); + expect(responses.get('r3')).toMatchObject({ code: 40406 }); + conn.close(); + }); + + it('closes the connection with 1008 when a task command has no readable request_id', async () => { + const output = makeTaskOutput('bash-k3f9x2ab'); + const socket = new FakeSocket(); + makeConn(socket, { taskSource: output.source }); + + send(socket, { type: 'task.stop', session_id: 's1', agent_id: 'main', task_id: 'bash-k3f9x2ab' }); + await vi.waitFor(() => expect(socket.closeCalls).toHaveLength(1)); + + expect(socket.closeCalls[0]).toEqual({ code: 1008, reason: 'invalid message' }); + }); +}); From 90230d2a06708af291fc43f48d618c92676f82c0 Mon Sep 17 00:00:00 2001 From: qer Date: Fri, 30 Oct 2026 03:14:49 +0800 Subject: [PATCH 04/13] feat(transcript): list the lost tasks in the lost task notice The notice for background tasks that lost contact when the app exited now carries lostTasks, one { taskId, description, kind } entry per task line of the reminder, so clients can show which tasks were lost without displaying the model-facing reminder text. --- docs/en/reference/server-api.md | 2 +- docs/zh/reference/server-api.md | 2 +- packages/transcript/src/history/groupTurns.ts | 12 +++++++++- packages/transcript/test/layers.test.ts | 23 +++++++++++++++++-- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/docs/en/reference/server-api.md b/docs/en/reference/server-api.md index a5456cc2b81..82884ccf1a9 100644 --- a/docs/en/reference/server-api.md +++ b/docs/en/reference/server-api.md @@ -2374,7 +2374,7 @@ Three global lifecycle events keep a cross-workspace overview fresh without poll Events also split into durable and volatile: durable events carry a strictly increasing `seq`, are journaled, and can be replayed; volatile events (the `*.delta` family, `tool.progress`, `shell.*`, and similar) are marked `volatile: true` and never replayed. When consuming a volatile text stream, compare `offset` (the cumulative character offset within the turn) against your locally accumulated text: below the local length means a duplicate frame; above means a gap that needs snapshot recovery. -Each agent's running tasks — shell commands in the foreground and background, subagents (swarm members included), and other asynchronous work — are synced by two flat frames, `task.list` and `task`, never replayed like `interaction`. When a connection starts receiving an agent's tasks (an agent whose grade in `subscribe_v2` is not `off` and that it was not receiving yet), it first gets `{ type: "task.list", session_id, agent_id, tasks }`, where `tasks` is every task the agent is running right now and replaces the client's list; after that it gets `{ type: "task", session_id, agent_id, event_created_at, ...task state }` on every change. An agent that joins the session later starts directly with `task` frames. The task state is `{ task_id, kind: "shell" | "subagent" | "other", status: "running" | "ended", outcome?, phase?, detached, description?, child_agent_id?, parent_tool_call_id?, swarm_index?, command?, started_at?, ended_at?, result_summary?, error?, state_reason?, model?, thinking? }`, and every frame carries the full state. `detached` means the tool call that started the task no longer waits for it, so it runs in the background; `outcome` (ended tasks only) is `completed` / `failed` / `timed_out` / `killed` / `lost`; `phase` (running subagents only) is `working` / `suspended`, with the reason in `state_reason`; `child_agent_id` is a subagent task's agent id; `swarm_index` is a swarm member's position in its swarm; `command` is a shell task's command. When a task ends, its last `task` frame (`status: "ended"`) is sent and it no longer appears in `task.list`: the outcome of an ended task lives in the session history (tool results and task notifications), and `task.watch` on it replies with its exact final state. Background tasks lost to a server restart show up in the transcript as a `notice` marker with `level: "warning"`. Tasks are independent of the transcript: a `reset` op inside `transcript.ops` after an undo does not touch tasks. Task output streams separately as `{ type: "task.output", session_id, agent_id, task_id, offset, text }`, where `offset` is the UTF-8 byte offset of `text` in the task's output. +Each agent's running tasks — shell commands in the foreground and background, subagents (swarm members included), and other asynchronous work — are synced by two flat frames, `task.list` and `task`, never replayed like `interaction`. When a connection starts receiving an agent's tasks (an agent whose grade in `subscribe_v2` is not `off` and that it was not receiving yet), it first gets `{ type: "task.list", session_id, agent_id, tasks }`, where `tasks` is every task the agent is running right now and replaces the client's list; after that it gets `{ type: "task", session_id, agent_id, event_created_at, ...task state }` on every change. An agent that joins the session later starts directly with `task` frames. The task state is `{ task_id, kind: "shell" | "subagent" | "other", status: "running" | "ended", outcome?, phase?, detached, description?, child_agent_id?, parent_tool_call_id?, swarm_index?, command?, started_at?, ended_at?, result_summary?, error?, state_reason?, model?, thinking? }`, and every frame carries the full state. `detached` means the tool call that started the task no longer waits for it, so it runs in the background; `outcome` (ended tasks only) is `completed` / `failed` / `timed_out` / `killed` / `lost`; `phase` (running subagents only) is `working` / `suspended`, with the reason in `state_reason`; `child_agent_id` is a subagent task's agent id; `swarm_index` is a swarm member's position in its swarm; `command` is a shell task's command. When a task ends, its last `task` frame (`status: "ended"`) is sent and it no longer appears in `task.list`: the outcome of an ended task lives in the session history (tool results and task notifications), and `task.watch` on it replies with its exact final state. Background tasks lost to a server restart show up in the transcript as a `notice` marker with `level: "warning"` whose `lostTasks` lists each lost task as `{ taskId, description, kind }`. Tasks are independent of the transcript: a `reset` op inside `transcript.ops` after an undo does not touch tasks. Task output streams separately as `{ type: "task.output", session_id, agent_id, task_id, offset, text }`, where `offset` is the UTF-8 byte offset of `text` in the task's output. ### Reconnect and recovery diff --git a/docs/zh/reference/server-api.md b/docs/zh/reference/server-api.md index 3b1577068e7..034b95df5dd 100644 --- a/docs/zh/reference/server-api.md +++ b/docs/zh/reference/server-api.md @@ -2377,7 +2377,7 @@ locator 寻址的目录(脱敏配置),外加对每个 OAuth 候选的批 事件另分持久与易失两种:持久事件带严格递增的 `seq`,落盘并可回放;易失事件(各 `*.delta`、`tool.progress`、`shell.*` 等)标 `volatile: true`,不回放。消费易失文本流时用 `offset`(该轮次内的累计字符偏移)与本地已累积文本比对:小于本地长度说明是重复帧,大于说明有缺漏、需走快照恢复。 -每个 agent 正在跑的任务——Shell 命令(前台与后台)、subagent(含 swarm 成员)及其他异步工作——由 `task.list` 与 `task` 两种平铺帧同步,和 `interaction` 一样不回放。连接开始接收某个 agent 的任务时(`subscribe_v2` 中粒度非 `off` 且此前未接收的 agent),先收到一帧 `{ type: "task.list", session_id, agent_id, tasks }`,`tasks` 是该 agent 此刻正在跑的全部任务,客户端用它整份替换本地列表;之后每次变化收到一帧 `{ type: "task", session_id, agent_id, event_created_at, ...任务状态 }`,之后才加入会话的 agent 直接从 `task` 帧开始。任务状态为 `{ task_id, kind: "shell" | "subagent" | "other", status: "running" | "ended", outcome?, phase?, detached, description?, child_agent_id?, parent_tool_call_id?, swarm_index?, command?, started_at?, ended_at?, result_summary?, error?, state_reason?, model?, thinking? }`,每帧都携带完整状态。`detached` 表示发起它的工具调用已不再等它,即后台任务;`outcome`(仅已结束的任务)为 `completed` / `failed` / `timed_out` / `killed` / `lost`;`phase`(仅运行中的 subagent)为 `working` / `suspended`,挂起原因在 `state_reason`;`child_agent_id` 是 subagent 任务的 agent id;`swarm_index` 是 swarm 成员在其 swarm 中的序号;`command` 是 Shell 任务的命令。任务结束时推送它最后一帧 `task`(`status: "ended"`),之后不再出现在 `task.list` 里:已结束任务的结果在会话历史中(工具结果与完成通知),需要精确的最终状态时对它发送 `task.watch`,应答带有其状态。服务端重启后失联的后台任务会以 `level: "warning"` 的 `notice` marker 出现在转录里。任务与转录互相独立:`transcript.ops` 里因撤销产生的 `reset` 操作不影响任务。任务输出单独推送,形状为 `{ type: "task.output", session_id, agent_id, task_id, offset, text }`,`offset` 是 `text` 在任务输出中的 UTF-8 字节偏移。 +每个 agent 正在跑的任务——Shell 命令(前台与后台)、subagent(含 swarm 成员)及其他异步工作——由 `task.list` 与 `task` 两种平铺帧同步,和 `interaction` 一样不回放。连接开始接收某个 agent 的任务时(`subscribe_v2` 中粒度非 `off` 且此前未接收的 agent),先收到一帧 `{ type: "task.list", session_id, agent_id, tasks }`,`tasks` 是该 agent 此刻正在跑的全部任务,客户端用它整份替换本地列表;之后每次变化收到一帧 `{ type: "task", session_id, agent_id, event_created_at, ...任务状态 }`,之后才加入会话的 agent 直接从 `task` 帧开始。任务状态为 `{ task_id, kind: "shell" | "subagent" | "other", status: "running" | "ended", outcome?, phase?, detached, description?, child_agent_id?, parent_tool_call_id?, swarm_index?, command?, started_at?, ended_at?, result_summary?, error?, state_reason?, model?, thinking? }`,每帧都携带完整状态。`detached` 表示发起它的工具调用已不再等它,即后台任务;`outcome`(仅已结束的任务)为 `completed` / `failed` / `timed_out` / `killed` / `lost`;`phase`(仅运行中的 subagent)为 `working` / `suspended`,挂起原因在 `state_reason`;`child_agent_id` 是 subagent 任务的 agent id;`swarm_index` 是 swarm 成员在其 swarm 中的序号;`command` 是 Shell 任务的命令。任务结束时推送它最后一帧 `task`(`status: "ended"`),之后不再出现在 `task.list` 里:已结束任务的结果在会话历史中(工具结果与完成通知),需要精确的最终状态时对它发送 `task.watch`,应答带有其状态。服务端重启后失联的后台任务会以 `level: "warning"` 的 `notice` marker 出现在转录里,其 `lostTasks` 逐个列出失联任务 `{ taskId, description, kind }`。任务与转录互相独立:`transcript.ops` 里因撤销产生的 `reset` 操作不影响任务。任务输出单独推送,形状为 `{ type: "task.output", session_id, agent_id, task_id, offset, text }`,`offset` 是 `text` 在任务输出中的 UTF-8 字节偏移。 ### 断线恢复 diff --git a/packages/transcript/src/history/groupTurns.ts b/packages/transcript/src/history/groupTurns.ts index fd7553fb7a6..6d32f058382 100644 --- a/packages/transcript/src/history/groupTurns.ts +++ b/packages/transcript/src/history/groupTurns.ts @@ -145,6 +145,7 @@ interface StepDraft { const HIDDEN_USER_ORIGINS = new Set(['injection', 'system_trigger', 'retry']); const LOST_TASK_REMINDER_VARIANT = 'task_resume_termination'; +const LOST_TASK_LINE = /^- (\S+) "(.*)" \(([a-z_]+)\)/; const TURN_OPENING_SYSTEM_TRIGGERS = new Set(['goal_continuation', 'subagent']); const MARKER_USER_ORIGINS: Readonly> = { skill_activation: 'skill', @@ -389,7 +390,8 @@ export function groupMessagesIntoSnapshot( pushMarker('hook', userPromptSubmitHookMarkerPayload(part)); } if (isLostTaskReminder(message)) { - pushMarker('notice', { level: 'warning', message: reminderText(textOf(message)) }); + const text = reminderText(textOf(message)); + pushMarker('notice', { level: 'warning', message: text, lostTasks: lostTasksOf(text) }); continue; } if (originKind !== undefined && HIDDEN_USER_ORIGINS.has(originKind)) { @@ -686,6 +688,14 @@ function reminderText(text: string): string { return text.replace(/^\n/, '').replace(/\n<\/system-reminder>$/, ''); } +function lostTasksOf(text: string): { taskId: string; description: string; kind: string }[] { + return text.split('\n').flatMap((line) => { + const match = LOST_TASK_LINE.exec(line); + if (match === null) return []; + return [{ taskId: match[1]!, description: match[2]!, kind: match[3]! }]; + }); +} + function textOf(message: HistoryMessage): string { return (message.content ?? []) .filter((part): part is { readonly type: 'text'; readonly text: string } => part.type === 'text' && 'text' in part) diff --git a/packages/transcript/test/layers.test.ts b/packages/transcript/test/layers.test.ts index 1c7c077dcf1..327607dc4ca 100644 --- a/packages/transcript/test/layers.test.ts +++ b/packages/transcript/test/layers.test.ts @@ -1478,7 +1478,19 @@ describe('groupMessagesIntoSnapshot (cold path)', () => { { role: 'assistant', content: [{ type: 'text', text: 'started' }], toolCalls: [] }, { role: 'user', - content: [{ type: 'text', text: '\nThe user exited the application.\n- bash-1: npm run dev\n' }], + content: [ + { + type: 'text', + text: [ + '', + 'The user exited the application.', + '- bash-1 "npm run dev" (bash)', + '- agent-2 "Review the "auth" module" (subagent) — resume it with Agent(resume="a2", prompt="Go on.").', + 'Check current state.', + '', + ].join('\n'), + }, + ], toolCalls: [], origin: { kind: 'injection', variant: 'task_resume_termination' } as { kind: string }, }, @@ -1494,8 +1506,15 @@ describe('groupMessagesIntoSnapshot (cold path)', () => { expect(markers).toHaveLength(1); expect(markers[0]).toMatchObject({ marker: 'notice', - payload: { level: 'warning', message: 'The user exited the application.\n- bash-1: npm run dev' }, + payload: { + level: 'warning', + lostTasks: [ + { taskId: 'bash-1', description: 'npm run dev', kind: 'bash' }, + { taskId: 'agent-2', description: 'Review the "auth" module', kind: 'subagent' }, + ], + }, }); + expect((markers[0]?.payload as { message: string }).message).toMatch(/^The user exited the application\.\n/); }); it('opens a task turn for a notification with a task-origin turn.started in the wire', () => { From 15304e5bcfb8ad1eb04b68847ca539aaae87df59 Mon Sep 17 00:00:00 2001 From: qer Date: Fri, 30 Oct 2026 04:10:00 +0800 Subject: [PATCH 05/13] chore(agent-core): regenerate the state manifest for the subagent task fields SubagentTaskInfo gained the optional swarmIndex, stateReason and resultSummary fields; only foreground swarm member tasks set them, and foreground tasks are never written to disk, so stored task records keep their shape. --- packages/agent-core-v2/docs/state-manifest.d.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/agent-core-v2/docs/state-manifest.d.ts b/packages/agent-core-v2/docs/state-manifest.d.ts index 35b743a587d..e57e79483a4 100644 --- a/packages/agent-core-v2/docs/state-manifest.d.ts +++ b/packages/agent-core-v2/docs/state-manifest.d.ts @@ -1848,6 +1848,9 @@ export interface AgentStateSnapshot { readonly model?: string; readonly thinkingEffort?: string; readonly stopCode?: string; + readonly swarmIndex?: number; + readonly stateReason?: string; + readonly resultSummary?: string; readonly taskId: string; readonly description: string; readonly status: /* AgentTaskStatus — packages/agent-core-v2/src/agent/task/types.ts */ 'completed' | 'failed' | 'running' | 'timed_out' | 'killed' | 'lost'; @@ -1900,6 +1903,9 @@ export interface AgentStateSnapshot { readonly model?: string; readonly thinkingEffort?: string; readonly stopCode?: string; + readonly swarmIndex?: number; + readonly stateReason?: string; + readonly resultSummary?: string; readonly taskId: string; readonly description: string; readonly status: /* AgentTaskStatus — packages/agent-core-v2/src/agent/task/types.ts */ 'completed' | 'failed' | 'running' | 'timed_out' | 'killed' | 'lost'; From 052c927a6713231e2808e00919d8ddd0a01fa54f Mon Sep 17 00:00:00 2001 From: qer Date: Fri, 30 Oct 2026 13:35:14 +0800 Subject: [PATCH 06/13] chore: add changesets for live task sync and the REST task API removal --- .changeset/remove-rest-task-api.md | 5 +++++ .changeset/task-output-watch.md | 5 +++++ .changeset/task-ws-events.md | 5 +++++ 3 files changed, 15 insertions(+) create mode 100644 .changeset/remove-rest-task-api.md create mode 100644 .changeset/task-output-watch.md create mode 100644 .changeset/task-ws-events.md diff --git a/.changeset/remove-rest-task-api.md b/.changeset/remove-rest-task-api.md new file mode 100644 index 00000000000..4a26018ce0f --- /dev/null +++ b/.changeset/remove-rest-task-api.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Remove the REST task endpoints (`GET /sessions/{id}/tasks`, `GET /sessions/{id}/tasks/{task_id}`, and the `:cancel` / `:detach` actions); use the WebSocket task frames and the `task.stop` / `task.background` requests instead. diff --git a/.changeset/task-output-watch.md b/.changeset/task-output-watch.md new file mode 100644 index 00000000000..3aa460ffa74 --- /dev/null +++ b/.changeset/task-output-watch.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Stream a task's output over the server's WebSocket with `task.watch` / `task.unwatch`, and stop a task or move it to the background with `task.stop` / `task.background`. diff --git a/.changeset/task-ws-events.md b/.changeset/task-ws-events.md new file mode 100644 index 00000000000..7bbb9571d73 --- /dev/null +++ b/.changeset/task-ws-events.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Send each agent's running tasks over the server WebSocket as a `task.list` frame followed by a `task` frame on every change; transcripts and the REST transcript endpoints no longer include tasks. From bfab98e9210530ae0991c3e9561c7f9465ad4682 Mon Sep 17 00:00:00 2001 From: qer Date: Fri, 30 Oct 2026 14:10:09 +0800 Subject: [PATCH 07/13] fix(agent-core): stop swarm members one at a time and keep them in the foreground Stopping one swarm member's task aborted the controller shared by the whole swarm, so every active and queued member was cancelled. Each member now runs on its own signal that follows the swarm's, and the batch takes the swarm signal from its options instead of from the first task, so a stop cancels only that member and the batch goes on with the rest. A swarm member could also be "moved to the background": the AgentSwarm tool waits for the whole swarm and never observes a member's foreground release, so the member was marked detached, persisted, and recorded as a background task while the tool call kept waiting. Members are now registered as not detachable, and detach leaves such a task in the foreground. Member tasks also carry the model and thinking effort: from the spawn plan while queued, and from the child agent's profile once the attempt starts, which also covers resumed members. --- packages/agent-core-v2/src/agent/task/task.ts | 1 + .../src/agent/task/taskService.ts | 2 + .../features/swarm/session/agentRunBatch.ts | 9 ++- .../swarm/session/sessionSwarmService.ts | 15 +++- .../features/swarm/session/swarmMemberTask.ts | 42 +++++++---- .../test/agent/task/taskManager.test.ts | 11 +++ .../test/features/swarm/sessionSwarm.test.ts | 72 ++++++++++++++++++- 7 files changed, 129 insertions(+), 23 deletions(-) diff --git a/packages/agent-core-v2/src/agent/task/task.ts b/packages/agent-core-v2/src/agent/task/task.ts index 00410258107..7fa38f3d37c 100644 --- a/packages/agent-core-v2/src/agent/task/task.ts +++ b/packages/agent-core-v2/src/agent/task/task.ts @@ -43,6 +43,7 @@ export interface AgentTaskOutputTail { export interface RegisterAgentTaskOptions { readonly detached?: boolean; + readonly detachable?: boolean; readonly timeoutMs?: number; readonly detachTimeoutMs?: number; readonly autoBackgroundOnTimeout?: boolean; diff --git a/packages/agent-core-v2/src/agent/task/taskService.ts b/packages/agent-core-v2/src/agent/task/taskService.ts index f2f4c293a68..64e92f59f5d 100644 --- a/packages/agent-core-v2/src/agent/task/taskService.ts +++ b/packages/agent-core-v2/src/agent/task/taskService.ts @@ -363,6 +363,7 @@ export class AgentTaskService extends Disposable implements IAgentTaskService { const timeoutMs = options.timeoutMs ?? task.timeoutMs; const entryOptions: RegisterAgentTaskOptions = { detached, + detachable: options.detachable, timeoutMs, detachTimeoutMs: options.detachTimeoutMs, autoBackgroundOnTimeout: options.autoBackgroundOnTimeout, @@ -694,6 +695,7 @@ export class AgentTaskService extends Disposable implements IAgentTaskService { private detachEntry(entry: ManagedTask, viaTimeout: boolean): AgentTaskInfo | undefined { if (TERMINAL_STATUSES.has(entry.status)) return this.toInfo(entry); + if (entry.options.detachable === false) return this.toInfo(entry); const foregroundRelease = entry.foregroundRelease; if (foregroundRelease === undefined) return this.toInfo(entry); diff --git a/packages/agent-core-v2/src/features/swarm/session/agentRunBatch.ts b/packages/agent-core-v2/src/features/swarm/session/agentRunBatch.ts index 835ecee1066..a05e850ef8c 100644 --- a/packages/agent-core-v2/src/features/swarm/session/agentRunBatch.ts +++ b/packages/agent-core-v2/src/features/swarm/session/agentRunBatch.ts @@ -29,6 +29,8 @@ export interface AgentSpawnAttemptOptions extends AgentRunAttemptOptions { export type AgentRunAttemptHandle = { readonly agentId: string; readonly profileName: string; + readonly model?: string; + readonly thinkingEffort?: string; readonly completion: Promise<{ readonly result: string; readonly usage?: TokenUsage; @@ -101,7 +103,8 @@ type ActiveAttempt = { export type AgentRunBatchOptions = { readonly maxConcurrency?: number; - readonly onStarted?: (task: QueuedAgentRunTask, agentId: string) => void; + readonly signal?: AbortSignal; + readonly onStarted?: (task: QueuedAgentRunTask, handle: AgentRunAttemptHandle) => void; readonly onSettled?: (result: AgentRunResult) => void; }; @@ -145,7 +148,7 @@ export class AgentRunBatch { })); this.pending = [...this.states]; this.results = Array.from({ length: tasks.length }); - this.batchSignal = tasks.find((task) => task.signal !== undefined)?.signal; + this.batchSignal = options.signal; this.batchAbortListener = () => { this.controller.abort(this.batchSignal?.reason); if (isUserCancellation(this.batchSignal?.reason)) { @@ -314,7 +317,7 @@ export class AgentRunBatch { } attempt.state.agentId = handle.agentId; - this.options.onStarted?.(task, handle.agentId); + this.options.onStarted?.(task, handle); try { const completion = await handle.completion; return { diff --git a/packages/agent-core-v2/src/features/swarm/session/sessionSwarmService.ts b/packages/agent-core-v2/src/features/swarm/session/sessionSwarmService.ts index 5e7b881011a..51034e16f0f 100644 --- a/packages/agent-core-v2/src/features/swarm/session/sessionSwarmService.ts +++ b/packages/agent-core-v2/src/features/swarm/session/sessionSwarmService.ts @@ -91,14 +91,19 @@ export class SessionSwarmService implements ISessionSwarmService { const controller = new AbortController(); this.inFlight.set(callerAgentId, controller); const unlinks: Array<() => void> = []; + const memberControllers = new Map(); const linkedTasks: SessionSwarmTask[] = tasks.map((task) => { if (task.signal !== undefined) unlinks.push(linkAbortSignal(task.signal, controller)); - return { ...task, signal: controller.signal }; + const memberController = new AbortController(); + unlinks.push(linkAbortSignal(controller.signal, memberController)); + const linked = { ...task, signal: memberController.signal }; + memberControllers.set(linked, memberController); + return linked; }); const members = new SwarmMemberTasks( this.agentLifecycle.handleOf(callerAgentId)?.accessor.get(IAgentTaskService), linkedTasks, - (reason) => controller.abort(reason), + (member, reason) => memberControllers.get(member)?.abort(reason), ); const terminalized = new Set(); const terminalize: TerminalizeSubagent = (agentId, event) => { @@ -135,7 +140,8 @@ export class SessionSwarmService implements ISessionSwarmService { const maxConcurrency = resolveSwarmMaxConcurrency(); const promise = new AgentRunBatch(launcher, linkedTasks, { maxConcurrency, - onStarted: (task, agentId) => members.started(task, agentId), + signal: controller.signal, + onStarted: (task, handle) => members.started(task, handle), onSettled: (result) => members.settled(result), }).run(); void promise.then( @@ -261,9 +267,12 @@ export class SessionSwarmService implements ISessionSwarmService { signal: options.signal, terminalize, }); + const profile = child.accessor.get(IAgentProfileService); return { agentId, profileName, + model: profile.data().modelAlias, + thinkingEffort: profile.getEffectiveThinkingLevel(), completion: mirrored.then((r) => ({ result: r.summary, usage: r.usage, diff --git a/packages/agent-core-v2/src/features/swarm/session/swarmMemberTask.ts b/packages/agent-core-v2/src/features/swarm/session/swarmMemberTask.ts index d837705a312..8983728adba 100644 --- a/packages/agent-core-v2/src/features/swarm/session/swarmMemberTask.ts +++ b/packages/agent-core-v2/src/features/swarm/session/swarmMemberTask.ts @@ -2,6 +2,7 @@ import type { IAgentTaskService } from '#/agent/task/task'; import type { AgentTask, AgentTaskInfoBase, AgentTaskSettlement, AgentTaskSink } from '#/agent/task/types'; import type { SubagentTaskInfo } from '#/agent/tools/agent/subagent-task'; +import type { AgentRunAttemptHandle } from './agentRunBatch'; import type { SessionSwarmRunResult, SessionSwarmTask } from './sessionSwarm'; export class SwarmMemberTasks { @@ -10,22 +11,23 @@ export class SwarmMemberTasks { constructor( private readonly tasks: IAgentTaskService | undefined, members: readonly SessionSwarmTask[], - cancelSwarm: (reason: unknown) => void, + stopMember: (member: SessionSwarmTask, reason: unknown) => void, ) { if (tasks === undefined) return; for (const member of members) { - const task = new SwarmMemberTask(member, cancelSwarm); + const task = new SwarmMemberTask(member, (reason) => stopMember(member, reason)); try { - this.members.set(member, { task, taskId: tasks.registerTask(task, { detached: member.runInBackground }) }); + const taskId = tasks.registerTask(task, { detached: member.runInBackground, detachable: false }); + this.members.set(member, { task, taskId }); } catch { } } } - started(member: SessionSwarmTask, agentId: string): void { + started(member: SessionSwarmTask, handle: AgentRunAttemptHandle): void { const entry = this.members.get(member); if (entry === undefined) return; - entry.task.started(agentId); + entry.task.started(handle); this.tasks?.refresh(entry.taskId); } @@ -53,35 +55,44 @@ export class SwarmMemberTask implements AgentTask { readonly idPrefix = 'agent'; readonly description: string; private agentId: string | undefined; + private model: string | undefined; + private thinkingEffort: string | undefined; private stateReason: string | undefined; private resultSummary: string | undefined; private sink: AgentTaskSink | undefined; private result: SessionSwarmRunResult | undefined; private finish: (() => void) | undefined; - private readonly cancelOnAbort = (): void => { - this.cancelSwarm(this.sink?.signal.reason); + private readonly stopOnAbort = (): void => { + this.stop(this.sink?.signal.reason); }; constructor( private readonly member: SessionSwarmTask, - private readonly cancelSwarm: (reason: unknown) => void, + private readonly stop: (reason: unknown) => void, ) { this.description = member.description; - this.agentId = member.kind === 'resume' ? member.resumeAgentId : undefined; + if (member.kind === 'resume') { + this.agentId = member.resumeAgentId; + } else { + this.model = member.plan.model; + this.thinkingEffort = member.plan.thinking; + } } start(sink: AgentTaskSink): Promise { this.sink = sink; - if (sink.signal.aborted) this.cancelOnAbort(); - else sink.signal.addEventListener('abort', this.cancelOnAbort, { once: true }); + if (sink.signal.aborted) this.stopOnAbort(); + else sink.signal.addEventListener('abort', this.stopOnAbort, { once: true }); return new Promise((resolve) => { this.finish = resolve; if (this.result !== undefined) void this.settleWith(this.result); }); } - started(agentId: string): void { - this.agentId = agentId; + started(handle: AgentRunAttemptHandle): void { + this.agentId = handle.agentId; + this.model = handle.model ?? this.model; + this.thinkingEffort = handle.thinkingEffort ?? this.thinkingEffort; this.stateReason = undefined; } @@ -102,7 +113,8 @@ export class SwarmMemberTask implements AgentTask { agentId: this.agentId, subagentType: this.member.profileName, parentToolCallId: this.member.parentToolCallId, - model: this.member.kind === 'spawn' ? this.member.plan.model : undefined, + model: this.model, + thinkingEffort: this.thinkingEffort, swarmIndex: this.member.swarmIndex, stateReason: this.stateReason, resultSummary: this.resultSummary, @@ -111,7 +123,7 @@ export class SwarmMemberTask implements AgentTask { private async settleWith(result: SessionSwarmRunResult): Promise { const sink = this.sink!; - sink.signal.removeEventListener('abort', this.cancelOnAbort); + sink.signal.removeEventListener('abort', this.stopOnAbort); this.agentId = result.agentId ?? this.agentId; this.stateReason = undefined; this.resultSummary = result.result; diff --git a/packages/agent-core-v2/test/agent/task/taskManager.test.ts b/packages/agent-core-v2/test/agent/task/taskManager.test.ts index d3ae51b1ffc..e507d7bedea 100644 --- a/packages/agent-core-v2/test/agent/task/taskManager.test.ts +++ b/packages/agent-core-v2/test/agent/task/taskManager.test.ts @@ -599,6 +599,17 @@ describe('AgentTaskService', () => { }).toThrow('Too many background tasks are already running.'); }); + it('keeps a task registered as not detachable in the foreground when asked to detach', () => { + const { manager } = createAgentTaskService(); + const taskId = manager.registerTask(agentTask(new Promise(() => {}), 'swarm member'), { + detached: false, + detachable: false, + }); + + expect(manager.detach(taskId)).toMatchObject({ status: 'running', detached: false }); + expect(manager.getTask(taskId)).toMatchObject({ detached: false }); + }); + it('lists active tasks by default', () => { const { manager } = createAgentTaskService(); registerProcess(manager, pendingProcess().proc, 'sleep 60', 'task 1'); diff --git a/packages/agent-core-v2/test/features/swarm/sessionSwarm.test.ts b/packages/agent-core-v2/test/features/swarm/sessionSwarm.test.ts index 19560b611f7..bbb6fd4bfdf 100644 --- a/packages/agent-core-v2/test/features/swarm/sessionSwarm.test.ts +++ b/packages/agent-core-v2/test/features/swarm/sessionSwarm.test.ts @@ -1479,6 +1479,7 @@ describe('SessionSwarmService metadata compatibility', () => { const registered: Array<{ readonly task: AgentTask; readonly detached: boolean | undefined; + readonly detachable: boolean | undefined; settlement?: AgentTaskSettlement; readonly refreshed: Array; }> = []; @@ -1486,7 +1487,12 @@ describe('SessionSwarmService metadata compatibility', () => { task.toInfo({ taskId: 't', description: task.description, status: 'running', startedAt: 0, endedAt: null }) as SubagentTaskInfo; const callerTasks = { registerTask: (task: AgentTask, options?: RegisterAgentTaskOptions) => { - const entry: (typeof registered)[number] = { task, detached: options?.detached, refreshed: [] }; + const entry: (typeof registered)[number] = { + task, + detached: options?.detached, + detachable: options?.detachable, + refreshed: [], + }; registered.push(entry); void task.start({ signal: new AbortController().signal, @@ -1537,7 +1543,10 @@ describe('SessionSwarmService metadata compatibility', () => { await running; await vi.advanceTimersByTimeAsync(0); - expect(registered.map((entry) => entry.detached)).toEqual([false, false]); + expect(registered.map((entry) => [entry.detached, entry.detachable])).toEqual([ + [false, false], + [false, false], + ]); const [rl, peerTask] = registered; expect(rl!.refreshed.map((info) => info.stateReason)).toContain( 'Provider rate limit; subagent requeued for retry.', @@ -1549,6 +1558,8 @@ describe('SessionSwarmService metadata compatibility', () => { agentId: 'agent-rl', parentToolCallId: 'call_swarm', swarmIndex: 1, + model: 'kimi-test', + thinkingEffort: 'medium', resultSummary: 'recovered summary', }); expect(peerTask!.settlement).toEqual({ status: 'completed' }); @@ -1558,6 +1569,62 @@ describe('SessionSwarmService metadata compatibility', () => { } }); + it('stopping one member task cancels only that member', async () => { + agents['agent-stop'] = { labels: { parentAgentId: 'main' } }; + agents['agent-keep'] = { labels: { parentAgentId: 'main' } }; + handles.set('agent-stop', agentHandle('agent-stop', lifecycle, eventBus)); + handles.set('agent-keep', agentHandle('agent-keep', lifecycle, eventBus)); + const members: Array<{ readonly stop: AbortController; settlement?: AgentTaskSettlement }> = []; + const callerTasks = { + registerTask: (task: AgentTask) => { + const member: (typeof members)[number] = { stop: new AbortController() }; + members.push(member); + void task.start({ + signal: member.stop.signal, + appendOutput: () => {}, + settle: async (settlement) => { + member.settlement = settlement; + return true; + }, + }); + return `task-${members.length - 1}`; + }, + refresh: () => {}, + } as unknown as IAgentTaskService; + handles.set( + 'main', + agentHandle('main', lifecycle, eventBus, {}, new Map([[IAgentTaskService, callerTasks]])), + ); + const kept = createControlledPromise<{ summary: string }>(); + runAgent.mockImplementation((agent, _request, options) => { + options?.onReady?.(); + const agentId = (agent as AgentContext).agentId; + if (agentId === 'agent-keep') return { agentId, turn: {} as never, completion: kept }; + return { + agentId, + turn: {} as never, + completion: new Promise((_resolve, reject) => { + options?.signal.addEventListener('abort', () => reject(options.signal.reason), { once: true }); + }), + }; + }); + + const running = ix.get(ISessionSwarmService).run({ + callerAgentId: 'main', + tasks: [resumeSessionTask('agent-stop'), resumeSessionTask('agent-keep')], + }); + await vi.waitFor(() => expect(runAgent).toHaveBeenCalledTimes(2)); + members[0]!.stop.abort(userCancellationReason()); + kept.resolve({ summary: 'kept summary' }); + const results = await running; + + expect(results.map((result) => [result.agentId, result.status])).toEqual([ + ['agent-stop', 'aborted'], + ['agent-keep', 'completed'], + ]); + expect(members.map((member) => member.settlement?.status)).toEqual(['killed', 'completed']); + }); + it('emits subagent.cancelled for a rate-limit-suspended child when the batch is cancelled', async () => { vi.useFakeTimers(); try { @@ -2486,6 +2553,7 @@ function createMockAgentRunBatchRunner( })); return new AgentRunBatch(launcher, activeTasks as readonly QueuedAgentRunTask[], { maxConcurrency: options.maxConcurrency, + signal: runOptions?.signal, }).run(); }, attempts, From 0f46ec0a2e7ddfc8ec21045c41d511008dd4e2cf Mon Sep 17 00:00:00 2001 From: qer Date: Fri, 30 Oct 2026 14:10:13 +0800 Subject: [PATCH 08/13] fix(kap-server): refuse to move a task that stays in the foreground to the background task.background replied 0 even when the task could not leave the foreground, such as an AgentSwarm member. It now replies 40933 (TASK_NOT_DETACHABLE) with the task's unchanged state, and the API reference lists the code. --- docs/en/reference/server-api.md | 4 +-- docs/zh/reference/server-api.md | 4 +-- .../kap-server/src/protocol/error-codes.ts | 1 + .../src/transport/ws/v1/taskRequests.ts | 19 ++++++++++++-- .../kap-server/test/wsConnectionV1.test.ts | 26 ++++++++++++++++++- 5 files changed, 47 insertions(+), 7 deletions(-) diff --git a/docs/en/reference/server-api.md b/docs/en/reference/server-api.md index 82884ccf1a9..a57b4448a91 100644 --- a/docs/en/reference/server-api.md +++ b/docs/en/reference/server-api.md @@ -2349,9 +2349,9 @@ Clients send JSON frames `{ "type", "id"?, "payload" }`; every request frame get | `task.watch` | flat: `{ request_id, session_id, agent_id, task_id, since_offset?, tail_bytes? }` | Stream one task's output to this connection, no session subscription needed; `agent_id` is the agent that owns the task (the `agent_id` of its `task` frame), `since_offset` resumes after output the client already has, and `tail_bytes` caps the size of the first chunk | | `task.unwatch` | flat: `{ request_id, session_id, agent_id, task_id }` | Stop streaming that task's output | | `task.stop` | flat: `{ request_id, session_id, agent_id, task_id }` | Stop a task; an ended task just replies with its state | -| `task.background` | flat: `{ request_id, session_id, agent_id, task_id }` | Stop the starting tool call from waiting so the task keeps running in the background; a task already in the background or ended just replies with its state | +| `task.background` | flat: `{ request_id, session_id, agent_id, task_id }` | Stop the starting tool call from waiting so the task keeps running in the background; a task already in the background or ended just replies with its state, and a task its caller cannot stop waiting for, such as an AgentSwarm member, fails with `40933` | -`task.watch`, `task.unwatch`, `task.stop`, and `task.background` are flat: their fields sit next to `type` instead of under `payload`, `request_id` replaces `id`, and the reply is `{ "type": "response", "request_id", "code", "msg"?, "task"? }` instead of an ack; a frame without a readable `request_id` closes the socket with code 1008. On success, `task.watch`, `task.stop`, and `task.background` carry the task's current state in `task` (the `task` frame shape below, without `type`, `session_id`, `agent_id`, and `event_created_at`). Requests for the same task are handled one at a time in arrival order, so a `task.unwatch` sent while a `task.watch` is still pending takes effect after it; different tasks, and other control frames, never wait on each other. A successful `task.watch` replies with code 0, then sends the last `tail_bytes` of output (32 KiB by default, at most 1 MiB; only the part after `since_offset` when it falls inside that window) as one `task.output` frame, with empty `text` when there is nothing to send, followed by live output; ended tasks can be watched too. Errors: `40001` malformed frame (including an out-of-range `tail_bytes`), `40401` session not found, `40406` the agent has no such task, `50001` internal error, `50003` output unreadable. +`task.watch`, `task.unwatch`, `task.stop`, and `task.background` are flat: their fields sit next to `type` instead of under `payload`, `request_id` replaces `id`, and the reply is `{ "type": "response", "request_id", "code", "msg"?, "task"? }` instead of an ack; a frame without a readable `request_id` closes the socket with code 1008. On success, `task.watch`, `task.stop`, and `task.background` carry the task's current state in `task` (the `task` frame shape below, without `type`, `session_id`, `agent_id`, and `event_created_at`). Requests for the same task are handled one at a time in arrival order, so a `task.unwatch` sent while a `task.watch` is still pending takes effect after it; different tasks, and other control frames, never wait on each other. A successful `task.watch` replies with code 0, then sends the last `tail_bytes` of output (32 KiB by default, at most 1 MiB; only the part after `since_offset` when it falls inside that window) as one `task.output` frame, with empty `text` when there is nothing to send, followed by live output; ended tasks can be watched too. Errors: `40001` malformed frame (including an out-of-range `tail_bytes`), `40401` session not found, `40406` the agent has no such task, `40933` the task cannot be moved to the background (its reply still carries the task state), `50001` internal error, `50003` output unreadable. ### Events diff --git a/docs/zh/reference/server-api.md b/docs/zh/reference/server-api.md index 034b95df5dd..575252b9351 100644 --- a/docs/zh/reference/server-api.md +++ b/docs/zh/reference/server-api.md @@ -2352,9 +2352,9 @@ locator 寻址的目录(脱敏配置),外加对每个 OAuth 候选的批 | `task.watch` | 平铺:`{ request_id, session_id, agent_id, task_id, since_offset?, tail_bytes? }` | 把某个任务的输出流式推送到本连接,无需订阅会话;`agent_id` 是任务所属的 agent(即该任务 `task` 帧上的 `agent_id`),`since_offset` 从客户端已有输出之后续传,`tail_bytes` 是首段输出最多包含的字节数 | | `task.unwatch` | 平铺:`{ request_id, session_id, agent_id, task_id }` | 停止推送该任务的输出 | | `task.stop` | 平铺:`{ request_id, session_id, agent_id, task_id }` | 停止任务;已结束的任务直接应答其状态 | -| `task.background` | 平铺:`{ request_id, session_id, agent_id, task_id }` | 让发起任务的工具调用不再等它,任务转入后台继续运行;已在后台或已结束时直接应答其状态 | +| `task.background` | 平铺:`{ request_id, session_id, agent_id, task_id }` | 让发起任务的工具调用不再等它,任务转入后台继续运行;已在后台或已结束时直接应答其状态,发起方无法不再等它的任务(例如 AgentSwarm 的成员)回复 `40933` | -`task.watch`、`task.unwatch`、`task.stop` 与 `task.background` 是平铺帧:字段与 `type` 并列而不放在 `payload` 下,用 `request_id` 代替 `id`,应答为 `{ "type": "response", "request_id", "code", "msg"?, "task"? }` 而不是 ack;缺少可读的 `request_id` 时服务端以关闭码 1008 断开连接。`task.watch`、`task.stop` 与 `task.background` 成功时,`task` 是任务此刻的状态(形状见下文 `task` 帧,不含 `type`、`session_id`、`agent_id` 与 `event_created_at`)。同一个任务的请求按到达顺序逐个处理,因此在 `task.watch` 尚未应答时发出的 `task.unwatch` 会在它之后生效;不同任务之间、以及它们与其他控制帧之间互不等待。`task.watch` 成功时先应答 `code` 0,再用一个 `task.output` 帧发送最后 `tail_bytes` 字节的输出(默认 32 KiB,最大 1 MiB;`since_offset` 落在这段范围内时只发它之后的部分;没有可发送的输出时 `text` 为空),之后推送实时输出;已结束的任务同样可以 watch。错误:`40001` 帧格式错误(含 `tail_bytes` 超出范围),`40401` 会话不存在,`40406` 该 agent 下没有这个任务,`50001` 内部错误,`50003` 输出无法读取。 +`task.watch`、`task.unwatch`、`task.stop` 与 `task.background` 是平铺帧:字段与 `type` 并列而不放在 `payload` 下,用 `request_id` 代替 `id`,应答为 `{ "type": "response", "request_id", "code", "msg"?, "task"? }` 而不是 ack;缺少可读的 `request_id` 时服务端以关闭码 1008 断开连接。`task.watch`、`task.stop` 与 `task.background` 成功时,`task` 是任务此刻的状态(形状见下文 `task` 帧,不含 `type`、`session_id`、`agent_id` 与 `event_created_at`)。同一个任务的请求按到达顺序逐个处理,因此在 `task.watch` 尚未应答时发出的 `task.unwatch` 会在它之后生效;不同任务之间、以及它们与其他控制帧之间互不等待。`task.watch` 成功时先应答 `code` 0,再用一个 `task.output` 帧发送最后 `tail_bytes` 字节的输出(默认 32 KiB,最大 1 MiB;`since_offset` 落在这段范围内时只发它之后的部分;没有可发送的输出时 `text` 为空),之后推送实时输出;已结束的任务同样可以 watch。错误:`40001` 帧格式错误(含 `tail_bytes` 超出范围),`40401` 会话不存在,`40406` 该 agent 下没有这个任务,`40933` 该任务不能转入后台(应答仍带任务状态),`50001` 内部错误,`50003` 输出无法读取。 ### 事件 diff --git a/packages/kap-server/src/protocol/error-codes.ts b/packages/kap-server/src/protocol/error-codes.ts index f5e0641a249..2be1b1f8df8 100644 --- a/packages/kap-server/src/protocol/error-codes.ts +++ b/packages/kap-server/src/protocol/error-codes.ts @@ -62,6 +62,7 @@ export const ErrorCode = { MCP_OAUTH_FAILED: 40929, SESSION_OCCUPIED: 40931, SESSION_STALE: 40932, + TASK_NOT_DETACHABLE: 40933, APPROVAL_EXPIRED: 41001, QUESTION_EXPIRED: 41002, diff --git a/packages/kap-server/src/transport/ws/v1/taskRequests.ts b/packages/kap-server/src/transport/ws/v1/taskRequests.ts index a04e51f3184..6dd11d77405 100644 --- a/packages/kap-server/src/transport/ws/v1/taskRequests.ts +++ b/packages/kap-server/src/transport/ws/v1/taskRequests.ts @@ -90,7 +90,15 @@ export class TaskRequests { const background = taskBackgroundMessageSchema.safeParse(frame); if (background.success) { this.enqueue(watchKey(background.data), () => - this.command(requestId, background.data, async (tasks, taskId) => tasks.detach(taskId)), + this.command( + requestId, + background.data, + async (tasks, taskId) => tasks.detach(taskId), + (info) => + info.status === 'running' && info.detached === false + ? { code: ErrorCode.TASK_NOT_DETACHABLE, msg: 'task cannot be moved to the background' } + : undefined, + ), ); return; } @@ -190,6 +198,7 @@ export class TaskRequests { requestId: string, request: TaskAddress, run: (tasks: IAgentTaskService, taskId: string) => Promise, + refuse?: (info: AgentTaskInfo) => { readonly code: number; readonly msg: string } | undefined, ): Promise { if (this.disposed) return; try { @@ -203,7 +212,13 @@ export class TaskRequests { return; } const info = (await run(resolved.tasks, request.task_id)) ?? resolved.tasks.getTask(request.task_id); - this.sink.respond(requestId, ErrorCode.SUCCESS, undefined, info === undefined ? undefined : toTaskState(info)); + const state = info === undefined ? undefined : toTaskState(info); + const refusal = info === undefined ? undefined : refuse?.(info); + if (refusal !== undefined) { + this.sink.respond(requestId, refusal.code, refusal.msg, state); + return; + } + this.sink.respond(requestId, ErrorCode.SUCCESS, undefined, state); } catch { this.sink.respond(requestId, ErrorCode.INTERNAL_ERROR, 'internal error'); } diff --git a/packages/kap-server/test/wsConnectionV1.test.ts b/packages/kap-server/test/wsConnectionV1.test.ts index c6ea5c57149..b8e56d51348 100644 --- a/packages/kap-server/test/wsConnectionV1.test.ts +++ b/packages/kap-server/test/wsConnectionV1.test.ts @@ -657,11 +657,13 @@ describe('WsConnectionV1 task output', () => { failTailRead(): void; corruptTailRead(): void; withholdTailBytes(count: number): void; + refuseDetach(): void; } function makeTaskOutput(taskId: string, initial = '', ownerAgentId = 'main'): FakeTaskOutput { let bytes = Buffer.from(initial, 'utf-8'); const listeners = new Set<(chunk: AgentTaskOutputChunk) => void>(); + let detachable = true; let gate: Promise | undefined; let sourceGate: Promise | undefined; let sourceCalls = 0; @@ -690,7 +692,7 @@ describe('WsConnectionV1 task output', () => { }, detach: (id: string) => { if (id !== taskId) return undefined; - info = { ...info, detached: true }; + if (detachable) info = { ...info, detached: true }; return info; }, onDidAppendOutput: (listener: (chunk: AgentTaskOutputChunk) => void) => { @@ -750,6 +752,9 @@ describe('WsConnectionV1 task output', () => { failTailRead: () => { tailReadFails = true; }, + refuseDetach: () => { + detachable = false; + }, }; } @@ -1261,6 +1266,25 @@ describe('WsConnectionV1 task output', () => { conn.close(); }); + it('refuses to move a task to the background when it stays in the foreground', async () => { + const output = makeTaskOutput('agent-member'); + output.refuseDetach(); + const socket = new FakeSocket(); + const conn = makeConn(socket, { taskSource: output.source }); + + send(socket, { type: 'task.background', request_id: 'r1', session_id: 's1', agent_id: 'main', task_id: 'agent-member' }); + await vi.waitFor(() => expect(taskFrames(socket)).toHaveLength(1)); + + expect(taskFrames(socket)[0]).toMatchObject({ + type: 'response', + request_id: 'r1', + code: 40933, + msg: 'task cannot be moved to the background', + task: { status: 'running', detached: false }, + }); + conn.close(); + }); + it('closes the connection with 1008 when a task command has no readable request_id', async () => { const output = makeTaskOutput('bash-k3f9x2ab'); const socket = new FakeSocket(); From 0114196896e377a3f7224388612fde88a475c6b1 Mon Sep 17 00:00:00 2001 From: qer Date: Fri, 30 Oct 2026 14:21:31 +0800 Subject: [PATCH 09/13] chore: sync web dist from code-app Built from MoonshotAI/kimi-code-app#1057, which reads tasks from the task.list / task frames and sends task.stop / task.background, so the bundled web UI matches the task protocol of this change. code-app: 3be5e316864f3843c2c868ec8ca388d6b99682e5 --- .changeset/web-interaction-polish.md | 5 + ...-DnNeGjd2.js => CodeBlockNode-CSMhG5XN.js} | 4 +- .../assets/NotoSansSC_wght_-BkPpiACN.woff2 | Bin 7782876 -> 0 bytes ...k-De4sXzpb.js => PreCodeBlock-B0I0Xb-g.js} | 2 +- ...hibstedGrotesk-Italic_wght_-DjkBGo1z.woff2 | Bin 75148 -> 0 bytes .../SchibstedGrotesk_wght_-DIzGrWVg.woff2 | Bin 70716 -> 0 bytes ...ooltip-CBoeS4mO.js => Tooltip-BdVJmbCS.js} | 2 +- ...7C.js => abnfDiagram-VRR7QNED-Bp1GCaU0.js} | 2 +- .../{arc-CBqyenpI.js => arc-Bj9lx7i-.js} | 2 +- ... architectureDiagram-ZJ3FMSHR-DYVSQUGS.js} | 2 +- ...u.js => blockDiagram-677ZJIJ3-DO9oHB0K.js} | 2 +- ...AX4-.js => c4Diagram-LMCZKHZV-bc6tUA9M.js} | 2 +- .../dist-web/assets/channel-C4EMkO7C.js | 1 - .../dist-web/assets/channel-ClURS_Ea.js | 1 + ...BG6es7i7.js => chunk-2Q5K7J3B-CsbB_ntD.js} | 2 +- ...CYFkA-hP.js => chunk-32BRIVSS-D7MwdJ6p.js} | 2 +- ...BRm-UcvZ.js => chunk-5VM5RSS4-KvcLaolS.js} | 2 +- ...DDKia6Wx.js => chunk-EX3LRPZG-Ca4RnKF5.js} | 2 +- ...CLdUjjB6.js => chunk-JWPE2WC7-Uy-JXH5L.js} | 2 +- ...B_4qq8M2.js => chunk-MOJQB5TN-79D1ZDoJ.js} | 2 +- ...BBtlUlC2.js => chunk-RYQCIY6F-LXCkkGKn.js} | 2 +- ...hOjH0svY.js => chunk-V7JOEXUC-CAUfoXO7.js} | 2 +- ...CBSF7Uhy.js => chunk-VR4S4FIN-BCfgdJck.js} | 2 +- ...9kW4cjFh.js => chunk-XXDRQBXY-9ESwJT2A.js} | 2 +- .../assets/classDiagram-OUVF2IWQ-DlYeIOPa.js | 1 + .../assets/classDiagram-OUVF2IWQ-VyyEPDVb.js | 1 - .../classDiagram-v2-EOCWNBFH-DlYeIOPa.js | 1 + .../classDiagram-v2-EOCWNBFH-VyyEPDVb.js | 1 - ...P.js => cose-bilkent-JH36ORCC-C27wp2zG.js} | 2 +- ...EG8-Vi.js => cynefin-VYW2F7L2-sdlx8CZZ.js} | 2 +- ...js => cynefinDiagram-TSTJHNR4-aQDF1JgA.js} | 2 +- ...D0TIaws6.js => dagre-VKFMJZFB-Vc-B2eEU.js} | 2 +- ...0uLCEZ.js => diagram-FQU43EPY-D0ZDEiAY.js} | 2 +- ...2riSBI.js => diagram-G47NLZAW-CL_7U8L-.js} | 2 +- ...8Wun3S.js => diagram-NH7WQ7WH-DeF1oeZo.js} | 2 +- ...DFynlc.js => diagram-OA4YK3LP-Dlq9spBB.js} | 2 +- ...DdsmaR.js => diagram-WEI45ONY-5HFE2xcH.js} | 2 +- ...AN.js => ebnfDiagram-CCIWWBDH-BYdalVG6.js} | 2 +- ...eEan.js => erDiagram-Q63AITRT-CTuCLPvz.js} | 2 +- ...tY.js => flowDiagram-23GEKE2U-CWQhNajK.js} | 2 +- ...b.js => ganttDiagram-NO4QXBWP-BrVrp4hr.js} | 2 +- ...s => gitGraphDiagram-IHSO6WYX-DpTueDy9.js} | 2 +- .../{index-B68Y3p9W.js => index-B-yRsok1.js} | 2 +- .../dist-web/assets/index-C36ZPHMp.css | 1 - .../{index-CQcWT59y.js => index-D5ARlvSN.js} | 955 +++++++++--------- .../dist-web/assets/index-D7mWK2Vr.css | 1 + .../{index-BJ3eGrh9.js => index-Dyr-Aywg.js} | 2 +- ...ndex10-D0AEmZyD.js => index10-Be7ZYTqL.js} | 2 +- ...{index5-COtrqpTL.js => index5-Eh5q5P40.js} | 2 +- ...{index6-BidN6rkR.js => index6-DKcAxzlF.js} | 2 +- ...{index8-CkikbLH9.js => index8-ylX79BBb.js} | 2 +- ...{index9-6cg8BGrC.js => index9-CSEH1DYH.js} | 2 +- ...dk.js => infoDiagram-FWYZ7A6U-DxMuDDa-.js} | 2 +- ...s => ishikawaDiagram-FXEZZL3T-mAq1Zg_F.js} | 2 +- ...s-mono-cyrillic-wght-normal-D73BlboJ.woff2 | Bin 12108 -> 0 bytes ...ains-mono-greek-wght-normal-Bw9x6K1M.woff2 | Bin 9004 -> 0 bytes ...-mono-latin-ext-wght-normal-DBQx-q_a.woff2 | Bin 15196 -> 0 bytes ...ains-mono-latin-wght-normal-B9CIFXIH.woff2 | Bin 40404 -> 0 bytes ...mono-vietnamese-wght-normal-Bt-aOZkq.woff2 | Bin 7504 -> 0 bytes ...js => journeyDiagram-5HDEW3XC-P6tl-m9E.js} | 2 +- ...=> kanban-definition-HUTT4EX6-C1M_Km8C.js} | 2 +- ...{linear-eT45Pk4H.js => linear-C2Si-ZqU.js} | 2 +- ...eam-9V28iECG.js => markstream-lzEeGTij.js} | 4 +- ...e-BTdvNJFM.js => mermaid.core-Bt6qxO1C.js} | 8 +- ...> mindmap-definition-LN4V7U3C-CMov88RB.js} | 2 +- ...7js.js => pegDiagram-2B236MQR-DuuC9MIP.js} | 2 +- ...bBB.js => pieDiagram-ENE6RG2P-BwMZPzdp.js} | 2 +- ...s => quadrantDiagram-ABIIQ3AL-BxPU7BmQ.js} | 2 +- ...s => railroadDiagram-RFXS5EU6-DGNnWC6m.js} | 2 +- ...> requirementDiagram-TGXJPOKE-Cu0G6DAf.js} | 2 +- ....js => sankeyDiagram-HTMAVEWB-BuAOeOZG.js} | 2 +- ...s => sequenceDiagram-DBY2YBRQ-Dfb8hz9g.js} | 2 +- ...E0.js => sizeCapture-X5ZJPWSS-ipMrmqbW.js} | 2 +- ...Q.js => stateDiagram-2N3HPSRC-DROKNJEJ.js} | 2 +- .../stateDiagram-v2-6OUMAXLB-C3AYnqxK.js | 1 - .../stateDiagram-v2-6OUMAXLB-DWSLmgIV.js | 1 + ...mWxC.js => swimlanes-5IMT3BWC-Cb2tMVXV.js} | 4 +- .../swimlanesDiagram-G3AALYLV-BEPOtlbc.js | 8 + .../swimlanesDiagram-G3AALYLV-BX6FVy6W.js | 8 - ... timeline-definition-FHXFAJF6-Cgyv1RcE.js} | 2 +- .../assets/turnDiff.worker-BzC1nIn4.js | 43 + .../assets/turnDiff.worker-DLKMFgbh.js | 43 - ...nw.js => vennDiagram-L72KCM5P-CpCMJbkQ.js} | 2 +- ...js => vue.runtime.esm-bundler-BOwKmLzp.js} | 2 +- ...js => wardleyDiagram-EHGQE667-CIR08HG0.js} | 2 +- ...js => xychartDiagram-FW5EYKEG-Bm9jjOHl.js} | 2 +- apps/kimi-code/dist-web/index.html | 4 +- 87 files changed, 609 insertions(+), 603 deletions(-) create mode 100644 .changeset/web-interaction-polish.md rename apps/kimi-code/dist-web/assets/{CodeBlockNode-DnNeGjd2.js => CodeBlockNode-CSMhG5XN.js} (99%) delete mode 100644 apps/kimi-code/dist-web/assets/NotoSansSC_wght_-BkPpiACN.woff2 rename apps/kimi-code/dist-web/assets/{PreCodeBlock-De4sXzpb.js => PreCodeBlock-B0I0Xb-g.js} (99%) delete mode 100644 apps/kimi-code/dist-web/assets/SchibstedGrotesk-Italic_wght_-DjkBGo1z.woff2 delete mode 100644 apps/kimi-code/dist-web/assets/SchibstedGrotesk_wght_-DIzGrWVg.woff2 rename apps/kimi-code/dist-web/assets/{Tooltip-CBoeS4mO.js => Tooltip-BdVJmbCS.js} (98%) rename apps/kimi-code/dist-web/assets/{abnfDiagram-VRR7QNED-Id86ug7C.js => abnfDiagram-VRR7QNED-Bp1GCaU0.js} (85%) rename apps/kimi-code/dist-web/assets/{arc-CBqyenpI.js => arc-Bj9lx7i-.js} (98%) rename apps/kimi-code/dist-web/assets/{architectureDiagram-ZJ3FMSHR-BU3h4qH_.js => architectureDiagram-ZJ3FMSHR-DYVSQUGS.js} (99%) rename apps/kimi-code/dist-web/assets/{blockDiagram-677ZJIJ3-DVoda3uu.js => blockDiagram-677ZJIJ3-DO9oHB0K.js} (99%) rename apps/kimi-code/dist-web/assets/{c4Diagram-LMCZKHZV-DK8zAX4-.js => c4Diagram-LMCZKHZV-bc6tUA9M.js} (99%) delete mode 100644 apps/kimi-code/dist-web/assets/channel-C4EMkO7C.js create mode 100644 apps/kimi-code/dist-web/assets/channel-ClURS_Ea.js rename apps/kimi-code/dist-web/assets/{chunk-2Q5K7J3B-BG6es7i7.js => chunk-2Q5K7J3B-CsbB_ntD.js} (67%) rename apps/kimi-code/dist-web/assets/{chunk-32BRIVSS-CYFkA-hP.js => chunk-32BRIVSS-D7MwdJ6p.js} (96%) rename apps/kimi-code/dist-web/assets/{chunk-5VM5RSS4-BRm-UcvZ.js => chunk-5VM5RSS4-KvcLaolS.js} (83%) rename apps/kimi-code/dist-web/assets/{chunk-EX3LRPZG-DDKia6Wx.js => chunk-EX3LRPZG-Ca4RnKF5.js} (99%) rename apps/kimi-code/dist-web/assets/{chunk-JWPE2WC7-CLdUjjB6.js => chunk-JWPE2WC7-Uy-JXH5L.js} (71%) rename apps/kimi-code/dist-web/assets/{chunk-MOJQB5TN-B_4qq8M2.js => chunk-MOJQB5TN-79D1ZDoJ.js} (99%) rename apps/kimi-code/dist-web/assets/{chunk-RYQCIY6F-BBtlUlC2.js => chunk-RYQCIY6F-LXCkkGKn.js} (99%) rename apps/kimi-code/dist-web/assets/{chunk-V7JOEXUC-hOjH0svY.js => chunk-V7JOEXUC-CAUfoXO7.js} (99%) rename apps/kimi-code/dist-web/assets/{chunk-VR4S4FIN-CBSF7Uhy.js => chunk-VR4S4FIN-BCfgdJck.js} (87%) rename apps/kimi-code/dist-web/assets/{chunk-XXDRQBXY-9kW4cjFh.js => chunk-XXDRQBXY-9ESwJT2A.js} (72%) create mode 100644 apps/kimi-code/dist-web/assets/classDiagram-OUVF2IWQ-DlYeIOPa.js delete mode 100644 apps/kimi-code/dist-web/assets/classDiagram-OUVF2IWQ-VyyEPDVb.js create mode 100644 apps/kimi-code/dist-web/assets/classDiagram-v2-EOCWNBFH-DlYeIOPa.js delete mode 100644 apps/kimi-code/dist-web/assets/classDiagram-v2-EOCWNBFH-VyyEPDVb.js rename apps/kimi-code/dist-web/assets/{cose-bilkent-JH36ORCC-BJZkFZnP.js => cose-bilkent-JH36ORCC-C27wp2zG.js} (99%) rename apps/kimi-code/dist-web/assets/{cynefin-VYW2F7L2-CPEG8-Vi.js => cynefin-VYW2F7L2-sdlx8CZZ.js} (99%) rename apps/kimi-code/dist-web/assets/{cynefinDiagram-TSTJHNR4-Cx1H0a5W.js => cynefinDiagram-TSTJHNR4-aQDF1JgA.js} (98%) rename apps/kimi-code/dist-web/assets/{dagre-VKFMJZFB-D0TIaws6.js => dagre-VKFMJZFB-Vc-B2eEU.js} (97%) rename apps/kimi-code/dist-web/assets/{diagram-FQU43EPY-BJ0uLCEZ.js => diagram-FQU43EPY-D0ZDEiAY.js} (98%) rename apps/kimi-code/dist-web/assets/{diagram-G47NLZAW-Bo2riSBI.js => diagram-G47NLZAW-CL_7U8L-.js} (97%) rename apps/kimi-code/dist-web/assets/{diagram-NH7WQ7WH-DT8Wun3S.js => diagram-NH7WQ7WH-DeF1oeZo.js} (93%) rename apps/kimi-code/dist-web/assets/{diagram-OA4YK3LP-CDDFynlc.js => diagram-OA4YK3LP-Dlq9spBB.js} (96%) rename apps/kimi-code/dist-web/assets/{diagram-WEI45ONY-DRDdsmaR.js => diagram-WEI45ONY-5HFE2xcH.js} (95%) rename apps/kimi-code/dist-web/assets/{ebnfDiagram-CCIWWBDH-B-sMl9AN.js => ebnfDiagram-CCIWWBDH-BYdalVG6.js} (87%) rename apps/kimi-code/dist-web/assets/{erDiagram-Q63AITRT-BUUoeEan.js => erDiagram-Q63AITRT-CTuCLPvz.js} (99%) rename apps/kimi-code/dist-web/assets/{flowDiagram-23GEKE2U-BgRIGxtY.js => flowDiagram-23GEKE2U-CWQhNajK.js} (99%) rename apps/kimi-code/dist-web/assets/{ganttDiagram-NO4QXBWP-BPjlGhob.js => ganttDiagram-NO4QXBWP-BrVrp4hr.js} (99%) rename apps/kimi-code/dist-web/assets/{gitGraphDiagram-IHSO6WYX-CBqntniz.js => gitGraphDiagram-IHSO6WYX-DpTueDy9.js} (99%) rename apps/kimi-code/dist-web/assets/{index-B68Y3p9W.js => index-B-yRsok1.js} (99%) delete mode 100644 apps/kimi-code/dist-web/assets/index-C36ZPHMp.css rename apps/kimi-code/dist-web/assets/{index-CQcWT59y.js => index-D5ARlvSN.js} (53%) create mode 100644 apps/kimi-code/dist-web/assets/index-D7mWK2Vr.css rename apps/kimi-code/dist-web/assets/{index-BJ3eGrh9.js => index-Dyr-Aywg.js} (99%) rename apps/kimi-code/dist-web/assets/{index10-D0AEmZyD.js => index10-Be7ZYTqL.js} (99%) rename apps/kimi-code/dist-web/assets/{index5-COtrqpTL.js => index5-Eh5q5P40.js} (98%) rename apps/kimi-code/dist-web/assets/{index6-BidN6rkR.js => index6-DKcAxzlF.js} (98%) rename apps/kimi-code/dist-web/assets/{index8-CkikbLH9.js => index8-ylX79BBb.js} (99%) rename apps/kimi-code/dist-web/assets/{index9-6cg8BGrC.js => index9-CSEH1DYH.js} (99%) rename apps/kimi-code/dist-web/assets/{infoDiagram-FWYZ7A6U-i0sqzIdk.js => infoDiagram-FWYZ7A6U-DxMuDDa-.js} (67%) rename apps/kimi-code/dist-web/assets/{ishikawaDiagram-FXEZZL3T-CD2HXIn9.js => ishikawaDiagram-FXEZZL3T-mAq1Zg_F.js} (99%) delete mode 100644 apps/kimi-code/dist-web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 delete mode 100644 apps/kimi-code/dist-web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 delete mode 100644 apps/kimi-code/dist-web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 delete mode 100644 apps/kimi-code/dist-web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 delete mode 100644 apps/kimi-code/dist-web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 rename apps/kimi-code/dist-web/assets/{journeyDiagram-5HDEW3XC-DoXENIdm.js => journeyDiagram-5HDEW3XC-P6tl-m9E.js} (98%) rename apps/kimi-code/dist-web/assets/{kanban-definition-HUTT4EX6-Cn4uJR7z.js => kanban-definition-HUTT4EX6-C1M_Km8C.js} (99%) rename apps/kimi-code/dist-web/assets/{linear-eT45Pk4H.js => linear-C2Si-ZqU.js} (98%) rename apps/kimi-code/dist-web/assets/{markstream-9V28iECG.js => markstream-lzEeGTij.js} (99%) rename apps/kimi-code/dist-web/assets/{mermaid.core-BTdvNJFM.js => mermaid.core-Bt6qxO1C.js} (99%) rename apps/kimi-code/dist-web/assets/{mindmap-definition-LN4V7U3C-B8mfB2gJ.js => mindmap-definition-LN4V7U3C-CMov88RB.js} (98%) rename apps/kimi-code/dist-web/assets/{pegDiagram-2B236MQR-7cr_V7js.js => pegDiagram-2B236MQR-DuuC9MIP.js} (86%) rename apps/kimi-code/dist-web/assets/{pieDiagram-ENE6RG2P-BothDbBB.js => pieDiagram-ENE6RG2P-BwMZPzdp.js} (94%) rename apps/kimi-code/dist-web/assets/{quadrantDiagram-ABIIQ3AL-DSWGD1og.js => quadrantDiagram-ABIIQ3AL-BxPU7BmQ.js} (99%) rename apps/kimi-code/dist-web/assets/{railroadDiagram-RFXS5EU6-tMYfgW3B.js => railroadDiagram-RFXS5EU6-DGNnWC6m.js} (84%) rename apps/kimi-code/dist-web/assets/{requirementDiagram-TGXJPOKE-CloEciiT.js => requirementDiagram-TGXJPOKE-Cu0G6DAf.js} (99%) rename apps/kimi-code/dist-web/assets/{sankeyDiagram-HTMAVEWB-4xW6Sp9V.js => sankeyDiagram-HTMAVEWB-BuAOeOZG.js} (99%) rename apps/kimi-code/dist-web/assets/{sequenceDiagram-DBY2YBRQ-Bi6rVWH0.js => sequenceDiagram-DBY2YBRQ-Dfb8hz9g.js} (99%) rename apps/kimi-code/dist-web/assets/{sizeCapture-X5ZJPWSS-Do5KxOE0.js => sizeCapture-X5ZJPWSS-ipMrmqbW.js} (86%) rename apps/kimi-code/dist-web/assets/{stateDiagram-2N3HPSRC-CSBDJyOQ.js => stateDiagram-2N3HPSRC-DROKNJEJ.js} (96%) delete mode 100644 apps/kimi-code/dist-web/assets/stateDiagram-v2-6OUMAXLB-C3AYnqxK.js create mode 100644 apps/kimi-code/dist-web/assets/stateDiagram-v2-6OUMAXLB-DWSLmgIV.js rename apps/kimi-code/dist-web/assets/{swimlanes-5IMT3BWC-JtIWmWxC.js => swimlanes-5IMT3BWC-Cb2tMVXV.js} (99%) create mode 100644 apps/kimi-code/dist-web/assets/swimlanesDiagram-G3AALYLV-BEPOtlbc.js delete mode 100644 apps/kimi-code/dist-web/assets/swimlanesDiagram-G3AALYLV-BX6FVy6W.js rename apps/kimi-code/dist-web/assets/{timeline-definition-FHXFAJF6-DzJM0sQI.js => timeline-definition-FHXFAJF6-Cgyv1RcE.js} (99%) create mode 100644 apps/kimi-code/dist-web/assets/turnDiff.worker-BzC1nIn4.js delete mode 100644 apps/kimi-code/dist-web/assets/turnDiff.worker-DLKMFgbh.js rename apps/kimi-code/dist-web/assets/{vennDiagram-L72KCM5P-DuiLWTnw.js => vennDiagram-L72KCM5P-CpCMJbkQ.js} (99%) rename apps/kimi-code/dist-web/assets/{vue.runtime.esm-bundler-DrbcV_y0.js => vue.runtime.esm-bundler-BOwKmLzp.js} (98%) rename apps/kimi-code/dist-web/assets/{wardleyDiagram-EHGQE667-DaNV0jkS.js => wardleyDiagram-EHGQE667-CIR08HG0.js} (99%) rename apps/kimi-code/dist-web/assets/{xychartDiagram-FW5EYKEG-UOnbMUrX.js => xychartDiagram-FW5EYKEG-Bm9jjOHl.js} (99%) diff --git a/.changeset/web-interaction-polish.md b/.changeset/web-interaction-polish.md new file mode 100644 index 00000000000..60597384bc5 --- /dev/null +++ b/.changeset/web-interaction-polish.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +web: Improved interactions and fixed known bugs. diff --git a/apps/kimi-code/dist-web/assets/CodeBlockNode-DnNeGjd2.js b/apps/kimi-code/dist-web/assets/CodeBlockNode-CSMhG5XN.js similarity index 99% rename from apps/kimi-code/dist-web/assets/CodeBlockNode-DnNeGjd2.js rename to apps/kimi-code/dist-web/assets/CodeBlockNode-CSMhG5XN.js index 6232b23acd0..10121e58f66 100644 --- a/apps/kimi-code/dist-web/assets/CodeBlockNode-DnNeGjd2.js +++ b/apps/kimi-code/dist-web/assets/CodeBlockNode-CSMhG5XN.js @@ -1,5 +1,5 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/markstream-9V28iECG.js","assets/index-CQcWT59y.js","assets/index-C36ZPHMp.css"])))=>i.map(i=>d[i]); -import{bR as Ya,cd as Ga,bQ as aa,M as oa,bl as Ja,af as Wt,bY as Qa,ce as Xa,b$ as ra,aU as S,c0 as Za,c1 as Ka,c2 as eo,a0 as Jn,aD as ia,bE as A,az as lo,cf as Qn,ca as to,cg as no,aI as ua,aL as ul,s as rt,A as Xn,bJ as il,aY as $l,aw as nt,u as Zn,I as ao,bL as oo,v as U,au as it,t as At,bB as ro,bb as Yt,bk as sa,q as m,b7 as io,ch as uo,ci as so,cj as fo,ck as co,as as O,cl as vo,cm as mo,cn as ho,co as po,cp as go,cq as yo,cr as bo,cs as Kn,ct as ea,bO as wo,T as ko,cu as xo,b_ as So}from"./index-CQcWT59y.js";import{B as la,g as Co,Q as Mo}from"./PreCodeBlock-De4sXzpb.js";import{i as se,t as at}from"./safeRaf-DGuzXxDK.js";var ot=(C,b,M)=>new Promise((ve,ee)=>{var R=k=>{try{n(M.next(k))}catch(H){ee(H)}},he=k=>{try{n(M.throw(k))}catch(H){ee(H)}},n=k=>k.done?ve(k.value):Promise.resolve(k.value).then(R,he);n((M=M.apply(C,b)).next())});let Ut=!1,Hl=null,Ol=null,Dl=null;function Bo(){return ot(this,null,function*(){if(Dl)return Dl;Dl=ot(null,null,function*(){if(!Ol)try{if(Ol=(function(C){var b,M,ve;const ee=C,R=typeof ee?.createCodeBlockRuntime=="function"||typeof ee?.useMonaco=="function"?ee:C?.default,he=(b=R?.createCodeBlockRuntime)!=null?b:R?.useMonaco;return typeof he!="function"?null:{createCodeBlockRuntime:n=>he.call(R,n),detectLanguage:(M=R?.detectLanguage)==null?void 0:M.bind(R),preloadStreamDiffs:(ve=R?.preloadStreamDiffs)==null?void 0:ve.bind(R)}})(yield Ya(()=>import("./markstream-9V28iECG.js"),__vite__mapDeps([0,1,2]))),!Ol)return null}catch{return null}try{return yield(function(C){return ot(this,null,function*(){return Ut?void 0:Hl||(Hl=ot(null,null,function*(){const M=C?.preloadStreamDiffs;typeof M=="function"&&(yield M()),Ut=!0}).finally(()=>{Hl=null}),Hl)})})(Ol),Ga(),Ol}catch{return null}});try{return yield Dl}finally{Dl=null}})}var Po=Object.defineProperty,$o=Object.defineProperties,Ho=Object.getOwnPropertyDescriptors,ta=Object.getOwnPropertySymbols,Oo=Object.prototype.hasOwnProperty,Do=Object.prototype.propertyIsEnumerable,na=(C,b,M)=>b in C?Po(C,b,{enumerable:!0,configurable:!0,writable:!0,value:M}):C[b]=M,Ce=(C,b)=>{for(var M in b||(b={}))Oo.call(b,M)&&na(C,M,b[M]);if(ta)for(var M of ta(b))Do.call(b,M)&&na(C,M,b[M]);return C},Nl=(C,b)=>$o(C,Ho(b)),D=(C,b,M)=>new Promise((ve,ee)=>{var R=k=>{try{n(M.next(k))}catch(H){ee(H)}},he=k=>{try{n(M.throw(k))}catch(H){ee(H)}},n=k=>k.done?ve(k.value):Promise.resolve(k.value).then(R,he);n((M=M.apply(C,b)).next())});const No={class:"html-preview-frame__header"},Eo={class:"html-preview-frame__title"},Fo={class:"html-preview-frame__label"},To=["sandbox","srcdoc"],zo=aa(oa({__name:"HtmlPreviewFrame",props:{code:{},isDark:{type:Boolean},htmlPreviewAllowScripts:{type:Boolean},htmlPreviewSandbox:{},onClose:{type:Function},title:{}},setup(C){const b=C,M=import.meta!==void 0&&!1;let ve=null;const{t:ee}=ra(),R=m(()=>{const k=b.code||"",H=k.trim().toLowerCase();return H.startsWith(" +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/markstream-lzEeGTij.js","assets/index-D5ARlvSN.js","assets/index-D7mWK2Vr.css"])))=>i.map(i=>d[i]); +import{bR as Ya,cd as Ga,bQ as aa,M as oa,bl as Ja,af as Wt,bY as Qa,ce as Xa,b$ as ra,aU as S,c0 as Za,c1 as Ka,c2 as eo,a0 as Jn,aD as ia,bE as A,az as lo,cf as Qn,ca as to,cg as no,aI as ua,aL as ul,s as rt,A as Xn,bJ as il,aY as $l,aw as nt,u as Zn,I as ao,bL as oo,v as U,au as it,t as At,bB as ro,bb as Yt,bk as sa,q as m,b7 as io,ch as uo,ci as so,cj as fo,ck as co,as as O,cl as vo,cm as mo,cn as ho,co as po,cp as go,cq as yo,cr as bo,cs as Kn,ct as ea,bO as wo,T as ko,cu as xo,b_ as So}from"./index-D5ARlvSN.js";import{B as la,g as Co,Q as Mo}from"./PreCodeBlock-B0I0Xb-g.js";import{i as se,t as at}from"./safeRaf-DGuzXxDK.js";var ot=(C,b,M)=>new Promise((ve,ee)=>{var R=k=>{try{n(M.next(k))}catch(H){ee(H)}},he=k=>{try{n(M.throw(k))}catch(H){ee(H)}},n=k=>k.done?ve(k.value):Promise.resolve(k.value).then(R,he);n((M=M.apply(C,b)).next())});let Ut=!1,Hl=null,Ol=null,Dl=null;function Bo(){return ot(this,null,function*(){if(Dl)return Dl;Dl=ot(null,null,function*(){if(!Ol)try{if(Ol=(function(C){var b,M,ve;const ee=C,R=typeof ee?.createCodeBlockRuntime=="function"||typeof ee?.useMonaco=="function"?ee:C?.default,he=(b=R?.createCodeBlockRuntime)!=null?b:R?.useMonaco;return typeof he!="function"?null:{createCodeBlockRuntime:n=>he.call(R,n),detectLanguage:(M=R?.detectLanguage)==null?void 0:M.bind(R),preloadStreamDiffs:(ve=R?.preloadStreamDiffs)==null?void 0:ve.bind(R)}})(yield Ya(()=>import("./markstream-lzEeGTij.js"),__vite__mapDeps([0,1,2]))),!Ol)return null}catch{return null}try{return yield(function(C){return ot(this,null,function*(){return Ut?void 0:Hl||(Hl=ot(null,null,function*(){const M=C?.preloadStreamDiffs;typeof M=="function"&&(yield M()),Ut=!0}).finally(()=>{Hl=null}),Hl)})})(Ol),Ga(),Ol}catch{return null}});try{return yield Dl}finally{Dl=null}})}var Po=Object.defineProperty,$o=Object.defineProperties,Ho=Object.getOwnPropertyDescriptors,ta=Object.getOwnPropertySymbols,Oo=Object.prototype.hasOwnProperty,Do=Object.prototype.propertyIsEnumerable,na=(C,b,M)=>b in C?Po(C,b,{enumerable:!0,configurable:!0,writable:!0,value:M}):C[b]=M,Ce=(C,b)=>{for(var M in b||(b={}))Oo.call(b,M)&&na(C,M,b[M]);if(ta)for(var M of ta(b))Do.call(b,M)&&na(C,M,b[M]);return C},Nl=(C,b)=>$o(C,Ho(b)),D=(C,b,M)=>new Promise((ve,ee)=>{var R=k=>{try{n(M.next(k))}catch(H){ee(H)}},he=k=>{try{n(M.throw(k))}catch(H){ee(H)}},n=k=>k.done?ve(k.value):Promise.resolve(k.value).then(R,he);n((M=M.apply(C,b)).next())});const No={class:"html-preview-frame__header"},Eo={class:"html-preview-frame__title"},Fo={class:"html-preview-frame__label"},To=["sandbox","srcdoc"],zo=aa(oa({__name:"HtmlPreviewFrame",props:{code:{},isDark:{type:Boolean},htmlPreviewAllowScripts:{type:Boolean},htmlPreviewSandbox:{},onClose:{type:Function},title:{}},setup(C){const b=C,M=import.meta!==void 0&&!1;let ve=null;const{t:ee}=ra(),R=m(()=>{const k=b.code||"",H=k.trim().toLowerCase();return H.startsWith(" diff --git a/apps/kimi-code/dist-web/assets/NotoSansSC_wght_-BkPpiACN.woff2 b/apps/kimi-code/dist-web/assets/NotoSansSC_wght_-BkPpiACN.woff2 deleted file mode 100644 index 1be88dd0cb0bac6de5f42efa924bb70863abee46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7782876 zcmV)LK)JtnPew8T0RR91cEQ{L7XSbO59BZacEMQy0RR9100000000000000000000 z0000Pf(jaea2tfuVjR{kKS)+VQiJ6x24Fu^R6$gM9w#aQfs$n}Y!L_wgzl1DgXPL? z3x~;c05F5Fo)7^x0we>6$#4W91(9|KAPff=Bp8RpUd#XgC#g^+hkIudfxL&*Ok3TC z0Q_aCJGUWD`H})PENU~H05jcX9Zlm5KXL3TSc==Y-t~1oXeVOfOgy`o5ZuC z3lT+KSRpJ2ZjY&cXLpuy2s^1he4oyDne?JLzvNe@y@!KE$e}S{y4cVDZ~F`Q}+9MFe*a>y{r z3XDwGAjE8mE(;78`da!10uvciV7umAzt;_`Ri8$u6U31Ar(Q0fc>x@C1QjMa70uC{ zTp1xHFv1Ceha^+xOIZ|F^$_1N94ZCnX-{BFm~OFIYYG0i+S7`{Mp1{13;a3OhRqo= zBwrmJ1g8;8bwMQo4>1}X(A~%WPVbfs{(5@6Jt5}oi7`#w{pjF?zI$Z;HVn40I?AxW zk7xDw;90q?T;16Cd%I7~_CKTZgF6f+pPzsH{0`TD`ZYXqaK~SV&3_&Lbj~^J!sA3c z?}aa#h4a&75w_Fmbc!GQKFhK!YhLOs%W7ye+U<@dMvS&qiw1toT084nM7F-^8T#L? z>PtRb<^QOw7<{9U&rVg$pO=Rl)o*p2e{m3^&hka__v*pniD<U1!F}b zt%J>0#7+dpzU%s7%;ba6QRpOe7P<&sg>FK3@t~Ax8hBzczH0B0-XHh&>;C)ydH&M= z-0OEAhsexCn4}RJfK6O;t?quQe<8Ljeh~vSryUS+0>Od=2o?y0kYMqPT||{&L9I}6 zT3=Vyt?k;0)h7Sl_G>5Iyq*33Tsu6!HZOkGUB7pC+z}~o92^(q9bq5{s2EdJFmfuQ zu52)JPV71D>73EqOtd$hV#d^^y;YOZUiF;A#^W=5Kzd-kx@f1w{DElQllI&BEq0j#Fw@Zs}Kut z`iLW{NA<{b0wZdp$EX@1f>N^K`MLF0#DZzk}BhNlP?cw=t#x}u>g}kp*7j?^`LQ1frF%VHfMFj;F6ay6l6$6!pn~SWw z?QXZb-Q9M#yW0T2&VLv0!krjn;@)FHFIb8dn?)J3WRYrVGZy*fO2rJD$c=I4%r|{b z)N$dQJcq+M+?_a_h5LQx08m1!dakU%5lNOzs%-13Dl|$|wUjRlIfT%AnGHezxZm64 zrN*(XC4`TZp7s_OPytDgLa}hm5Y-y*_uNhIgn-o(>Q!VE5N`l&ocQ&H0Y+tz2l%|t zKZTjsX=ZAratbmP_ubW>-bt$p_sgr=6#0|i9H9Z>L1_ficJuDnID`RO8;X6{j?vrz zO=m}&BBiC`X(N8QBzL*H_(krLi)&3%Q7DBLe7F@DW@8&L*ccN-HU~CmuFPJ?@ci8R zz=E##FwZ`-R^&!*q{K)iS^@J=DKSw26~V|;5Q|eV8UqlO0V71hIML(A-r}?Ww^3w^ zSj}$F{e=XHv3N!DLRy3-P$3&Rd}%7AfP{OYRGnjap6A}(9ye@G8ywMC^uYt{eB&(O zPk|-a=?8vp{lPY1L@n8WZzFTiDkEB=LJ>hw5eaP&^ejZe3{=z;3vJ|1&{I#jGY=F! zF=5nTfS~H+b<&N1Lpbs1xEj9x=cih!YyQab-v*2{HL6;pp-1qlv_aH`l+ZvEkO(x; zYG{d8lV)qxJmC_5Prnu|Z-^Sx8l7v)7qV*0pBHws@4lM)b?tKZE>~A|&&1J((a7+L z&kKCgNOlwhg&bmCtZUA8*pi7P#g?5&9DHj#;cwudUwcww&)j+YjTn{{!AAriQCTQ; zRq`uQUsI7Eb$(vUQzxFS_bEIfyPEuhyfgX|GkcI%LyxkiUNfF∈5vk^XU)A~x5R~7! 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

84|4SQfB8xlgWK?N@>e>)s zJXhza602;Ec-)E<#21_Ziw~cS)aV^{GEWVEbIspqopl1lm?Ap2F z5T6zOpnUxM5?))NQ&45ZuZ*3AA_{$`SQ6|YFqw-D?ULR0U_2Sr#}_3!ecmyMPi+`w zA)t8Qve2v}L88FwOCGsb`F?{=Z4J_{Tam2g)BVFeyXGoy&@KXbsdjw|QlTInot2t7 zcuVm(bI)JyW6n_%!5e~f1v>PKN4M4J+sJ+!H%$Wounow(7=uIiV=fRy{eO9i6rw%3 zt#==72WK^HV2+M!yLRW^nq0r&;fj92#)KvTY>Ingd!8O4EPzH$sVm`3s1TCVp-`#` z*Q~_-DR;4!qsPwjk}wFN+r(VG|84Q^;&pU@*#2LJK&u}i+Dtnz3a+C?n)vJ#9chnu zBf6x|jaV3a#bZaT#JR6AA|hcsN1UAa_~eZy*j4TT_WOzc*diAke=`Z*WUm24n|?uE$STzy6n4_icY4R4o&s=*{g1rapHka>z>)nZ-TTS3$VN-3YKF8SBHo-dE!I1@UX@U4$KT zDteLssVV$^0X3DMCx+t9e%>zDBwVm1KZX?OW^Nw;MX>H&8U{QHwbp&&Cn3+CWDfdg zEu$3LbprHIA>*f$U+$P&ODuoPMMnI5$ZSALK5=(0h3|Vy-4mR;2Z|t9Cqu5TnNg^K zmoGZwPZRW=WLQQNz6pXX$Ck2HVDMoA4Z;(k)S(+ao5>D{K~I$GMIISze0zhgQ)=#t z5qJ4f=d+{dEoHUwwgnp}8@pQXW}aMOStKw17bQ$5(MD~$f5xHx**&E3T^ou~Ns0l2t9_XC*-6Gig%|(^ZYO1(;il5Z^Md$@a z(^&HLGoxRGcz>}-0yXhBj(B$Wpw^v}E*D8SrsBC>L!Hhq72Yp7YbP}B z-)!9pm7L!ngRPc?(DFsM2qvBnDyKpTX!*CZV&uNSi1S2rm(=lsi)(k-_C|>;8{JdC zJh-N!&FKCGS=W`2zG*j^t)gs_|2?}Qy^N0Z!};-V`JKUw4`e=QEgCG zR3d;uY+r_SmVpRuXM>(#bo>YBt=Yu+`bS8&kc7`$eG?za6i^e6n>_K#r7<=h13P=c zD7naYi?3VQuJq=|(U3vQ6FH3kc#54)G=Es`bssj}YIAlzh=d6Dd(t58f*RG4VXyj%?w2n@ibQ4VvG?w#J z_^B9}eaSnRsO+aoy={^kz+0lAEMiM!fC7B}PW$Kb7`CLGN{Pkz8ypi%I_kE{#x;yw z^-Aq4ePbVXtRo2N<~>!t{!71Le@&I|x2g^9-KszgVsJW6Bs!mT@1WLELV!<41sBzL zhGc+T&*rKt)>uf~qFvcmGB4AfUQ|EWi~TwY5z&=M*u|TFiI*tdJM!1$;!aw3C%1yz zgeyWe_C_zyO)Utg8Ck0&EWcRfLZoad%TV%pGFpEmB2(%M7DSGg|N1wAfPrL~HDggP zBcC;tx%fG#I<4ka0#n3UrITfzs!H`XzEjRR7C^$)OkQR+Z79qFu?-gpU0WX&ro*B1xP+nf}G`9bzf>+IUd7G-F1 ziY}EbPxO2U7RlZ!mNWYuyf7zDwn~nE8YYxp=lgP#N8aY2rog&_lvt ze|i-o@2Y`9yuIJiJ);zN!K$&Dzh>oqg4-n^i9l?tp0i%ac)zhq5{R)rf z4x<+wDz{ZtYbB;yGbRHlrYtZC=XFP&Bw2;{(6n+7eYf3&oItV~upkY4bH%gkw-1ji zfK_}C8{#kwvPSuIb$X~rq2tEuu~3&~EfSusV)?2Sxh~Vsr)_zB zoYLAP3y_ouu7rSR>+|GG-C&dYlhnX~&W z$sW^+ilLF1>b5IEucZer^wt-3qBYfnF9`npEnD9Nw#@)YdvFaT1 z7Eqow3M9uah+$uNC=ZI!e?b1~)BhQSaJcsxoEnn(=2}zel5%JdtluFXJ!2%(zG{=M zs_rv07DGv|O%#91zp>NhudEc46~t)`3dAPH3`JtIMltP;Him*rL!)TOk@Ai)tRx+{ zPL~3Qs*;t&O#-Itj5K`paPf*aS38fUod;*XpqG?gnEkeSU{-zH*0)TZJya&^TuX|4 zw`Dmorqgq#0D5m?nG*Y$c3!mMzS3-H0o19N})F~SA%y${)FMOum1E#pcro)p*V zp8EdNRjfbT;2}tsW#8d?x<&0>wPkx`Enua~LU<+C#U`uy@K)5z6)T z|JgUE+NW(&J@m4yAUf}=d`7eJWlVx^jB2~Ysp#6eotxwS73q?on zDQmU&)cB!D41#`fxwM^F;5_)gf=2N6L@X5s) zZK%i{9}X=2nn}u}1rY~rYsaKOZp)O&>-UJCZwBJQ8~Dv&5n{E3sumfOS0TyM&DuD@ zKcyn6RSXn)_Bxub;aXo=cW0JHfl+u8W1#8#;_c0BGKT!{s$_I}FMBhkPE0L1b#YV- zsz)y1ebaMje1V0Y@#^=I8931J9M~#5WAM>pWfn1^v^5DPgXfnrN zcu~>rW>nBIDuD!^-rDZjB#$Kc8*FQ?iTk^yu4|&a3NfYc2bND8YQ+Fky0$ z{XQwYx$L49*5`3EO7laHXRI{XJR4< zQeA84A&n-h3Z|)UX&dfyiv@+DCH$)%ZXR{>Y~78I{Z}EijlnTwFpUA}^V;wB4UNpi zj-SLRX0x7l6qkQ5FQ^(iFMc&n!T$cdbj4;8=#jlCd^c(VyM%{Sl(C1POCo`YN@I=i zwoS|ep40OTTqJ+|Y9jTlW;qfs+B`J}L0VD{m*JdfW^n7(MUFsUd~Xizl9zCEy^dIr zCr-7uj8|_3ZS=V3<`?7MSg4>ULzDku&X(a97GPG3$&~8WWv*7*m|eOx?JWXwZ7LMq zbij_iYof=c9`4*|AGBu6bf% zO|!y?L*P`4c(pm*syawU_zXm|?s1e0WvR$jn65<*g+Y58EMUrGtVjCiGs_D-E`uLG!s1x8AF&i{i*>0`!{86(78mfH^YiU&@l%Xz18engl1OTd_PGX+2-Us zS2i^z;w-O_L2=+f&mkno;f^B_pZUczl1GI!<=GHZ<;Rn;t)zDzFAju8`*Jc$?IU%xlEG^TdiMa!)%6b8AhI*eJ%$3pKdlk%cGF(?lf>IKNxw~QW+ z;i60yT#~1Vz?z=%g)%a*ex(S@)|StDS}VJK+>s;7yiG!A5)yz$+a|e`jVm6}@tU{DqDySWQ89uUmHY ze9!y`)p_)+SW}(9g%O^zw-wi3IdI>Nl6K4CPtzp@1DccK|8|sW0Gz=YiPR+SB44rP zgJtEKE|y0Q?i)1NqO8zcs1GH53k-AO%doZXg5{Kv;NNxI0c^C|DU9L=o0gXq)->cj zQq%M$A#JU~u;z{0IJ&mnPbHL5V=xM<1Qg}=WQi?{oe#^q+tcT$l|_Ll32N}m3j(*^ zbs;@yT;y$tc~TNChK#lW(OF&KrL$yE)jk_KNfp%ap~z_YCFtN(qAC_gA|#EIxl&~# zaL8KN&^LIeevJ0%N{utk6yd$f10BDl^0T2y6y17FX+qpq(W5pax8y$~d>}tkZwN`C2rfJcS?CPx)$UIISF!v1>Ud`CIzRP2u@@0B&)I zc&Lw9hytmAwlhllHz3io6bovO)!tp6=$+`$RFlT2P4J0^|TcJyU;AY8TT+x=o;VmfNl6(-7i4W_g9R2SMMeZdN$Pr{e2;u zn=<-tBEO-QE;*PanQjDMJWqS32oh7gmy+fW6F*M=teMgTH!0Y)quIR?4q#BHrDXLn z!NRmX@C>#B%Hizjfrs*&!Fy%O>l`%}dO8x1%)naKa zS7jlLuBteUyfnC|S#a=RS+aU|Ge6?Uwf9vK%gGx@r6vwIx0q1i za$FM(v1+0mvJ^GNESuBpgpDbyO5glG?;SejO}lO35LOyeMJ63a^0tIdW{vdmU5?H3 z?nUQew9-w$0X~IcpSYtLnF5oDSons91s{an2U0WJIp^nWDHiC85F6RG>^1Sf!2Z_Q z#ZeG3Sm=qke4!i=TUkqJj{7ZVqMAa!v1Br$ePgYk7YWrbYA|nR_^mvsJQVKlQ2Hy9pGVA+91>n^2gtj)V^$C3&8? zsrw)PI(2C;X?8d&MVcD|6t#oOZ`k<%09AgvstVTD0j9-Hr#kgo$C#_z>LgCB-=DJP zPrUXqg)BY=hl8m&Z7nc}9E8I%*+N4XvUY&=@M%s!YNK|Z4Sj%clHuNK#0MAjRI7%0 z`d~VVE0zXflW){7o;eM>&zY?R`xkqxH!m!~09EfuQ`j#t2CYyJhC<>de9o;#T=oP7 z^38ZiTUnjM6qDy-1DS!de9RF2bO6dl$%-hc(*#CD;9Cg@z6t1$54R-E^Mf}XQ8FS8 zCT5=x#m-wto5txzJsVHaT|_yYxRj8=3FT}kTTBMJ;DgYy_Bn8uM0WG4Q|9MOqB zcZ9B4X;UQ{;_Z;af(u`1EG-JhH0iUe7f=5v62p#Uw(r8p0>TSv!4hPLOT-ArMR|f8 zy2o9qyV0Kyhct2ojjH1c)!*9U^1}?azzV}(*;V@1C`&lI0Fvy+8gO*BFwy0yihf=^ z7^bgR`BqL(<*r8}$c0k0ucBuze5<6?`7?3LSnZa#NCzJb4~{yu$iK*49_+Bl;Z5Qj6!G(7Trq<)oR`XB^Lq2k?Yj9?9(`g)Z!Nq-dn-q0-q-x4efK8nfE?w$ z%qnB@yc{nrajcS`6gkWnWHA_CYpg6WHZmM`J917F8pND%pfQ6C;V+}?4{RNhEpO4` zlj7E6YJTn~*ytBD41c__$^FB*!Zoo2>wA&ru?WzG^E!xU)yO`W3)>n_0hgidEv^p; zHfRg)EU-!gm#MU)vNpUZdBEE|+)scB?-p=48IP@wmoL&YNC0uim+a-M5le$9)x_gR z$ELRTCRNB3?iETNP_s*uv@ZEUTQ;5UsOR-|x0}ui2$!Q-Y-c@`W$-dN3rhS9 zd-He(;XN5J1IFJ>=!L2#&u=5&8>I7ZG&d#9?PMyO;a3Y`umEQ>e647%8p$c0@&5Ws)u? z)ZD2>Sj`9Zm8FqzNx{206#@l)%(=shW@GkkSbBC1MaZ*Ti|D=DO=pf#@W>B#gl%xm zxgb^(Gq7Pudy1LO#5X+`uX6D&84PE|+WOQ%90!jIi zLLMJG6~8)BoZZV}Q}h>6+`0JWgm_T|mymLUmcK^n-fCCWye>;#Sp%HsF50kgR4Pj*n=5khleO8eCwFV0EX z9K1rb_+8sBjpnn6kn*of{^4oL<$}MALvJ6)ZXBti^{S&>339uMg;K#pE95U1OSOS$ zZz|m`F(j<=mAAlio*_vg;*{y&6SJz_`>?K*8GaS4i|7_{*tYZAq#i^3pY%azzAu831*>$C%4_|5C_;Y4%U zTnA8fxwDy|4;`YJ-`UODo}Id0pF0ah5M-8+qPmWQT@xOU%xmhRtn6^@SG0c$n`r5* zxPDZ~MtD3X+lAD?6ZcZa;ta$IkHq}16XbK>pgG!}dG8bxUoO&nlzP1k%G06`=Ml|- z=+@}5W^mZ@ueL#_KT?#c{Hbj>heg^M2<6vs`mQd|;DHv`eSk(-ac)Vo(dvOt6bWAV zBhTG$x*Ss2%muo>sRum8@}7wTwli@3X!g_sLI&P~=Fdys}(O@GY;pX1+)ob8v38#v0`;xj>LhSFp6>Yn`DvaBj66C5;vL(R5YfF@$xC#ybR2ezEXwb zaJ__U^*ZrSKrP>dvpc-uPp9W2Smz1Cm|v{oq}=L^hrtq?xPOrX!$8jg0Sv21!v(HB zI>onE%(Vxi9-c5?>J%;KD$rbO7`&Qqpb#jgK?Q{i5#GN8nrUy-f&s7fwBWJ0<);oF zp*1sPq#l-#V-#ZT$amy`6i6(rlUv3YEa)j4MV#Asx`ghjK0mK~{VbRM7U<%mn|haq z^;Se6f`CFaJYXNX2abOd-c!Oy1psuwsu6V<`t=EiR`9B_cOj&?u506q;jNAb`mIB>r|Bv!Ac;VLee3c2}Z0Zyi~1NuMzo^tTgLR_`1pI4N2$_VS50dDDt!F`Y0pqEzN7Ck_*- zy59)a8wZy@Wui7(QWCZ4Oi2U;XnKW;ubVS z;gt7LCXI7s&(_1`=f23ePcF0~aVj_w=&M7N`zH-DP1%wmDWxlv&j!t4o4{z=M1uwO z+VxD)({^%^w~M_f`WPWy3nOFO?$z9~Nf3m!7AlGBo~huaqcEOXg)nMkt}6I!>KzRmIa zZXtb1=a-W@)kuvqlq&FC1;7pjx<~}llAf1uK0S-767C}~)s=4CWNG3=WhtdVHcO)q zunpEiMZuo`y1FmRgPo2`Y`dC@$J`mcsBrTa%3TjL&Y!LX?e6c|G}K4{8w8jXV+ExO zXdd!6lDE_WX+;~CtSI5SZ~0ZLk8cS~PO5u!$9_p~i026G70Co>rUAfi&jg*b0++i0 zwpX1uX<%@V80A-By0m}ilD;VacFrzceTa%jC}1^@ApAFkAeYMA-$pa+*XU z%47^3)uoSymY!hvueK-#X6E;m19|-rku97NN@cY0r?E9bI|m+A8(I*w0^XfEV{@5eHWmu{yE~{>ms!#_wYIx5)1?iAGdWyx3%Jc8f$r)W z$k?mJW!kviHcPnZ6~{PjhOm~}$Vad@DwoA?VQXk8B7dyXG|b!Uv^WV(8(^b;dVVb8 zaCLwztC|v|h}2S?V%(*G0bYWmfk;w*3iAZ9@UL-!JN!m;yy^zbKg_0pnTsZ0E6$i^ zvVm%|9cvzOnayk~O@>JM<1WxPNO|o+g0)*?ShTkxZ73+yn^0}dXq@6o95`VH&d-LZ zB>!1!5b12F!~c0QwozDVw$Avs7c{}W#M1VMm9+IxBCBozI|txf+l@kGN=o+6&R*DH zGg1~wM_!B%^WhG{@_ox25LOIg&=5=%@@+{(UoJ!Pkld^dbNr@VLXN{BZ=OY?4xnlW>SL%&M_ zCLGKjwLPVR#ea!`bHrW=jY!BZ2zQ+W{9w3Yfj8?_H`3cwCf^XKp8SrGoB?j8>=NNv zw|r3l$8y|J`$xFRY>-GEEb%P0WRM9-J8tuca;An?MmY|Wiy6hyK-IMA{i9x^Nnyl2rp{kzRJN;bmzh%GM zLd6-delc1)paR4`-j~eQ%L$se?yoW!;#^PyeQ03H$DZ$Dzy%*#Q<0jL<_OG}o?hkX z=a);lWOXgk+z91sb0T*lf*YQ07zX{x*}?kFQ=Rg@Uf4A?vqAaI~`CnTG` z4~ela94?Z_YT8u0x@iNt*gFw6Et}^%e5g~+P_B=rkvYEdXc$i-YOidk&rF45PNo#L z0iqBGrY~;2a%}Th-wKyxB}A2JC=$P#?HP}Xq7Se6W}TNESw(}dkkvUnj)UwiMB!AA zgLFnqWq*qu#qtA*4s63qMcJ~O>7V@%MP_Iw1q@_?EON`GsBWU5Ip&lVPL(2|lIk_P zRG{l^t=1B}Rg^eG3c^RE1TK>_FW`=wR|=}Jge*)=?NXW;PInl>0tw}D?l&_A#o|8N zT@iMBD=Ui{5zxm`~7jCwc%6VOc#a2gK7ums2vmCI_i0#Q#K+r zl=0F>!10JARF@KC0D^@nKAF{^Gxh`-Ij60Vwmi~y!CM=lZ;=_lJRcO0tt#zuq4=2g zAm4je0L>5SPEhzl82pE7?W-KT6axm#JJeGS@{%k*1T57=liHIi6_hMxsHWxiw%*Mv zr75Nc+|NL&--pZGrL9%y9X9n~me!L+y6G-|)fzGcjpl-0GTB&jjG~N$kNM?Az6yMM zY_ZE`=S*N2ju8=uZOsurt>2&P!>gW2cw2{ZA;R-IkRvrg+XV+0xz%wE)RSbFwP zEXNS;9MwH`Aa*XzAZyv?ZCq<@EO!j9X`}~@S3u4}JM#@1_@j#3DV3?pFpNbDb3toA znn3&ulcAza?s*LWO31w(;>3{T8g}tCFxU#Q1K#-+{?JYOu2F z^6)WAy^aOuf#0^wq#oOjCS%&%j7}{&)=^V0jQ3s zUXRo>m@ri$v#n~>7=l4oR+gtv{o4tOL9=|H7myhqTTx5=)rI8EX}Rs2P;~RW-f&nx zn!;fWG1(o`pip7fm}K~11|O?h;@U@j7rtd&MXQ{faj7KL@|vfrKIghXRS!SPCDu{mMhPJMJylA+qfv0Baj+#i(svSAq zDyncL-}!5N#@BGt;+PJkGA6j6NI>4YR&-xrpF33+OH;N9)o|!TA$ift$70gTR7Qg- zAVW}{5vA2THo0~e8m0EhIEWF+%b)B25;n|`+pKfZY)*l>TV-L8<~m#O#H%&|tpx-F z;KBNK;bK^{Z^W74LG+7dlbW*oP<@Wb1tH`dpW|2rVD@}EjgTw$u+0ek9y;#4uORz> z;V+B}J40*iqy2YcIuIFqfQluy4E7JFN~-&mL|%!ym*P+ww?@nMjCCA@&YAbu2JXr6 zL8~DBR1Fty1%hy^di~LK{I{A`nV}Xe2Ge&9W)%v~tVXr}$-nv~)45*{Ay&~Kuh-`JFi@*;1unZ!U6a`Y4(1s5UGx3BL50ret*hx}}z5|C-H zTt7JM4PrmrF?5e@i@lc)Nnj@@?YJI}U74Ta?W4<`#bY0?Tj)H#S15GPV`-Bcq6s!u zxOqo#V?3+OTH z7fQHwYZUP_oU(sh13s-tuVFGwoxFM0l$Q3w>Q|45j@SX*V9$*mkRjSP*HI<9QKRv_ zevg)yVj2uVo0-sb8FA7xI0s1&&+oX1R8yORqy!?$0$vkhuY?DIs**u@KTCH0DoMHqFi=H%X0$?7KS9LFl1k`^&Mp|b zEO7#ZU~tU2VpQ%0I;fdElRpvGh)iRgbaCI!{V;Kn)k=Iz`GH~7CB5EcUXPM|s$r6%DkTq=~3DXmzS^TNr@l9*ch0 zLulj=?U}}7AiqvG(eT>mWikc2VkAeoKB}S$P++VwjJD`6J1>`~z- z?L=#;!!4`vz>rEl2@kKyCux6++%LVfH574pABPUJ;$7fHaNDlife0C){X`9!1)2)! zucHiE__XTV@q`TW0q6K>#u~wfUk0}^3EtuRbroyT2>1JxmA1q`1cQVw(N70~cS~0T z8s)OfhoOPcJT|h#hURAxx!iBas6=*|O9bYxVu19l;xJ`zS#cm_;Mw0iHgjNM%Kmxi zLW1-sgRdK_8nXL<8$2!vO9KL}F00IJuC_G8q~?*^Wp9l6vZkIFom&1=ISz?v@J#^O zo!;cS=^zU}ZAh`Z#u$_)EkAv%Wi7jIbk_@tUM0Gew7Kwajl5h4R{viJXP@8lVAjVp zJ@lV!?i0^MH#bV2suc{P3~a9?hs9X?k`IP_dS&&p+U=%JvJW z{q2LJK9EyD)HaUaSnoUbB>eAPT>i(~5vX&cjvc5&4h3CB>}FjuZ+cS$?xQJpVuhBC zY{}1%2@|a-C9zDU>DY>P31w}#M5z57Ju&_|^`lL3DxW5IK84nH&>a}BV(Lom^ zsU765GPzD~XssUdl1fAE1x*aGmzib8Bqn+CU>rAf!A~rUE8i()qUvzIyB=u^v%(WO za$d%M%ru%z&;3vHq$Yib%*#%d;7j?IwhSj^2iGoz^lYOMAw~3~)}}uhk&7VbWEhQ& z{rh6Ft96Hq9&Kx7>>+r?$S|Vduia^0#RgL?hTQsn1pNuJfTsM=&u^?iT0cdQ__`V% zgx@Qq)`jmJuk+)m=4|{<-^TjN?o{<4t24hVGRD_)rBFS0bMG%G={|GzyWTjP;^WJd z&a~Wbz?UKy!RYAJZ|;QQQmg{AHqy4}|K@JqHFxt#=^&TRP0}jSKN3K2Xvqr}Ho==p zNz-aKZDR>@!Tp^6e%IT5@<{iw};PN?F zbehi8oiq*hVT0bs{#$5>>31Q~39}j?!m{4Y;!xKmLHb2BnCsd+iL}UzRjLTF88jD= zRP;u&X3DBw%}_5P&#FP>$0FF~S;Z)DoTP|o!jri9L=7NqQ=+$SxECJ|H7{vXX^-Ie z*v>hGuhpl}zyT_HOM+SEoWle3Ev_rCUsPPuv*bp}$O$5Rz-Ak2@meaV2L89Heh#Rb z%S|p*ZU)=50NoI-?)27-@;-EkYOj`)`(n|v;^An6)NP4u}eVUkf?M2rs3MAE!4#jS5$x!ac(TkbOqeDd4n@o-st$__7+)kPY8E5X0b0!ErvZ19cd`#a1?u>3KwZp~C z92(9qwu5K-QJ7QHUQ~prQlXM%NFc9p^EUYkjE;gw&dB6~PE>;LRWGC3b2bm=CB9EV zqGe{5twWqE4081Wl8Fd?e+}gc=B63zU(Gv?I(7RmcLQo?{o9~*XgnXwE-694_J75} zbdY|SFx5pf8~X;gBvWkQCWRr-28jn;Tc@e}sD$)=%O@#SotJ z{Ly6EHfzpT8&g5BD^WJzwiZ@omMS4SMu>UrGzOK+h2JnRJ?~8QM7mGh3;C93)L^J4 zHXRc<`*9GtuwYI*9!3Q+l9Xoopz5&|-|{QIeIowmEAj@As7`n)n=iJ4NHo?q3VH8) z+k!k{IFO(EUv)^`3?J4AxGB}XAPpM3kjYiZ%-N*4p{!%LDIS|CZ1Lbr5ydYjCWVZ; zTzq#c6u(i9j|2Q4I=_sE*E|%^ zNJ*xSfH`~I#S$>PoSZ67fb30ArffJNj|m<VYlM3p3T}kyiQ-1iGW%xQGHdk@DA7Lz(1ljiXx-Bj41BDu@=_4?{i_iNS zli}k$l~NpNb8pK6i7>xZIs1?_Ew|W9CUsqzDoj?zk2%2|b(A9IDQkg6VztQ3xll^{ zkOlM`vgyA3w3BmbWt6zbKrvAvooowjZUQ;@OQV0ZB-l;q!JZp6pQwATVpv1;BJ}2U zXI%pRYG{V!F+8c_=x%7&bFpNDm=MfN(*c-^PMJuaqR*A>JDPBkJ39}C6lLs{uihpz z{6?!C6a;Mc0xPFs&q+Wu#eyr1A|+5FTwE{UN(R)F;1wQcy{ewW3BEeOb!X@@m8Z!K zoFO!c53|GEK39Wd?*($Th%_}CtxXiCIn>Txl_bkDQw!4208EkSWDdEni^}!z`wA$f zu3-MN>;^3k(w7yU&-Wg=rW`f1Ojl;4>piK{Grd7}{@K^@uSWMu{x7Bwo$Yb#-;e)z zo2k2SrmPW|%qjGAhSgyCz=VUhUS$t3_>cSAy8i)|*#XSaa$0V~WztD~tKK_?8~2rSHwo2^%~Rm4(eo-FS^}ts%7Xaak8rQN0=J4e z?6P%0LQr}IL|{XZV0f13OSY38+pPc@t_ai3@yFs(omDDRRWY`>%kL@X;^$^AfaUwc=Y@nM6|dq;mkK?awbQg($bD-#|w zkr^PMnYDamGb1C}7kWvKv2VmgjSyxSNhLy2WR%mtaMi$aM_ief^cGS9LJ-s8p;W>u5Q3(Rf(-J9osx|dm=eyO`-47AEc?nPM_CI%PRTpha6ZAo zM2%$NGZ%iv9C=%|V;+ZtegnD9^lOLO^w)=X;i(GaAd1!s8moS+BjcYY8%z6Rih+8> z#zz>|L`DTj8_0!nDIe|5C6Q*Fnat3zs}abk>?NOw4(+;@+A!xCtE2lnMKO$2mNs+| zF3a#;FM{o}z~)Wvc&s}20Z)*f?4;UPUsyc5O))kMG3uU^@v*%vgBW=4M_^$S@fCM0JTk~?q^Yd6ODNu41Z)US zGOK~!nQl#@T&Aa@zBxKn$1kSZ_2E^9^9okyJcU{_950UYb!E{F_6m8t%ch23j&<*X zi{1E|R)LV{#9%EYFOS91KuA6zOio4t6&+p{KY-(p-&s(u&Req?e(m>x$6B_BFN;xL zf*7{Kv*nI6gqV1Rfesl~O%~RXrgP#;MvD;ESTE`vC&t@kjvSEB3to(BhWB?rcmf4r zb^7XDR=MfPVD_(ep)N7d0u6lmn)sC-7L;X0eSj=u>eojob;^@{c45%64m(b!<(M|( z`R{^H9gHDnhg)QD=zk^ylf*;SqbQ4qnvse=q4j&zsq*v+mIP z$)N9t+TEzwp;@_f*y{})nXei!bYw&!COJPMCA>l(?yOD4*9X4{=081dN##1y*Q_cY z$yXRXjSTIBA&*h(o2UunYh+L;smn=4?=rGLd88{Ccnst6x3y^-?ZRwt2U;KKX)Qkr zpqyB6tKHXB!YQVdDfhY@bN^aQsg3O616;Z^fuT+mGq}yKec+3n^Wn?cECOD1IP`n# zbp+@-f&}dI?fdcQX@a-ZU5vx^k;KTayGnP}_Z=6KsX3dxS=d_R3PR#TucJN&XpkJ~ z= z@cK3vLkNbNMZ`o4>6=f!;VyLxN%+}yF#qD^+Qn;nrA#=fn%uXN9CbxfT22pDsj=I- zQ?`gMal0VH9HGjd1!*eH&!|z}xTJb4pf`93CwUnqhz0d0@5l-UCXyOduW_t|uYS90 zO#`u=z|W-cPp{nmS>fd)P09SxvB^Rg@PlB{#;IiNQkjliMIC?2NnSQ-wP^J=)LI= z^8=cKF{vRS=b{LkKD1t9*#DjGfx>`?AnzoG`6Cp$=R2Wk;THp}Qw&G^G21(jJ$#IQ z%-;gycWwXXv0HNeHN+0j?P=S~G($`QMTN-c*gBMuSDWi+7n)G5Rdx23+_r6`v;V>+ zHI#pl{A0a(aP3Ttu8>txU*ze{BGg}+*W#2nWXlPWwx=2+TFMzTe?x6Vk#N7~m>+7e zP!ybXZuM1x*8UVC+d4(l%n#m5(Nt8^QT$cewF_U&QwL#rNt*21SL zss4rqTkyfi1>4$yL0Yd8jvsCppTDHr$|Q&YnDJh1vO~+fa_^QI z;|>M4G8pP4bN(nr0k+xq0B6{rrgcMX<=g!^Mdjh;F?fdSapAz3!?;DQOH1?5{hjgY z<*eS(711j%SUn@D5x+^glUO!wY#Q6{re~_(CskYEiF%QB%P|kke_{hu@w)8uy`N|v zDlQK1py~A1TtZj8^8&!Ny;N>i`W&%4HM>R$D``0dr;0YP1zk*c`ya_2~v+Zj_w|)+A6tJCv4~vploX%FCQxlb{VvtxjAEp0&FUHk}7NcWJHyNn#-98JUI-C%$he|ycv__pV?ZwdwY zl$AwcsWZ!v^IbX$qVL-RY-??qb6b_o{Y%p+ZU#2=y47{wPK-9M=@X790XP^_>b;=t zAUkYNSKo>o9jS-a80p$5WA!k6owdWxAoi+Mnng1kKzjpc{8QmW<=mEl!nVy$%01Jc z(*9yA$gZO%^{Wl}u)&q`XRdI0d8x_DRtuYvyF}5fOc00!FCJYc@4S6sh6!LRW7 zT9jY=y!DGv_>$=Iw1a5NWKa5BB319e>2q(xOw%l83X77)4!VI|E#9C$S#&R;RfKCm zDOyt!oxeNaKfiZyw@vUmT?<3wR94aAknjVYySvAcN7-f(--d?rN$o6d0X*xvL4 zyA-DTH%-e-TIkjIEk=m4h z%@hs?%6y`s2ug#b@V-GXtb0!XbvucWi?eH~Q30dfe1=+9BzjRzMeQa8_>1QIeT(49 z$H$GCxz-x_fu+*6RoHM^c@{KLMHI7?E+BrlNoLIqYAhtT*(R%7RxrzVb_eJ*`&7S< zt`kUZ5!8QCu7{>MzLhg63AmNMsLXb3fc3hksTrJ?=mnOv#bm+={YoT#aH9sf08v1$ zzfJzSY?$j|UE$!SkTv>9=LCI{(e)BCSDQPbtZwlX3tZuk0^|nX_48_L`bc_QlV_iw z&1r_27%ZV_;>s5qjkvh-b$EjZbI`-AtOxO@P87+?<69rDPygO!c~Qc*=bWg0QqGLu zbT?v7qyM|f7;mDskMwg4wPIg^tPT1Zao^sW6tYXmd=hwITS}x~1UU(Rt9!9tuePCQ zH*6YfNdrs@EHcbTHdO2~Yd91);5q+?ck*UkFKF)8Ck{8-0wc3zvjeL5f(&t$7 zV?gCTzn73Mp#cd~f@${M9J;@jBP)nntx$>EGAv2CC0U>r>r_1+H1qUl@N#^pZ|NgU z@MIR?Cg;lT;PkK85a)RcS{#1ZAi+=>M>iUQ><9AHBZ|oS}9;T=7DlcO7PpWz4OeiM`nx5Xsp`12?p7 zhI`sT`!2RjP0)g+GClq6Uf)!&zfDpM$z0c2Bm4qq$~gDpX60W(R{|9GYt2nzS&o4l z_iI&aE{}XftlePvy`!@pM8gr$I~!_szkT*H@+JtPR&Z%2r@ZOxqM|ac!-eN8jBb=z zAeNiQXSiYHK>v;;@p8O+a){=)vw_I%vv5*d(ieCFIbFOrGZq*W?I1M9H8N+*h z^9QTr0NhxrLX7?Bb>JBC>X*Dzu|7vb81by)0MaQg#vWVXMI0?>i{-jgaijPLq0*`q zVpa!lvo0ou5w&!o10k=_&xtn!D|lV1FCv0QM2F|9|_MA z*aB9GT#MP!zDU}x-d+NE#7*zTx+-R8EI}BYnI|t7W25^)Ala#6(1Bz(A!Sht)v_E; zdU->G`B27mc0B}O?3Dg^*5!&SkNd!d`JiQ7LvEJ}wyAC0T#s2 zsYJCyw2t?VY0+dCYO^ox^_@ydDD*n_RN3?Y(i^IpcG`%?ORhFu;r(6rm+NcxNn{TZz*$o?_%(p^s*n7xuMQ z*6sva{?L8zlgb^d9u6Az!le_bHr^TG_RPz-rdH)<&Z%HXj>>s_GoJ2VvA?mExmLb+ z{KV@f0qU4vzt-5LdV8cBC6J^L9&P*VwWl$22?33l{P6mKX-z#>+1v<)P7L9$*cYgY3+4%k)?5m?XE*mU7%o)z3u~?0 z9(OAByt!gLy0hYTbP7{^r`**Ao;s^JH#0{kM7yB749=9g>?<|PQ%}uGwYJ>8SEJ3` zv{ZUTvsQL<-)o^VT5h_($ES@h*~5|8ng#BMGfBii|4!GXcvP~&)zGuC%Y0ZBu*I=^ z`QVLRpkW?~I~GwI9%On%orR>k211vNYI;flX*^23-E^bnrAz%|f%3OpI5Mk6|6ukQ z{fL2f62-Dr9PYQ>DmvoPQB@mse3DGU-1tzF0*G&Br6AM6@z^EZjP@RqA;M*K-opOrX~3PDsmUQmyn6W*C>mQ3iuXNRgn$A9}q&mzg02Kz-y?qW;3-Zr%se{mP? zKVIT-mGuEQft?Uay*QcM<_=i>U!j$CF39f;`Nyj}L)L)({!Cw&5(wtI>^EQHG%n8p+3TgVc=#4(YPRgjdXDl;J{RpMxc zxlv+Zxa*?W?_~7e-H|-rGAM$iFkiG-jXxnov3!kn^^A#u2s+$J-bcBh0P z?@t$39Bma;d0p;dgG1VcaZDj!Sw*+I*_#;3nx1rOd2Y%*DaR2p6M8P!ExN1;r5;g4 z{)v_j*H_Iw8C|1{hI9)F1D%1+TOt8@3deaGD~QSrJHUl}xSjBg0)425rvGhUdHm_q zv~3vImgxJ7wtdqTbj)HUidzlcEU1q9G7%TFj_JB+W`1uKxHQ- z4!j29$k4(~iu7jROeGQ;*1Ioa+^)F2M*V0aQoE23G0jt+BF@c{zVvO}w)5}sZ-0b; zz<3W)q`+~8|MGlcHau)!T#*eiy7RX1O991`$T!QkB2pf%z+5{7nq{S5FVY;pxmi|b zCN;jvpS*bmfA?{>1Ez=sIV5y=y4V33xHA9uF~~Nx7SV)L*e@r{V7qoGqh8$m2`SXQ zp_vr+@&Li_ea#q^){q5a_jL-p;Kln7%*ZlcS?wBkpZdUG;9;*7#wjgal#Ax>lz~{D zq@{Vmkm?;(+qhu?NkX{6mYv)d{bT)tb|l;fL!g3Oq~W%pMjP!JDE9j64pU(u2mg|W zf$o`;P6Yc{4a73fB50n~9dHT8!b6-DQ{fpMU zihe^{g4pTwYFD}J{{)@!|J?WxP^nu) z-?%K~zFiSS5z)NrP4UOYCwQ{K@UCxFEHOSH-x)1{rOku|)^9QVjrG>`dwP2r_}^de zC?@DReuy$EZRp_M7~9UvlVyRs$anMGHJnNQ2fj`4Fj&?kNl94bN-Tml-?lSQt)7&3 zuX5ceMPojWNf%o%UC;jnEH6&!fDu7VjWeiOY!MT2)2KRp>nn7hoP5M7PNG<5DDJRB z?*zl#YXrIXwdp?h3Fm6!99&=WJiOsc7qug+2d&H}QV+KmMuy42_+@nHNg<=o%~$1e z%yENrT(zUsgad%r4PTo0#Q8hI@zy+<)jLNE-U=pYkO{b9N%`w{Kap>w5s<%7qC+MT zPA;0Aw4!@>eNT$32W~eqNy`g2{VFGW1}}9ESeD}^-t1)x6i3$9VV6OwkdCeEV)gyH z;U-cs?^!%%*2&XqgSCO#zW4#$sr7>~B7gIxohs&woAO{YAa|QUf*rUg@Ya}erF&j1XUUodlhFeu1l>ud z4zl&bYk|0DIR5Bqck^iT%+XO)fwpc8edepS*om|$?^rYQ3|35IbL1glymOo2(V-9_ z!7u_w7Gd%Ez^v?o=Vp_z%f2_l+%$8N44w3iRp4CqM?r}x>Br3%$^^o82fZJqayc!F%kQU z&=Z?{82drd{LhxTf3NG2E+kbG@nrK!gy-|i5Eo{wpQ*DDa2EQ#jqJsARIq$q9778H z?CZnlhlYrQ85yB6d?5#`Bx&!u8g z`DpIM5+qEN16a5B_;+wx=vp;^mugqDUBFCl_0s7cVIqb3fSgOGq8QFlnsHC zMT1PA$btgMPS(e;-*#nOP<-w3Q^<=)ZFeB)VjZDJEBxjk^!=DT(C)Ic%Kaiy$crrM zaGd9k32_K0r#(xEyYP3&%H}SgaZ=y>yJ*CwvDkYj-SXBRSJJZ_w?h3R3OjEh?zD6M zc#dG!iO(M(tHt{(-Qv2koEN}v%a15?&@)b+y|&?XV-Bk0e+4X1O(m?xY1yw^butD!<2vsJsguwX8cRyClJ)v6xL5oHd2?sW{j6 znYTalgdc@Db3p6C6;_u7G^y3gqTJ=nX%4&m04|Hs_vXeydSnm3_m)&aIi#?&tongp z{vbR1G>$}~pMD@^!#i37#zD}(i6{*LdC&9RWzVAhO`SM+*wDs|bTB>689 zps2FfcEf896i%52${X^e@)ZuOeWue*I$wZA0V%WNLr<5Uc9q0fVUm;CsL?VFQJrcr zD>Z!|gpSnnynx4#T;AqAGxwuQ{qJY3sbBS%%AU`j`AQIU(B<7a70#W~p+0S~ky`u~ zS|Ndfmnjv}h!DL>TEO`mm@K;(#v2X>dZ$)9`P0VRd$0BLWccW&1)o8!dc976lQ}O= zS6EuAV9BMZ7}~0an1(KoOnKRRdwmA;y&t2Mt0ObFm3dguGvic=!vqW*{&OnWt#9UR zd;3aMQp36e5M&zP?21{jz=qWZtZxOOu$7v>|~ zL>w6QLaD7JYxy}!nN}mR+!?6P&BioPvQk(8w%;q@vXJKuD)$Z+^*R_JaL9C9rQp-! zdp)l?_X)A=)4p7lc#EwGNlb|0=FH{I>h&HNJAEV=pce=7GnDH&^AetkeYPWgKhOb? zeD}=x+C))p8vNvY# zTu55$JPHro?y!te^C_#;WhUe>tJPFx_DJp#Tm!=PoVW3dF!DUqQGb_LRJmZ0wBX0M zg|Qbdne}}`jX{*0&_taw2D*{$O&4AjLO=6+mWwv(ZbxD5JYv*2_qE(N)X7XG=%fBw z@o>M({cdA)>V8O(f#Uf-BLV6%_-m*=gH$jEFRKv6fK^kSM6bVkquSYJJCsGYD*_b6 zMN}gDl!TE&je)gOQTacX>lHXlw1CV3#G<_Tv5R8Eqbxqme6i0MBnuF0TvmqKne9r6 zVmD{^P#!N83>OeXNm$X}NnP*1;?7GGr7rrLJq2YtUCV4Zsz45mwX3B&!gpK2L+7x& z`48#CYuqX&4vg8XshAM)W)@BgRlW0kRgZAL=Q8zJ0tmy9U;Q-^alGs_9VMD^!jv-O z0K@(*%lr+sPLU$1bV)vVDB$o)DYa5+5iU!|H&$L7`r1#G4V&3vS$8K0`53O|XYPe3 zkrK6}*Pdu5sQCwCKF};!qZN7Jf52&Cm*7WU?Fer7=_?$ii5858rJ&#s4+;sG!g-|m zl(Fzd^GE}^k_5N$6grt6TMt&Cbh3A+quVXFMJ^7JEctruz#zz}S{4Q*PuOuRg>UKO zV_HfoFCbbnE=mL`yCTd}C;(@mPI86H^XBOGy~cCZL*$#< z_9|S**&2eu!XNJ@-?9wknzcz+7ub^?DSu?^H`eUQksw5jz^%~TExcwRXV_LS=5?uRq7!~p4qk-yN@(Rb~G`q^d8U)Yzje}m_pc%|1$HkjT z?VWBOhHUJ5=b*mWszm?$fZ|$6V{l? zZn{0=i!&v&J>*BtsLpGjVB`*;nTNtymb0W{q4oFX%vi0sYnrkkB3Sm!QhW5m2);od zinLmI!akjYCKzBtjBA~`ADChQ2y|3%tDnT86PZicT<_(5o5pGy{pqVs02R2z<QAPUZNv9((6p62ke@;1}b*fWC)iMOYjsLemCnV+ov|W+cpx zmYZ19uZTtwr+ZBWFCfW_8MvcM!_WoP{$96(Kz4LsXmHJW0cIW9ndlUUJUx`;p zu8cNOWp8c4Y6q$7pTq-8Cxh(SP+{5&Ck~z$kgKJizH8<2vcx^lqzn5}RHj*d827u%n3<7AzLNF&t zZX?)hCgB@;mJ9mWcc0}uuL?LfG&V5p^bk!s4fbE~HM^wG#jLuIqyg3d zj>M25p3^BY3Ms=Tj}2I+y3W2CjgAMCt>}x`b{WkWW`&_n>?=gvO$JOO+X@sCp!z#P zBE6p7bwTXYhjji7m8Or}_X^WQJuqy$+Ro^g&-+ly&#_Th6B<5=rSFoevG4h~uy3{; za%Hpa|LOa7AlMt>tHHLOPBknTsH+Le2jl)oM*73E*B*Qh%h$ssS}iX%p#vNapJg!) zj5Ypsr>mk}Q?6v5N0qTr((~>5-I`P`jB5)i-E=$Hi^e|L_>^mcQzmU0et$p<*^NFIw_U@pOgNz*@eGfWx(`UPOF)C1Kc2m``^r;dR5 zBx|u&6cKrz3q0!C%I4IWVyq^RI1oo5Rh*SSE2acSZZp>((E`iLi88OwHL1+!Am@eR zf8xi5uSV{me}clxaA?e7wD^tP1EzYNewX{krZY)rpyhE3;(GtsgFbjrsF!H|QL4P@Jd%T<2^v3iz&MQxKkpNextX3xs^8 zdRufB9pzGN-#-*Ef=w=N(ic;4{_Ym~gZ)bK2Gs)^`m?3+=TA{?$Ns+;q!G|mN}akiqPR9odUN#ow3c9D|cB=^2Ifouc9d(;+_fIH%`L2O?n zN?m&D$?Af4?g!5bIpV-%c@@tMAdD@}c>#|OP8AVfb5^zn>og|Eko8)*XZzc%Ac*c? za}VuqL!?><^&qvTA-&9jAyVCc-Tir1N5eN(X=R0uHalZ=*t0H?gZVBlC7rHnOL&dK zc@3yoP>;9O0i%;59SfS^8S?A{6&@O-bu>JPsuk@r!yIj%cd^Mix`mzck<*b)P+?>+ zNL8wtEWl&JSv9p8NOj6rMWWsn@O?$a-MMPD45FPwxGjg$LH`CIV%a6TbG~mpCDdle zSpR`raU#qPVDoC3coZ;cq32T5DfyRTN3rwuz-cHS0Po9uWE+OZY}|d=2y)39%G{}o zT&9cjO~L@PNyaZc?Ud${Q88!UNp0~v=?)N~4q-H6 z36%5DGQ5XsOXSqHP@vPFRb50BGItx+GhF$!s3$~ma}RXj)h&X;SvsU(W~fSD1NorA z2{!ooK@%pl3C-%e<8bRVO0?fkeg(*W6IV#)A!4TPxHm(|cnsX3*tsOCkrW9gkPK33 zdHyyx=#^IsL6oiGQOrD$rOe);Advu1oGU4K$N`fcvyq?(SL8CJGauWFT($%o#liZ? zkA{XMFiRtM$bA?C5vC}ol($Zpo23yh7g(J%h!JZMhGG-Bq_gIgU8__J1&RRZ$C^?{ z)mc3%@*yYqnn*^)JE9x+D8NMSglFqh&(4$CjeKn(F4)C^{Moz7`H$+&2=H!AVdK>s za9Vao)YYgw<+qIG-z7kemzeK4b;XvstX7Y8iPqIgA%VIMTN7;Roh8lVZn+@anKs@f zAHSyl^v2w#1v#isYfS&C5x?nCE9c+u{}(K~!qGliQ@B*RSNMY6eC|BC9l_A4PWiM< z+(do%9ZhW99LcMSV0b=M;>9}i=fvc zUDPydyccqs@q+|En4Mj9^poyKQ3rx0}FPRG_AGB{JIPtP3 zF|QJO7g(%6JL3&b5b8&(N0SktLErHP7Y0_bmG;_vR3r