fix(subagent): cancel pending live delivery

This commit is contained in:
Dudu-0223
2026-07-27 19:49:22 +08:00
committed by imccyu
parent fea31a012d
commit 644bf00b86
19 changed files with 320 additions and 65 deletions

View File

@@ -1,4 +1,4 @@
import { afterEach, describe, expect, it } from 'vitest'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
@@ -34,9 +34,10 @@ async function setup(script: ConstructorParameters<typeof MockAdapter>[0]) {
await ctx.plugin(LocalTaskService)
await ctx.plugin(ToolTasks, {})
await ctx.plugin(tool)
ctx.llm.registerAdapter(['mock'], new MockAdapter(script))
const adapter = new MockAdapter(script)
ctx.llm.registerAdapter(['mock'], adapter)
const parent = ctx.agentLoop.create(SessionId('parent'), { provider: 'mock', model: 'mock' })
return { ctx, parent }
return { ctx, parent, adapter }
}
function text(result: { content: { type: string; text?: string }[] }): string {
@@ -44,9 +45,15 @@ function text(result: { content: { type: string; text?: string }[] }): string {
}
let calls = 0
function callTool(ctx: Context, name: string, args: unknown, agent?: unknown) {
function callTool(
ctx: Context,
name: string,
args: unknown,
agent?: unknown,
signal: AbortSignal = testToolSignal,
) {
return ctx.tools.execute({
signal: testToolSignal,
signal,
callId: CallId(`call-${++calls}`),
name,
arguments: args,
@@ -114,6 +121,40 @@ describe('dsh-tool-subagent-control', () => {
expect(text(result)).toBe('message delivered to running task subagent-9')
})
it('cancels a pending live-delivery wait when the tool signal aborts', async () => {
const { ctx, parent, adapter } = await setup(['hang'])
const started = ctx.subagents.startContinuable({
provider: 'spawn',
label: 'hung work',
request: { prompt: [{ type: 'text', text: 'wait' }], parent },
})
await vi.waitFor(() => { expect(adapter.requests).toHaveLength(1) })
const deliveryStarted: PromiseWithResolvers<void> = Promise.withResolvers()
const sendMessage = ctx.subagents.sendMessage.bind(ctx.subagents)
ctx.subagents.sendMessage = (agent, childId, message, source, signal) => {
const delivery = sendMessage(agent, childId, message, source, signal)
deliveryStarted.resolve()
return delivery
}
const controller = new AbortController()
const execution = callTool(ctx, 'send_message', {
subagent_id: started.childId,
message: 'follow up',
}, parent, controller.signal)
await deliveryStarted.promise
controller.abort('parent tool cancelled')
const result = await execution
expect(result.isError).toBe(true)
expect(result.error?.info?.code).toBe('CANCELLED')
expect(ctx.agents.get(started.childId)).toBeUndefined()
const snapshot = await ctx.tasks.wait(started.taskId, 5_000, parent)
expect(snapshot.status).toBe('killed')
const loaded = await ctx.sessionPersistence.load(started.childId)
expect(loaded.events.some(event => event.type === 'steering/message')).toBe(false)
})
it('reports a delivery failure as an errored, not-delivered result', async () => {
const { ctx, parent } = await setup([])
const result = await callTool(ctx, 'send_message', {