refactor(agent-loop): simplify observable state machine
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import { mkdtemp } from 'node:fs/promises'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { Context } from 'cordis'
|
||||
import Loader from '@cordisjs/plugin-loader'
|
||||
import { agentEvents, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { agentEvents } from '@deepseek-ai/dsh-agent'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { CallId, type Message } from '@deepseek-ai/dsh-llm'
|
||||
import { TOOL_ORDER_REST } from '@deepseek-ai/dsh-system-prompt'
|
||||
import type { ToolExecution } from '@deepseek-ai/dsh-tools'
|
||||
@@ -39,12 +41,9 @@ async function mount(config: cliDemo.Config, withBash = false): Promise<Context>
|
||||
}
|
||||
|
||||
async function composePrefix(ctx: Context): Promise<Message[]> {
|
||||
const agent = { session: { header: { cwd: '/tmp' } } } as unknown as Agent
|
||||
const empty: Message[] = []
|
||||
return await agentEvents(ctx, agent).waterfall(
|
||||
'agent/session-prefix', empty, new AbortController().signal,
|
||||
() => Promise.resolve(empty),
|
||||
)
|
||||
const agent = ctx.agentLoop.create(SessionId(`cli-demo-prefix-${randomUUID()}`), {}, { cwd: '/tmp' })
|
||||
await agentEvents(ctx, agent).serial('agent/step', 1, 1, new AbortController().signal)
|
||||
return agent.session.deriveMessages()
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
|
||||
@@ -319,7 +319,7 @@ describe('runOneShot and executeCli', () => {
|
||||
const { ctx, agent, persistenceRoot } = await harness([textResponse('final answer')])
|
||||
const output = await invoke(ctx, ['task'])
|
||||
expect(output).toEqual({ code: 0, stdout: 'final answer\n', stderr: '' })
|
||||
expect(agent.status).toBe('disposed')
|
||||
expect(agent.status).toBe('idle')
|
||||
const files = await readdir(persistenceRoot, { recursive: true })
|
||||
expect(files.some(file => file.endsWith('.jsonl.zstd'))).toBe(true)
|
||||
})
|
||||
@@ -379,11 +379,13 @@ describe('runOneShot and executeCli', () => {
|
||||
const output = await invoke(ctx, ['--output-format', 'stream-json', 'task'])
|
||||
const lines = output.stdout.trimEnd().split('\n').map(line => JSON.parse(line) as Record<string, unknown>)
|
||||
const events = lines.slice(0, -1).map(line => line['event'] as SessionEvent)
|
||||
expect(lines.at(-1)).toMatchObject({ type: 'result', success: true, turn: 2, result: 'streamed' })
|
||||
expect(events[0]).toMatchObject({ type: 'turn/start', data: { turn: 2, trigger: { kind: 'message' } } })
|
||||
expect(events.at(-1)).toMatchObject({ type: 'turn/end', data: { turn: 2 } })
|
||||
expect(lines.at(-1)).toMatchObject({ type: 'result', success: true, turn: 1, result: 'streamed' })
|
||||
expect(events[0]).toMatchObject({ type: 'turn/start', data: { turn: 1, trigger: { kind: 'message' } } })
|
||||
expect(events.at(-1)).toMatchObject({ type: 'turn/end', data: { turn: 1 } })
|
||||
expect(lines.slice(0, -1).every(line => line['sessionId'] === agent.session.id)).toBe(true)
|
||||
expect(events.some(event => event.type === 'user/message' && event.data.source.kind !== 'user')).toBe(false)
|
||||
expect(events.some(event => event.type === 'user/message'
|
||||
&& event.data.source.kind === 'plugin'
|
||||
&& event.data.source.plugin === 'test')).toBe(false)
|
||||
})
|
||||
|
||||
it('emits partial data and a diagnostic for non-completed turns', async () => {
|
||||
@@ -409,7 +411,7 @@ describe('runOneShot and executeCli', () => {
|
||||
expect(JSON.parse(output.stdout)).toMatchObject({ success: false, reason: { kind: 'aborted' } })
|
||||
expect(output.code).toBe(1)
|
||||
expect(output.stderr).toContain('turn 1 was aborted')
|
||||
expect(agent.status).toBe('disposed')
|
||||
expect(agent.status).toBe('idle')
|
||||
})
|
||||
|
||||
it('contains stream-writer failures, cancels, flushes, and returns the output error', async () => {
|
||||
@@ -444,7 +446,7 @@ describe('runOneShot and executeCli', () => {
|
||||
expect(output.code).toBe(1)
|
||||
expect(output.stdout).toBe('')
|
||||
expect(output.stderr).toContain('stdout closed')
|
||||
expect(final.agent.status).toBe('disposed')
|
||||
expect(final.agent.status).toBe('idle')
|
||||
|
||||
const disposal = await harness([textResponse('answer')])
|
||||
const disposalOutput = await invoke(disposal.ctx, ['task'], { failDispose: true })
|
||||
|
||||
Reference in New Issue
Block a user