fix(subagent): close continuation lifecycle gaps

This commit is contained in:
Dudu-0223
2026-07-30 21:21:14 +08:00
committed by Tianyi Cui
parent 853f4d5cfb
commit a91b20f6be
29 changed files with 365 additions and 142 deletions

View File

@@ -681,12 +681,11 @@ export function defineCoverageCases(group: CoverageGroup): void {
})
it('runs a SubagentStop hook in the CHILD session workspace, not the server cwd', async () => {
// `SubagentStop` recovers the child at `subagent/end`; a relative marker proves `runPoint`
// receives that agent and runs in the child's cwd rather than the executor default.
const serverDir = dir()
const childDir = dir()
const marker = join(childDir, 'stopwhere')
hooks(serverDir, { SubagentStop: [{ hooks: [{ type: 'command', command: 'pwd > stopwhere' }] }] })
const payload = join(childDir, 'stoppayload')
hooks(serverDir, { SubagentStop: [{ hooks: [{ type: 'command', command: 'cat > stoppayload.tmp; mv stoppayload.tmp stoppayload; pwd > stopwhere' }] }] })
const ctx = new Context()
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(AgentLoop, { agents: [] })
@@ -698,15 +697,22 @@ export function defineCoverageCases(group: CoverageGroup): void {
const { SessionId } = await import('@deepseek-ai/dsh-session')
const childHandle = await ctx.agents.create({ sessionId: SessionId('child-stop-session'), meta: { cwd: childDir }, agentOptions: { provider: 'mock', model: 'mock' } })
ctx.emit(subagentCarrier(ctx), 'subagent/end', { runId: SubagentRunId('run-stop'), provider: 'inproc', id: childHandle.agent.id, local: true, stopReason: 'completed' })
const runId = SubagentRunId('run-stop')
const identity = { runId, provider: 'inproc', id: childHandle.agent.id, local: true }
// Start is the registry-backed capture edge; end deliberately follows
// handle disposal, matching continuable Activation settlement.
ctx.emit(subagentCarrier(ctx), 'subagent/start', identity)
await childHandle.dispose()
expect(ctx.agents.get(childHandle.agent.id)).toBeUndefined()
ctx.emit(subagentCarrier(ctx), 'subagent/end', { ...identity, stopReason: 'completed' })
await waitFor(() => existsSync(marker))
expect(existsSync(marker)).toBe(true) // the marker landed in the CHILD dir
const { readFileSync } = await import('node:fs')
const where = readFileSync(marker, 'utf8').trim()
const input = JSON.parse(readFileSync(payload, 'utf8')) as { cwd: string; session_id: string }
// `pwd` may resolve symlinks (/var → /private/var etc.), so compare basenames.
expect(where.endsWith(childDir.split('/').pop()!)).toBe(true)
await childHandle.dispose()
expect(input).toMatchObject({ cwd: childDir, session_id: childHandle.agent.id })
})
})