refactor(agent-loop): simplify observable state machine

This commit is contained in:
_Kerman
2026-07-24 21:18:48 +08:00
parent fb0ef82aa6
commit b73eb7663c
131 changed files with 2011 additions and 4292 deletions

View File

@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import { Context, symbols, type EffectMeta } from 'cordis'
import Loader from '@cordisjs/plugin-loader'
import AgentRegistry from '@deepseek-ai/dsh-agent'
import AgentRegistry, { type Agent } from '@deepseek-ai/dsh-agent'
import { SessionId } from '@deepseek-ai/dsh-session'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
@@ -52,6 +52,17 @@ function start(ctx: Context, provider: string, request: Omit<SubagentStartReques
return ctx.subagents.start(provider, { signal: request.signal ?? new AbortController().signal, ...request })
}
/** Invoke the child lifecycle effect while its parent-owned setup is still unpublished. */
function disposeChildLifecycle(parent: Agent): void {
const lifecycle = [...parent.ctx.fiber._disposables]
.find((dispose) => {
const effect = (dispose as typeof dispose & { [symbols.effect]?: EffectMeta })[symbols.effect]
return effect?.label.startsWith('agentLoop.lifecycle(') === true
})
if (lifecycle === undefined) throw new Error('child lifecycle effect not found')
void lifecycle()
}
describe('dsh-subagent-spawn', () => {
it('runs a fresh child to completion and returns its final assistant output', async () => {
// One model call for the child: a plain text answer.
@@ -460,6 +471,12 @@ describe('dsh-subagent-spawn', () => {
ctx.on('session/created', () => void published.push('session/created'))
ctx.on('agent/created', () => void published.push('agent/created'))
ctx.on('agent/session-start', () => void published.push('agent/session-start'))
let teardownStarted = false
ctx.on('internal/plugin', (fiber) => {
if (teardownStarted || fiber.name !== 'scope') return
teardownStarted = true
disposeChildLifecycle(parentHandle.agent)
})
const starting = start(ctx, 'spawn', {
prompt: [{ type: 'text', text: 'must never run' }],
@@ -468,8 +485,8 @@ describe('dsh-subagent-spawn', () => {
// The factory has entered its awaited unpublished setup transaction. The
// parent context owns that transaction, so disposal wins without an
// observer ever seeing the child.
await parentHandle.dispose()
await expect(starting).rejects.toThrow(/owner disposed during setup|inactive context/)
await parentHandle.dispose()
expect(published).toEqual([])
})