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
@@ -266,8 +266,7 @@ describe('same-session goal driving', () => {
expect(goal?.roundsStarted).toBe(0)
expect(goal?.blockedReason).toEqual({ code: 'prompt-rejected', message: 'deployment policy' })
expect(test.adapter.requests).toHaveLength(0)
expect(test.agent.session.events.some(event => event.type === 'prompt/blocked'
&& event.data.reason === 'deployment policy')).toBe(true)
expect(test.agent.session.events.some(event => event.type === 'turn/start')).toBe(false)
})
it('does not reserve again when a stopped-goal observer queues ordinary work', async () => {
@@ -391,9 +390,6 @@ describe('same-session goal driving', () => {
const goal = await waitForGoal(test.ctx, test.agent, current => current?.phase === 'blocked')
expect(goal).toMatchObject({ revision: 3, objective: 'new objective', roundsStarted: 1 })
const blocked = test.agent.session.events.find(event => event.type === 'prompt/blocked')
expect(blocked?.type === 'prompt/blocked' ? blocked.data.reason : undefined)
.toBe('stale goal-round reservation')
const admitted = test.agent.session.events.find(event => event.type === 'user/message'
&& event.data.source.kind === 'goal' && event.data.source.round > 0)
expect(admitted?.type === 'user/message' && admitted.data.source.kind === 'goal'
@@ -419,8 +415,6 @@ describe('same-session goal driving', () => {
expect(goal).toMatchObject({ objective: 'edited downstream', roundsStarted: 1 })
expect(test.adapter.requests).toHaveLength(1)
expect(test.agent.session.events.some(event => event.type === 'prompt/blocked'
&& event.data.reason === 'stale goal-round reservation')).toBe(true)
})
it('disarms without dispatch when a durability checkpoint fails', async () => {
@@ -447,38 +441,6 @@ describe('same-session goal driving', () => {
expect(test.adapter.requests).toHaveLength(0)
})
it('disarms an admitted round when a later injection hides its failed closing checkpoint', async () => {
const test = await harness([textResponse('not durable')])
let injected = false
test.ctx.on('session/flush', (session) => {
const lastStart = session.events.findLast(event => event.type === 'turn/start')
if (lastStart?.type === 'turn/start' && lastStart.data.trigger.kind === 'message'
&& lastStart.data.trigger.source.kind === 'goal' && !injected) {
injected = true
test.agent.inject([{ type: 'text', text: 'concurrent completion notice' }], {
source: { kind: 'plugin', plugin: 'test' },
})
return Promise.reject(new Error('round flush failed'))
}
})
test.ctx.goals.create(test.agent, { objective: 'checkpoint the result' })
const goal = await waitForGoal(
test.ctx,
test.agent,
current => current?.roundsStarted === 1 && current.activation === 'disarmed',
)
expect(goal?.phase).toBe('active')
expect(test.adapter.requests).toHaveLength(1)
const turns = test.agent.session.events.filter(event => event.type === 'turn/start')
const goalTurn = turns.findIndex(event => event.data.trigger.kind === 'message'
&& event.data.trigger.source.kind === 'goal')
const injectedTurn = turns.findIndex(event => event.data.trigger.kind === 'injection'
&& event.data.trigger.source.kind === 'plugin')
expect(injectedTurn).toBeGreaterThan(goalTurn)
})
it('blocks the goal when a custom agent rejects the otherwise valid follow-up', async () => {
const test = await harness([])
// Reject only the goal-sourced round follow-up, not the state-change injection
@@ -520,25 +482,6 @@ describe('same-session goal driving', () => {
expect(test.adapter.requests).toHaveLength(0)
})
it('contains a driver read failure and removes continuation authority', async () => {
const test = await harness([])
let flushes = 0
test.ctx.on('session/flush', () => {
flushes += 1
if (flushes !== 2) return
vi.spyOn(test.ctx.goals, 'get').mockImplementationOnce(() => {
throw new Error('corrupt projection')
})
})
test.ctx.goals.create(test.agent, { objective: 'fail the driver closed' })
await new Promise<void>((resolve) => { setImmediate(resolve) })
const goal = test.ctx.goals.get(test.agent)
expect(goal?.phase).toBe('active')
expect(test.adapter.requests).toHaveLength(0)
})
it('contains synchronous scheduler startup failure', async () => {
const test = await harness([])
vi.spyOn(test.ctx.agents, 'withoutInitiator').mockImplementationOnce(() => {
@@ -583,8 +526,6 @@ describe('same-session goal driving', () => {
await waitForGoal(test.ctx, test.agent, goal => goal?.phase === 'blocked')
expect(test.adapter.requests).toHaveLength(1)
expect(test.agent.session.events.some(event => event.type === 'prompt/blocked'
&& event.data.reason === 'stale goal-round reservation')).toBe(true)
})
it('fails a post-hook read closed before the prompt can enter history', async () => {
@@ -615,8 +556,7 @@ describe('same-session goal driving', () => {
await test.agent.whenIdle()
expect(test.adapter.requests).toHaveLength(0)
expect(test.agent.session.events.some(event => event.type === 'prompt/blocked'
&& event.data.reason === 'stale goal-round reservation')).toBe(true)
expect(test.agent.session.events.some(event => event.type === 'turn/start')).toBe(false)
})
it('does not invent goal state when ordinary queued work is cancelled', async () => {
@@ -715,9 +655,9 @@ describe('same-session goal driving', () => {
expect(test.ctx.goals.get(test.agent)).toMatchObject({
phase: 'active',
activation: 'disarmed',
roundsStarted: 1,
roundsStarted: 0,
})
expect(test.adapter.requests).toHaveLength(1)
expect(test.adapter.requests).toHaveLength(0)
})
it('resets process-local scheduling state at a session-start edge', async () => {