refactor(agent-loop): simplify message machine
This commit is contained in:
@@ -12,13 +12,6 @@ import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type { TurnEndReason } from '@deepseek-ai/dsh-session'
|
||||
import * as goalSession from '../src/index.ts'
|
||||
|
||||
declare module '@deepseek-ai/dsh-session' {
|
||||
interface TurnTriggerMap {
|
||||
/** Test-only plugin turn with no message source. */
|
||||
'test-metadata': { kind: 'test-metadata' }
|
||||
}
|
||||
}
|
||||
|
||||
type ScriptEntry = StreamChunk[] | Error | 'hang' | ((options: GenerateOptions) => StreamChunk[])
|
||||
|
||||
/** Small request-recording adapter with controllable failure and cancellation. */
|
||||
@@ -334,31 +327,6 @@ describe('same-session goal driving', () => {
|
||||
expect(requestText(test.adapter.requests[1]!)).toContain('<goal_round>')
|
||||
})
|
||||
|
||||
it('ignores plugin-owned turn triggers while a goal round is queued', async () => {
|
||||
const test = await harness([textResponse('goal answer')])
|
||||
const warnings: string[] = []
|
||||
test.ctx.logger.warn = ((message: unknown) => { warnings.push(String(message)) }) as typeof test.ctx.logger.warn
|
||||
let inserted = false
|
||||
test.ctx.on('agent/inbox/enqueue', (agent, info) => {
|
||||
if (agent !== test.agent || info.source.kind !== 'goal' || inserted) return
|
||||
inserted = true
|
||||
const lastStart = agent.session.events.findLast(event => event.type === 'turn/start')
|
||||
const turn = (lastStart?.data.turn ?? 0) + 1
|
||||
agent.session.append('turn/start', {
|
||||
turn,
|
||||
trigger: { kind: 'test-metadata' },
|
||||
})
|
||||
agent.session.append('turn/end', { turn, reason: { kind: 'completed' } })
|
||||
})
|
||||
test.ctx.goals.create(test.agent, { objective: 'ignore metadata', maxGoalRounds: 1 })
|
||||
|
||||
await waitForGoal(test.ctx, test.agent, goal => goal?.phase === 'blocked')
|
||||
|
||||
expect(inserted).toBe(true)
|
||||
expect(test.adapter.requests).toHaveLength(1)
|
||||
expect(warnings.some(warning => warning.includes('session/event listener threw'))).toBe(false)
|
||||
})
|
||||
|
||||
it('makes a reserved round stale when a listener queues human work behind it', async () => {
|
||||
const test = await harness([textResponse('human batch'), textResponse('later goal')])
|
||||
let inserted = false
|
||||
@@ -908,8 +876,7 @@ describe('same-session goal driving', () => {
|
||||
let queued = false
|
||||
test.ctx.on('session/event', (session, event) => {
|
||||
if (session !== test.agent.session || queued) return
|
||||
if (event.type === 'turn/start' && event.data.trigger.kind === 'message'
|
||||
&& event.data.trigger.source.kind === 'goal') {
|
||||
if (event.type === 'user/message' && event.data.source.kind === 'goal') {
|
||||
queued = true
|
||||
test.agent.followup(createUserMessage({ content: [{ type: 'text', text: 'human interleaved' }], source: { kind: 'user' } }))
|
||||
}
|
||||
@@ -997,7 +964,6 @@ describe('same-session goal driving', () => {
|
||||
const orphan = test.ctx.sessions.create(SessionId('goal-session-orphan'))
|
||||
orphan.append('turn/start', {
|
||||
turn: 1,
|
||||
trigger: { kind: 'injection', source: { kind: 'plugin', plugin: 'test' } },
|
||||
})
|
||||
orphan.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ function view(roundsStarted: number): GoalView {
|
||||
}
|
||||
|
||||
function appendChange(session: Session): void {
|
||||
session.append('turn/start', { turn: 1, trigger: { kind: 'injection', source: changeSource } })
|
||||
session.append('turn/start', { turn: 1 })
|
||||
session.append('user/message', createUserMessage({
|
||||
content: renderGoalChange(change),
|
||||
source: changeSource,
|
||||
@@ -51,7 +51,7 @@ function appendChange(session: Session): void {
|
||||
|
||||
function appendRound(session: Session, turn: number, content = renderGoalRoundPrompt(view(turn - 2), turn - 1)): void {
|
||||
const source = { kind: 'goal', goalId: change.goal.id, revision: 1, round: turn - 1 } as const
|
||||
session.append('turn/start', { turn, trigger: { kind: 'message', source } })
|
||||
session.append('turn/start', { turn })
|
||||
session.append('user/message', createUserMessage({
|
||||
content, source,
|
||||
}), { surfaceOp: 'append' })
|
||||
@@ -82,7 +82,7 @@ describe('goal-session prompt invariants', () => {
|
||||
ctx.sessions.create(SessionId('goal-session-invariant-dispatch'))
|
||||
|
||||
const userSource = { kind: 'user' } as const
|
||||
session.append('turn/start', { turn: 4, trigger: { kind: 'message', source: userSource } })
|
||||
session.append('turn/start', { turn: 4 })
|
||||
session.append('user/message', createUserMessage({
|
||||
content: [{ type: 'text', text: 'ordinary human message' }],
|
||||
source: userSource,
|
||||
@@ -90,7 +90,7 @@ describe('goal-session prompt invariants', () => {
|
||||
session.append('turn/end', { turn: 4, reason: { kind: 'completed' } })
|
||||
|
||||
const stateSource = { ...changeSource, round: 0 } as const
|
||||
session.append('turn/start', { turn: 5, trigger: { kind: 'message', source: stateSource } })
|
||||
session.append('turn/start', { turn: 5 })
|
||||
expect(() => {
|
||||
session.append('user/message', createUserMessage({
|
||||
content: [{ type: 'text', text: 'round zero is not a driver continuation' }],
|
||||
@@ -114,7 +114,7 @@ describe('goal-session prompt invariants', () => {
|
||||
it('rejects a goal round without a reconstructable active goal', async () => {
|
||||
const { session } = await mount()
|
||||
const source = { kind: 'goal', goalId: change.goal.id, revision: 1, round: 1 } as const
|
||||
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source } })
|
||||
session.append('turn/start', { turn: 1 })
|
||||
|
||||
expect(() => {
|
||||
session.append('user/message', createUserMessage({
|
||||
@@ -128,7 +128,7 @@ describe('goal-session prompt invariants', () => {
|
||||
|
||||
it('attributes an invalid durable prefix during late loading', async () => {
|
||||
const { ctx, session } = await mount(true)
|
||||
session.append('turn/start', { turn: 1, trigger: { kind: 'injection', source: changeSource } })
|
||||
session.append('turn/start', { turn: 1 })
|
||||
session.append('user/message', createUserMessage({
|
||||
content: [{ type: 'text', text: 'counterfeit goal state' }],
|
||||
source: changeSource,
|
||||
|
||||
Reference in New Issue
Block a user