refactor(agent): simplify inbox-driven turn admission
This commit is contained in:
@@ -11,7 +11,7 @@ import type { CompactionResult, CompactionTrigger } from '@deepseek-ai/dsh-compa
|
||||
import type { TokenMeterService } from '@deepseek-ai/dsh-token-meter'
|
||||
import type { Session } from '@deepseek-ai/dsh-session'
|
||||
import { CONTEXT_WINDOW_EXCEEDED_CODE, assertNever } from '@deepseek-ai/dsh-llm'
|
||||
import type { ContentBlock, LlmCallConfig } from '@deepseek-ai/dsh-llm'
|
||||
import type { LlmCallConfig } from '@deepseek-ai/dsh-llm'
|
||||
import type { Agent, PreStepDecision } from '@deepseek-ai/dsh-agent'
|
||||
// Type-only: makes the optional sibling service available to `ctx.get()`.
|
||||
import type {} from '@deepseek-ai/dsh-compact-tool-result-prune'
|
||||
|
||||
@@ -130,7 +130,7 @@ function conversation(turns = 4, text = 'fixture '.repeat(40).trim()): Session {
|
||||
}),
|
||||
}, { surfaceOp: 'append' })
|
||||
session.append('step/end', { turn, step: 1 })
|
||||
session.append('turn/end', { turn, reason: { kind: 'completed' } })
|
||||
session.append('turn/end', { turn, step: 1, reason: { kind: 'completed' } })
|
||||
}
|
||||
session.append('turn/start', {
|
||||
turn: turns + 1,
|
||||
@@ -180,7 +180,7 @@ function toolConversation(): Session {
|
||||
}),
|
||||
}, { surfaceOp: 'append' })
|
||||
session.append('step/end', { turn, step: 1 })
|
||||
session.append('turn/end', { turn, reason: { kind: 'completed' } })
|
||||
session.append('turn/end', { turn, step: 1, reason: { kind: 'completed' } })
|
||||
}
|
||||
session.append('turn/start', { turn: 4 })
|
||||
return session
|
||||
@@ -226,7 +226,7 @@ function oversizedToolResult(chars = 3_000, withCompactablePrompt = false): Sess
|
||||
meta: { presentation: 'preserved' },
|
||||
}, { surfaceOp: 'append' })
|
||||
session.append('step/end', { turn: 1, step: 1 })
|
||||
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
|
||||
session.append('turn/end', { turn: 1, step: 1, reason: { kind: 'completed' } })
|
||||
session.append('turn/start', { turn: 2 })
|
||||
return session
|
||||
}
|
||||
@@ -935,7 +935,7 @@ describe('compaction region transaction', () => {
|
||||
it('requires an open turn and an idle compaction bracket', async () => {
|
||||
const compact = service()
|
||||
const closed = conversation(1)
|
||||
closed.append('turn/end', { turn: 2, reason: { kind: 'completed' } })
|
||||
closed.append('turn/end', { turn: 2, step: 0, reason: { kind: 'completed' } })
|
||||
const nodes = closed.surface.nodes
|
||||
await expect(compact.compactRegion(
|
||||
nodes[0]!,
|
||||
|
||||
@@ -209,7 +209,7 @@ function overflowHistorySeed(): SessionEvent[] {
|
||||
}),
|
||||
}, { surfaceOp: 'append' })
|
||||
session.append('step/end', { turn, step: 1 })
|
||||
session.append('turn/end', { turn, reason: { kind: 'completed' } })
|
||||
session.append('turn/end', { turn, step: 1, reason: { kind: 'completed' } })
|
||||
}
|
||||
return [...session.events]
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('real Loader composition', () => {
|
||||
expect(unloaded).toEqual([])
|
||||
expect(loaded.get('toolResultPrune')).toBeInstanceOf(ToolResultPruneService)
|
||||
expect(loaded.get('compact')).toBeInstanceOf(BasicCompactService)
|
||||
expect((loaded.compact as BasicCompactService).config).toMatchObject({
|
||||
expect((loaded.compact as unknown as BasicCompactService).config).toMatchObject({
|
||||
thresholdRatio: 0.5,
|
||||
retainRatio: 0.125,
|
||||
auto: false,
|
||||
|
||||
@@ -112,7 +112,7 @@ async function loopHarness(): Promise<LoopHarness> {
|
||||
const agent = ctx.agentLoop.create(SessionId('manual-compact'), { provider: MODEL, model: MODEL })
|
||||
const log: string[] = []
|
||||
ctx.on('session/event', (_session, event) => {
|
||||
if (event.type === 'turn/start') log.push(`turn/start:${event.data.trigger.kind}`)
|
||||
if (event.type === 'turn/start') log.push('turn/start')
|
||||
if (event.type === 'turn/end') log.push('turn/end')
|
||||
if (event.type === 'compact/start') log.push(`compact/start:${String(event.data.turn)}`)
|
||||
if (event.type === 'compact/summary') log.push('compact/summary')
|
||||
@@ -169,7 +169,7 @@ function closedConversation(turns = 2, lastTurnNumber = turns): Session {
|
||||
const session = new Session(SessionId(`closed-${turns}-${lastTurnNumber}`))
|
||||
for (let index = 1; index <= turns; index += 1) {
|
||||
const turn = index === turns ? lastTurnNumber : index
|
||||
session.append('turn/start', { turn, trigger: { kind: 'message', source: { kind: 'user' } } })
|
||||
session.append('turn/start', { turn })
|
||||
session.append('user/message', createUserMessage({
|
||||
content: [{ type: 'text', text: `${PROMPT} ${turn}` }],
|
||||
source: { kind: 'user' },
|
||||
@@ -190,7 +190,7 @@ function closedConversation(turns = 2, lastTurnNumber = turns): Session {
|
||||
}),
|
||||
}, { surfaceOp: 'append' })
|
||||
session.append('step/end', { turn, step: 1 })
|
||||
session.append('turn/end', { turn, reason: { kind: 'completed' } })
|
||||
session.append('turn/end', { turn, step: 1, reason: { kind: 'completed' } })
|
||||
}
|
||||
return session
|
||||
}
|
||||
@@ -256,7 +256,7 @@ describe('compactNow through the real loop', () => {
|
||||
const summary = log.indexOf('compact/summary')
|
||||
const end = log.indexOf('compact/end:null')
|
||||
const flush = log.indexOf('flush')
|
||||
const nextTurn = log.indexOf('turn/start:message')
|
||||
const nextTurn = log.indexOf('turn/start')
|
||||
expect(start).toBeLessThan(summary)
|
||||
expect(summary).toBeLessThan(end)
|
||||
expect(end).toBeLessThan(flush)
|
||||
@@ -424,8 +424,8 @@ describe('compactNow transaction and failure classification', () => {
|
||||
const { compact } = detachedService()
|
||||
const original = closedConversation(2)
|
||||
original.append('compact/start', { turn: null })
|
||||
original.append('turn/start', { turn: 3, trigger: { kind: 'message', source: { kind: 'user' } } })
|
||||
original.append('turn/end', { turn: 3, reason: { kind: 'interrupted' } })
|
||||
original.append('turn/start', { turn: 3 })
|
||||
original.append('turn/end', { turn: 3, step: 0, reason: { kind: 'interrupted' } })
|
||||
const reloaded = new Session(SessionId('reloaded-orphan'), [...original.events])
|
||||
const agent = fakeAgent(reloaded, () => () => undefined)
|
||||
|
||||
@@ -436,7 +436,7 @@ describe('compactNow transaction and failure classification', () => {
|
||||
it('refuses an open turn in the log', async () => {
|
||||
const { compact } = detachedService()
|
||||
const session = closedConversation(2)
|
||||
session.append('turn/start', { turn: 3, trigger: { kind: 'message', source: { kind: 'user' } } })
|
||||
session.append('turn/start', { turn: 3 })
|
||||
const agent = fakeAgent(session, () => () => undefined)
|
||||
|
||||
const error = await rejection(compact.compactNow(agent, SIGNAL))
|
||||
@@ -814,7 +814,7 @@ describe('compactNow transaction and failure classification', () => {
|
||||
it('excludes a manual request while an explicit region compaction runs', async () => {
|
||||
const { compact } = detachedService()
|
||||
const session = closedConversation(3)
|
||||
session.append('turn/start', { turn: 4, trigger: { kind: 'message', source: { kind: 'user' } } })
|
||||
session.append('turn/start', { turn: 4 })
|
||||
const agent = fakeAgent(session, () => () => undefined)
|
||||
const gate = deferred()
|
||||
compact.gate = gate.promise
|
||||
|
||||
@@ -60,7 +60,7 @@ function appendToolStep(
|
||||
...extra,
|
||||
}, { surfaceOp: 'append' })
|
||||
session.append('step/end', { turn, step: 1 })
|
||||
session.append('turn/end', { turn, reason: { kind: 'completed' } })
|
||||
session.append('turn/end', { turn, step: 1, reason: { kind: 'completed' } })
|
||||
return result.seq
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ describe('compaction invariants', () => {
|
||||
|
||||
expect(() => replayed.append(
|
||||
'turn/end',
|
||||
{ turn: 1, reason: { kind: 'interrupted' } },
|
||||
{ turn: 1, step: 0, reason: { kind: 'interrupted' } },
|
||||
)).not.toThrow()
|
||||
})
|
||||
|
||||
@@ -100,7 +100,7 @@ describe('compaction invariants', () => {
|
||||
const source = new Session(SessionId('stale-repaired-compaction-source'))
|
||||
source.append('compact/start', { turn: null })
|
||||
startTurn(source)
|
||||
source.append('turn/end', { turn: 1, reason: { kind: 'interrupted' } })
|
||||
source.append('turn/end', { turn: 1, step: 0, reason: { kind: 'interrupted' } })
|
||||
const replayed = ctx.sessions.create(SessionId('stale-repaired-compaction-replay'), {
|
||||
seed: source.events,
|
||||
})
|
||||
@@ -116,7 +116,7 @@ describe('compaction invariants', () => {
|
||||
|
||||
expect(() => {
|
||||
startTurn(replayed, 2)
|
||||
replayed.append('turn/end', { turn: 2, reason: { kind: 'completed' } })
|
||||
replayed.append('turn/end', { turn: 2, step: 0, reason: { kind: 'completed' } })
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
@@ -126,7 +126,7 @@ describe('compaction invariants', () => {
|
||||
const source = new Session(SessionId('closed-nested-compaction-source'))
|
||||
source.append('compact/start', { turn: null })
|
||||
startTurn(source)
|
||||
source.append('turn/end', { turn: 1, reason: { kind: 'interrupted' } })
|
||||
source.append('turn/end', { turn: 1, step: 0, reason: { kind: 'interrupted' } })
|
||||
source.append('compact/end', { turn: null, error: 'failed after crossing turn' })
|
||||
const replayed = ctx.sessions.create(SessionId('closed-nested-compaction-replay'), {
|
||||
seed: source.events,
|
||||
@@ -147,7 +147,7 @@ describe('compaction invariants', () => {
|
||||
await ctx.plugin(InvariantService)
|
||||
await ctx.plugin(CompactInvariant)
|
||||
expect(() => session.append('compact/end', { turn: 1, error: 'resume failed' })).not.toThrow()
|
||||
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
|
||||
session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } })
|
||||
})
|
||||
|
||||
it('adopts a bare session and ignores unrelated committed events', async () => {
|
||||
@@ -200,7 +200,7 @@ describe('compaction invariants', () => {
|
||||
await ctx.plugin(SessionStore)
|
||||
const session = ctx.sessions.create()
|
||||
startTurn(session)
|
||||
session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
|
||||
session.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } })
|
||||
session.append('compact/start', { turn: 1 })
|
||||
await ctx.plugin(InvariantService)
|
||||
await expect(ctx.plugin(CompactInvariant).then(() => undefined)).rejects.toThrow(/outside any open turn/)
|
||||
@@ -215,7 +215,7 @@ describe('compaction invariants', () => {
|
||||
standalone.append('compact/end', { turn: null, error: 'cancelled' })
|
||||
expect(() => {
|
||||
startTurn(standalone)
|
||||
standalone.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
|
||||
standalone.append('turn/end', { turn: 1, step: 0, reason: { kind: 'completed' } })
|
||||
}).not.toThrow()
|
||||
|
||||
const numbered = ctx.sessions.create()
|
||||
@@ -223,12 +223,12 @@ describe('compaction invariants', () => {
|
||||
numbered.append('compact/start', { turn: 1 })
|
||||
expect(() => numbered.append(
|
||||
'turn/end',
|
||||
{ turn: 1, reason: { kind: 'completed' } },
|
||||
{ turn: 1, step: 0, reason: { kind: 'completed' } },
|
||||
)).toThrow(/turn\/end cannot cross an open compaction for turn 1/)
|
||||
numbered.append('compact/end', { turn: 1, error: 'cancelled' })
|
||||
expect(() => numbered.append(
|
||||
'turn/end',
|
||||
{ turn: 1, reason: { kind: 'completed' } },
|
||||
{ turn: 1, step: 0, reason: { kind: 'completed' } },
|
||||
)).not.toThrow()
|
||||
})
|
||||
|
||||
|
||||
@@ -271,8 +271,7 @@ describe('tool-pairing cache refresh', () => {
|
||||
expect(eventIndexReads).toBe(3)
|
||||
|
||||
events.push({
|
||||
type: 'turn/end', seq: 3, time: 3,
|
||||
data: { turn: 1, reason: { kind: 'completed' } },
|
||||
type: 'turn/end', seq: 3, time: 3, data: { turn: 1, step: 0, reason: { kind: 'completed' } },
|
||||
})
|
||||
expect(toolPairingBalancedAfter(session, nodes[2]!)).toBe(true)
|
||||
expect(eventCollectionReads).toBe(1)
|
||||
|
||||
Reference in New Issue
Block a user