fix(subagent): identify lifecycle runs explicitly
This commit is contained in:
@@ -9,6 +9,7 @@ import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
|
||||
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
|
||||
import { SubagentRunId } from '@deepseek-ai/dsh-subagent'
|
||||
import * as HooksClaude from '@deepseek-ai/dsh-hooks-claude'
|
||||
import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
|
||||
|
||||
@@ -288,8 +289,8 @@ describe('hooks-claude bridge — SubagentStart / SubagentStop (observe)', () =>
|
||||
// Drive the observe-only lifecycle events directly (no real child needed — the
|
||||
// bridge just listens). No child agent is registered, so SubagentStart's
|
||||
// child lookup yields undefined and it simply runs the hook.
|
||||
ctx.emit('subagent/start', { provider: 'inproc', id: SessionId('child-1') })
|
||||
ctx.emit('subagent/end', { provider: 'inproc', id: SessionId('child-1'), stopReason: 'completed', lastAssistantMessage: [{ type: 'text', text: 'done' }] })
|
||||
ctx.emit('subagent/start', { runId: SubagentRunId('run-1'), provider: 'inproc', id: SessionId('child-1'), local: false })
|
||||
ctx.emit('subagent/end', { runId: SubagentRunId('run-1'), provider: 'inproc', id: SessionId('child-1'), local: false, stopReason: 'completed', lastAssistantMessage: [{ type: 'text', text: 'done' }] })
|
||||
|
||||
// Both hooks run async (detached .then); poll for their marker files rather
|
||||
// than a fixed sleep that flakes under load.
|
||||
@@ -324,7 +325,7 @@ describe('hooks-claude bridge — SubagentStart / SubagentStop (observe)', () =>
|
||||
const { ctx, hooks } = await harnessWithFiber(dir, new MockAdapter([]))
|
||||
const warn = vi.fn()
|
||||
ctx.logger.warn = warn as never
|
||||
ctx.emit('subagent/start', { provider: 'inproc', id: SessionId('child-1') })
|
||||
ctx.emit('subagent/start', { runId: SubagentRunId('run-1'), provider: 'inproc', id: SessionId('child-1'), local: false })
|
||||
await waitFor(() => existsSync(marker))
|
||||
const pid = Number(readFileSync(pidFile, 'utf8').trim())
|
||||
await hooks.dispose()
|
||||
|
||||
@@ -9,6 +9,7 @@ import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
|
||||
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
|
||||
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
|
||||
import { SubagentRunId } from '@deepseek-ai/dsh-subagent'
|
||||
import * as HooksClaude from '@deepseek-ai/dsh-hooks-claude'
|
||||
import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
|
||||
|
||||
@@ -231,7 +232,7 @@ export function defineCoverageCases(group: CoverageGroup): void {
|
||||
const injected: string[] = []
|
||||
const child = { id: SessionId('child-x'), inject: (content: { type: string; text?: string }[]) => { injected.push(content.map(b => b.text ?? '').join('')) }, session: { id: SessionId('child-x'), header: { id: 'child-x' } } } as unknown as Parameters<typeof ctx.agents.register>[0]
|
||||
ctx.agents.register(child)
|
||||
ctx.emit('subagent/start', { provider: 'p', id: SessionId('child-x') })
|
||||
ctx.emit('subagent/start', { runId: SubagentRunId('run-x'), provider: 'p', id: SessionId('child-x'), local: true })
|
||||
await waitFor(() => injected.includes('child guidance'))
|
||||
expect(injected).toContain('child guidance')
|
||||
})
|
||||
@@ -247,7 +248,7 @@ export function defineCoverageCases(group: CoverageGroup): void {
|
||||
const warn = vi.fn(); ctx.logger.warn = warn as never
|
||||
const child = { id: SessionId('child-y'), inject: () => { throw new Error('inject boom') }, session: { id: SessionId('child-y'), header: { id: 'child-y' } } } as unknown as Parameters<typeof ctx.agents.register>[0]
|
||||
ctx.agents.register(child)
|
||||
ctx.emit('subagent/start', { provider: 'p', id: SessionId('child-y') })
|
||||
ctx.emit('subagent/start', { runId: SubagentRunId('run-y'), provider: 'p', id: SessionId('child-y'), local: true })
|
||||
await waitFor(() => warn.mock.calls.some(c => String(c[0]).includes('SubagentStart hook failed')))
|
||||
expect(warn).toHaveBeenCalledWith(expect.stringContaining('SubagentStart hook failed'))
|
||||
})
|
||||
@@ -291,7 +292,7 @@ export function defineCoverageCases(group: CoverageGroup): void {
|
||||
const s = sh(d, 'stop.sh', `#!/usr/bin/env bash\ntouch "${marker}"\n`)
|
||||
const path = hooks(d, { SubagentStop: [{ hooks: [{ type: 'command', command: s }] }] })
|
||||
const ctx = await harness(path, new MockAdapter([]))
|
||||
ctx.emit('subagent/end', { provider: 'p', id: SessionId('child-z'), stopReason: 'completed' })
|
||||
ctx.emit('subagent/end', { runId: SubagentRunId('run-z'), provider: 'p', id: SessionId('child-z'), local: false, stopReason: 'completed' })
|
||||
await waitFor(() => existsSync(marker))
|
||||
expect(existsSync(marker)).toBe(true)
|
||||
})
|
||||
@@ -691,7 +692,7 @@ export function defineCoverageCases(group: CoverageGroup): void {
|
||||
// Register a live child on its own session cwd; emit subagent/end with its id.
|
||||
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('subagent/end', { provider: 'inproc', id: childHandle.agent.id, stopReason: 'completed' })
|
||||
ctx.emit('subagent/end', { runId: SubagentRunId('run-stop'), provider: 'inproc', id: childHandle.agent.id, local: true, stopReason: 'completed' })
|
||||
|
||||
await waitFor(() => existsSync(marker))
|
||||
expect(existsSync(marker)).toBe(true) // the marker landed in the CHILD dir
|
||||
|
||||
Reference in New Issue
Block a user