fix: commit step context before request dispatch
This commit is contained in:
@@ -58,6 +58,7 @@ function preparing(turn: number, step: number): Session {
|
||||
session.append('step/start', { turn, step: priorStep })
|
||||
session.append('step/end', { turn, step: priorStep })
|
||||
}
|
||||
session.append('step/start', { turn, step })
|
||||
return session
|
||||
}
|
||||
|
||||
@@ -92,8 +93,8 @@ describe('time-context invariants', () => {
|
||||
content: [{ type: 'text', text: 'prepare' }],
|
||||
source: { kind: 'user' },
|
||||
}), { surfaceOp: 'append' })
|
||||
appendReading(session, reading())
|
||||
session.append('step/start', { turn: 1, step: 1 })
|
||||
appendReading(session, reading())
|
||||
|
||||
await ctx.plugin(InvariantService, { enabled: true })
|
||||
await expect(ctx.plugin(TimeInvariant)).resolves.toBeDefined()
|
||||
@@ -108,6 +109,7 @@ describe('time-context invariants', () => {
|
||||
content: [{ type: 'text', text: 'prepare' }],
|
||||
source: { kind: 'user' },
|
||||
}), { surfaceOp: 'append' })
|
||||
session.append('step/start', { turn: 1, step: 1 })
|
||||
appendReading(session, reading('1', '2', 'step context'))
|
||||
|
||||
await ctx.plugin(InvariantService, { enabled: true })
|
||||
@@ -127,17 +129,17 @@ describe('time-context invariants', () => {
|
||||
const session = preparing(1, 2)
|
||||
session.append('turn/end', { turn: 1, reason: { kind: 'aborted', reason: { kind: 'user' } } })
|
||||
expect(() => { ctx.emit('session/event', session, event(reading('1', '2', 'step context'))) })
|
||||
.toThrow(/inside an open turn/)
|
||||
.toThrow(/inside an open step/)
|
||||
})
|
||||
|
||||
it('rejects a reading after step/start or without any open turn', async () => {
|
||||
it('rejects a reading outside an open step', async () => {
|
||||
const ctx = await setup()
|
||||
const started = preparing(1, 1)
|
||||
started.append('step/start', { turn: 1, step: 1 })
|
||||
expect(() => { ctx.emit('session/event', started, event(reading())) }).toThrow(/must precede step\/start/)
|
||||
const ended = preparing(1, 1)
|
||||
ended.append('step/end', { turn: 1, step: 1 })
|
||||
expect(() => { ctx.emit('session/event', ended, event(reading())) }).toThrow(/inside an open step/)
|
||||
expect(() => {
|
||||
ctx.emit('session/event', new Session(SessionId('time-invariant-empty')), event(reading()))
|
||||
}).toThrow(/inside an open turn/)
|
||||
}).toThrow(/inside an open step/)
|
||||
})
|
||||
|
||||
it.each([
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('time-context through a real headless cordis.yml', () => {
|
||||
expect(contexts).toHaveLength(2)
|
||||
expect(starts).toHaveLength(2)
|
||||
for (let index = 0; index < contexts.length; index += 1) {
|
||||
expect(contexts[index]!.seq).toBeLessThan(starts[index]!.seq)
|
||||
expect(contexts[index]!.seq).toBeGreaterThan(starts[index]!.seq)
|
||||
expect(contexts[index]!.surfaceOp).toBe('append')
|
||||
expect(contexts[index]!.data.source).toEqual({ kind: 'plugin', plugin: 'time-context' })
|
||||
}
|
||||
|
||||
@@ -45,9 +45,7 @@ function sessionAgent(session: Session, id = 'agent'): Agent {
|
||||
ctx: new Context(),
|
||||
followup: () => {},
|
||||
steer: () => {},
|
||||
inject(input) {
|
||||
session.append('user/message', input, { surfaceOp: 'append' })
|
||||
},
|
||||
inject: () => { throw new Error('time-context must append directly to the open step') },
|
||||
cancel() {},
|
||||
whenIdle: () => Promise.resolve(),
|
||||
}
|
||||
@@ -358,7 +356,7 @@ describe('real agent-loop request history', () => {
|
||||
it.each([
|
||||
['throws', 'error'],
|
||||
['cancels', 'aborted'],
|
||||
] as const)('discards the pending preparation reading when a later step listener %s', async (mode, reasonKind) => {
|
||||
] as const)('retains the durable preparation reading when a later step listener %s', async (mode, reasonKind) => {
|
||||
const adapter = new ScriptedAdapter([textResponse('unused')])
|
||||
const ctx = await loopHarness(adapter)
|
||||
let laterSawReading = false
|
||||
@@ -372,10 +370,10 @@ describe('real agent-loop request history', () => {
|
||||
agent.followup(createUserMessage({ content: [{ type: 'text', text: 'start' }], source: { kind: 'user' } }))
|
||||
await agent.whenIdle()
|
||||
|
||||
expect(laterSawReading).toBe(false)
|
||||
expect(contextTexts(agent.session)).toHaveLength(0)
|
||||
expect(laterSawReading).toBe(true)
|
||||
expect(contextTexts(agent.session)).toHaveLength(1)
|
||||
expect(adapter.requests).toHaveLength(0)
|
||||
expect(agent.session.events.some(event => event.type === 'step/start')).toBe(false)
|
||||
expect(agent.session.events.some(event => event.type === 'step/start')).toBe(true)
|
||||
const turnEnd = agent.session.events.findLast(event => event.type === 'turn/end')
|
||||
expect(turnEnd?.type === 'turn/end' && turnEnd.data.reason.kind).toBe(reasonKind)
|
||||
await ctx.fiber.dispose()
|
||||
@@ -405,7 +403,7 @@ describe('real agent-loop request history', () => {
|
||||
expect(contexts).toHaveLength(adapter.requests.length)
|
||||
expect(starts).toHaveLength(adapter.requests.length)
|
||||
for (let index = 0; index < contexts.length; index += 1) {
|
||||
expect(contexts[index]!.seq).toBeLessThan(starts[index]!.seq)
|
||||
expect(contexts[index]!.seq).toBeGreaterThan(starts[index]!.seq)
|
||||
}
|
||||
expect(contexts.every(event => event.data.source.kind === 'plugin'
|
||||
&& event.data.source.plugin === 'time-context'
|
||||
|
||||
Reference in New Issue
Block a user