mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-14 04:01:35 +00:00
Merge remote-tracking branch 'origin/master' into turtle/omit-unneeded-invariants
# Conflicts: # docs/module-graph.i18n.yaml # docs/module-graph.md # docs/module-graph.zh.md # packages/session/session-projection/src/invariant.ts # tsconfig.base.json
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-compaction-basic",
|
||||
"description": "Token-meter-driven compaction policy and LLM summarization backend for the DeepSeek Harness",
|
||||
"version": "0.1.2-alpha.2",
|
||||
"version": "0.1.2-alpha.3",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
|
||||
@@ -162,7 +162,7 @@ export async function compactSurfaceRegion(
|
||||
): Promise<CompactionResult> {
|
||||
if (options.owner === null) signal?.throwIfAborted()
|
||||
const selection = validateSurfaceRegion(session, start, end)
|
||||
const entryState = inspectCompactionEntryState(session.events)
|
||||
const entryState = inspectCompactionEntryState(session)
|
||||
assertCompactionInactive(
|
||||
entryState.unmatchedCompactionStart,
|
||||
entryState.latestEndSeedSeq,
|
||||
@@ -305,7 +305,7 @@ function assertCompactionInactive(
|
||||
* @param stage - operation label included in the busy diagnostic.
|
||||
*/
|
||||
export function assertNoActiveCompaction(session: Session, stage: string): void {
|
||||
const entryState = inspectCompactionEntryState(session.events)
|
||||
const entryState = inspectCompactionEntryState(session)
|
||||
assertCompactionInactive(
|
||||
entryState.unmatchedCompactionStart,
|
||||
entryState.latestEndSeedSeq,
|
||||
@@ -510,11 +510,10 @@ function buildSummarizationInput(
|
||||
shadowedSeqs: readonly number[],
|
||||
): SummarizationInput {
|
||||
const header = session.requestHeader()
|
||||
const events = session.events
|
||||
const regionMessages = shadowedSeqs
|
||||
// shadowedSeqs are current surface seqs, so each is a valid log index.
|
||||
// oxlint-disable-next-line typescript/no-non-null-assertion
|
||||
.map(seq => session.deriveEventMessage(events[seq]!))
|
||||
.map(seq => session.deriveEventMessage(session.eventAt(seq)!))
|
||||
.filter((message): message is Message => message !== null)
|
||||
return {
|
||||
...header?.system === undefined ? {} : { system: header.system },
|
||||
@@ -524,15 +523,15 @@ function buildSummarizationInput(
|
||||
}
|
||||
|
||||
/** Inspect open-turn, unmatched-compaction, and latest seed-boundary state independently. */
|
||||
function inspectCompactionEntryState(events: readonly SessionEvent[]): CompactionEntryState {
|
||||
function inspectCompactionEntryState(session: Session): CompactionEntryState {
|
||||
let openTurn: number | null = null
|
||||
let openTurnStateKnown = false
|
||||
let unmatchedCompactionStart: SessionEvent<'compaction/start'> | undefined
|
||||
let compactionEntryStateKnown = false
|
||||
let latestEndSeedSeq: number | undefined
|
||||
for (let index = events.length - 1; index >= 0; index -= 1) {
|
||||
for (let seq = session.seq - 1; seq >= 0; seq -= 1) {
|
||||
// oxlint-disable-next-line typescript/no-non-null-assertion
|
||||
const event = events[index]!
|
||||
const event = session.eventAt(seq)!
|
||||
if (latestEndSeedSeq === undefined && event.type === 'session/end-seed') {
|
||||
latestEndSeedSeq = event.seq
|
||||
}
|
||||
|
||||
@@ -609,7 +609,7 @@ describe('pressure measurement and retention', () => {
|
||||
|
||||
await expect(compactIfNeeded(compact, session, 'context-overflow')).resolves.toBeNull()
|
||||
expect(session.surface.replaceGeneration).toBe(generation)
|
||||
expect(session.events.some(event => event.type === 'compaction/start')).toBe(false)
|
||||
expect(session.snapshotEvents().some(event => event.type === 'compaction/start')).toBe(false)
|
||||
})
|
||||
|
||||
it('does nothing below threshold and compacts a priced head above threshold', async () => {
|
||||
@@ -837,10 +837,10 @@ describe('optional model-free tool-result pruning', () => {
|
||||
|
||||
expect(await compactIfNeeded(compact, session)).not.toBeNull()
|
||||
expect(compact.calls).toHaveLength(1)
|
||||
const original = session.events.find(event => event.type === 'tool/result')
|
||||
const original = session.snapshotEvents().find(event => event.type === 'tool/result')
|
||||
expect(original?.type === 'tool/result' && original.data.message.content[0].content[0])
|
||||
.toEqual({ type: 'text', text: 'X'.repeat(3_000) })
|
||||
expect(session.events.filter(event =>
|
||||
expect(session.snapshotEvents().filter(event =>
|
||||
event.type === 'tool/result' && event.surfaceOp !== 'append')).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
@@ -866,7 +866,7 @@ describe('compaction region transaction', () => {
|
||||
expect(result.shadowedTokenCount).toBeGreaterThan(0)
|
||||
expect(compact.calls[0]).toMatchObject({ signal: SIGNAL })
|
||||
expect(summarizedText(compact.calls[0]!.input)).toContain('fixture user 1')
|
||||
const summary = session.events.findLast(event => event.type === 'compaction/summary')
|
||||
const summary = session.snapshotEvents().findLast(event => event.type === 'compaction/summary')
|
||||
expect(summary?.data).toMatchObject({
|
||||
shadowedSeqs: result.shadowedSeqs,
|
||||
shadowedTokenCount: result.shadowedTokenCount,
|
||||
@@ -882,7 +882,7 @@ describe('compaction region transaction', () => {
|
||||
expect(head.content[0]?.type === 'text' ? head.content[0].text : '').toContain('<compacted-summary>')
|
||||
expect(head.content.at(-1)).toEqual({ type: 'text', text: '</compacted-summary>' })
|
||||
|
||||
const replay = Session.create(SessionId('replay'), [...session.events])
|
||||
const replay = Session.create(SessionId('replay'), session.snapshotEvents())
|
||||
expect(replay.deriveMessages()).toEqual(session.deriveMessages())
|
||||
})
|
||||
|
||||
@@ -1012,7 +1012,7 @@ describe('compaction region transaction', () => {
|
||||
agent(session, MODEL),
|
||||
)).rejects.toThrow('summary unavailable')
|
||||
expect(session.surface.nodes).toEqual(before)
|
||||
expect(session.events.findLast(event => event.type === 'compaction/end')?.data)
|
||||
expect(session.snapshotEvents().findLast(event => event.type === 'compaction/end')?.data)
|
||||
.toMatchObject({ error: 'summary unavailable' })
|
||||
})
|
||||
|
||||
@@ -1026,7 +1026,7 @@ describe('compaction region transaction', () => {
|
||||
nodes[2]!,
|
||||
agent(session, MODEL),
|
||||
)).rejects.toBe('plain failure')
|
||||
expect(session.events.findLast(event => event.type === 'compaction/end')?.data)
|
||||
expect(session.snapshotEvents().findLast(event => event.type === 'compaction/end')?.data)
|
||||
.toMatchObject({ error: 'plain failure' })
|
||||
})
|
||||
|
||||
@@ -1046,7 +1046,7 @@ describe('compaction region transaction', () => {
|
||||
nodes[2]!,
|
||||
agent(session, MODEL),
|
||||
)).resolves.toMatchObject({ shadowedSeqs: nodes.slice(0, 3) })
|
||||
expect(session.events.some(event => event.type === 'compaction/summary')).toBe(true)
|
||||
expect(session.snapshotEvents().some(event => event.type === 'compaction/summary')).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects concurrent surface appends before committing the replacement', async () => {
|
||||
@@ -1065,7 +1065,7 @@ describe('compaction region transaction', () => {
|
||||
nodes[2]!,
|
||||
agent(session, MODEL),
|
||||
)).rejects.toThrow(/session surface changed/)
|
||||
expect(session.events.some(event => event.type === 'compaction/summary')).toBe(false)
|
||||
expect(session.snapshotEvents().some(event => event.type === 'compaction/summary')).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects a non-shrinking framed summary under the conversation meter', async () => {
|
||||
@@ -1082,7 +1082,7 @@ describe('compaction region transaction', () => {
|
||||
nodes[2]!,
|
||||
agent(session, MODEL),
|
||||
)).rejects.toThrow(/summary is not smaller/)
|
||||
expect(session.events.some(event => event.type === 'compaction/summary')).toBe(false)
|
||||
expect(session.snapshotEvents().some(event => event.type === 'compaction/summary')).toBe(false)
|
||||
})
|
||||
|
||||
it('lets a model-independent custom summarizer compact without a conversation model', async () => {
|
||||
@@ -1333,7 +1333,7 @@ describe('default one-shot summarizer', () => {
|
||||
const session = conversation(3, 'large history '.repeat(500))
|
||||
const nodes = session.surface.nodes
|
||||
await compact.compactRegion(nodes[0]!, nodes[3]!, agent(session, MODEL), SIGNAL)
|
||||
expect(session.events.findLast(event => event.type === 'compaction/summary')?.data).toMatchObject({
|
||||
expect(session.snapshotEvents().findLast(event => event.type === 'compaction/summary')?.data).toMatchObject({
|
||||
summary: [{ type: 'text', text: 'routed summary' }],
|
||||
llmStreamCall: true,
|
||||
provider: 'routed-summary-provider',
|
||||
@@ -1459,7 +1459,7 @@ describe('automatic listener and loader composition', () => {
|
||||
next: () => Promise<RequestErrorAction> = () => Promise.resolve(undefined),
|
||||
): Promise<boolean> {
|
||||
const failure: LlmFailure = { message: error.message, code: error.code ?? 'UNKNOWN' }
|
||||
const turn = owner.session.events.findLast(event => event.type === 'turn/start')?.data.turn ?? 1
|
||||
const turn = owner.session.snapshotEvents().findLast(event => event.type === 'turn/start')?.data.turn ?? 1
|
||||
return agentEvents(ctx, owner).waterfall(
|
||||
'agent/request-error',
|
||||
{ turn, step: 1, provider: 'test', failure, retryPolicy: undefined, signal },
|
||||
@@ -1479,11 +1479,11 @@ describe('automatic listener and loader composition', () => {
|
||||
})
|
||||
const pressured = conversation(4)
|
||||
await preStep(ctx, agent(pressured, 'unconfigured-agent-fallback'))
|
||||
expect(pressured.events.some(event => event.type === 'compaction/summary')).toBe(true)
|
||||
expect(pressured.snapshotEvents().some(event => event.type === 'compaction/summary')).toBe(true)
|
||||
|
||||
const small = conversation(1)
|
||||
await preStep(ctx, agent(small, MODEL))
|
||||
expect(small.events.some(event => event.type === 'compaction/start')).toBe(false)
|
||||
expect(small.snapshotEvents().some(event => event.type === 'compaction/start')).toBe(false)
|
||||
expect(compact.calls).toHaveLength(1)
|
||||
})
|
||||
|
||||
@@ -1500,7 +1500,7 @@ describe('automatic listener and loader composition', () => {
|
||||
.resolves.toEqual({ kind: 'enter', messages: [] })
|
||||
|
||||
expect(compactIfNeeded).not.toHaveBeenCalled()
|
||||
expect(pressured.events.some(event => event.type === 'compaction/start')).toBe(false)
|
||||
expect(pressured.snapshotEvents().some(event => event.type === 'compaction/start')).toBe(false)
|
||||
})
|
||||
|
||||
it('warns and continues after operational failures, including non-Errors', async () => {
|
||||
@@ -1516,7 +1516,7 @@ describe('automatic listener and loader composition', () => {
|
||||
|
||||
await expect(preStep(ctx, agent(session, MODEL))).resolves.toEqual({ kind: 'enter', messages: [] })
|
||||
expect(warnings).toContainEqual(expect.stringContaining('temporary failure'))
|
||||
expect(session.events.some(event => event.type === 'compaction/summary')).toBe(false)
|
||||
expect(session.snapshotEvents().some(event => event.type === 'compaction/summary')).toBe(false)
|
||||
})
|
||||
|
||||
it('warns once per routed target when proactive pressure has no context metadata', async () => {
|
||||
@@ -1575,7 +1575,7 @@ describe('automatic listener and loader composition', () => {
|
||||
|
||||
expect(decision).toBe(true)
|
||||
expect(session.surface.replaceGeneration).toBe(beforeGeneration + 1)
|
||||
expect(session.events.some(event => event.type === 'compaction/summary')).toBe(true)
|
||||
expect(session.snapshotEvents().some(event => event.type === 'compaction/summary')).toBe(true)
|
||||
expect(session.surface.nodes).toContain(retainedSeq)
|
||||
})
|
||||
|
||||
@@ -1594,7 +1594,7 @@ describe('automatic listener and loader composition', () => {
|
||||
|
||||
expect(await recover(ctx, agent(session, MODEL), overflow())).toBe(true)
|
||||
expect(session.surface.replaceGeneration).toBe(1)
|
||||
expect(session.events.some(event => event.type === 'compaction/summary')).toBe(false)
|
||||
expect(session.snapshotEvents().some(event => event.type === 'compaction/summary')).toBe(false)
|
||||
expect(compact.calls).toHaveLength(0)
|
||||
})
|
||||
|
||||
@@ -1612,7 +1612,7 @@ describe('automatic listener and loader composition', () => {
|
||||
const session = toolConversation()
|
||||
|
||||
expect(await recover(ctx, agent(session, MODEL), overflow())).toBe(true)
|
||||
expect(session.events.some(event => event.type === 'compaction/summary')).toBe(true)
|
||||
expect(session.snapshotEvents().some(event => event.type === 'compaction/summary')).toBe(true)
|
||||
expect(compact.calls).toHaveLength(1)
|
||||
expect(summarizedText(compact.calls[0]!.input)).toContain('tool result middle pruned')
|
||||
})
|
||||
@@ -1635,8 +1635,8 @@ describe('automatic listener and loader composition', () => {
|
||||
|
||||
expect(await recover(ctx, agent(session, MODEL), overflow())).toBe(true)
|
||||
expect(session.surface.replaceGeneration).toBe(1)
|
||||
expect(session.events.filter(event => event.type === 'tool/result')).toHaveLength(2)
|
||||
expect(session.events.findLast(event => event.type === 'compaction/end')?.data)
|
||||
expect(session.snapshotEvents().filter(event => event.type === 'tool/result')).toHaveLength(2)
|
||||
expect(session.snapshotEvents().findLast(event => event.type === 'compaction/end')?.data)
|
||||
.toMatchObject({ error: 'summary unavailable after prune' })
|
||||
expect(warnings).toContainEqual(expect.stringContaining('retrying from the replacement surface'))
|
||||
})
|
||||
@@ -1838,10 +1838,10 @@ describe('automatic listener and loader composition', () => {
|
||||
})
|
||||
const session = conversation(4)
|
||||
await preStep(ctx, agent(session, MODEL))
|
||||
const summaries = session.events.filter(event => event.type === 'compaction/summary').length
|
||||
const summaries = session.snapshotEvents().filter(event => event.type === 'compaction/summary').length
|
||||
expect(summaries).toBe(1)
|
||||
expect(await recover(ctx, agent(session, MODEL), overflow())).toBe(false)
|
||||
expect(session.events.filter(event => event.type === 'compaction/summary')).toHaveLength(summaries)
|
||||
expect(session.snapshotEvents().filter(event => event.type === 'compaction/summary')).toHaveLength(summaries)
|
||||
})
|
||||
|
||||
it('auto:false installs neither automatic listener', async () => {
|
||||
@@ -1853,7 +1853,7 @@ describe('automatic listener and loader composition', () => {
|
||||
})
|
||||
const session = conversation(4)
|
||||
await preStep(ctx, agent(session, MODEL))
|
||||
expect(session.events.some(event => event.type === 'compaction/start')).toBe(false)
|
||||
expect(session.snapshotEvents().some(event => event.type === 'compaction/start')).toBe(false)
|
||||
expect(await recover(ctx, agent(session, MODEL), overflow())).toBe(false)
|
||||
})
|
||||
|
||||
@@ -1885,7 +1885,7 @@ describe('automatic listener and loader composition', () => {
|
||||
|
||||
const session = conversation(4)
|
||||
await preStep(ctx, agent(session, MODEL))
|
||||
expect(session.events.some(event => event.type === 'compaction/start')).toBe(false)
|
||||
expect(session.snapshotEvents().some(event => event.type === 'compaction/start')).toBe(false)
|
||||
expect(await recover(ctx, agent(session, MODEL), overflow())).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -2025,7 +2025,7 @@ describe('route-priced image pressure', () => {
|
||||
|
||||
const result = await compact.compactIfNeeded(agent(session), 'pressure', SIGNAL)
|
||||
expect(result).not.toBeNull()
|
||||
const summaryEvent = session.events.find(event => event.type === 'compaction/summary')
|
||||
const summaryEvent = session.snapshotEvents().find(event => event.type === 'compaction/summary')
|
||||
expect(summaryEvent).toBeDefined()
|
||||
const shadowedHeuristic = before.nodes
|
||||
.filter(node => result?.shadowedSeqs.includes(node.seq))
|
||||
|
||||
@@ -188,7 +188,7 @@ function waitForIdle(ctx: Context, agent: Agent): Promise<void> {
|
||||
})
|
||||
}
|
||||
|
||||
function overflowHistorySeed(): SessionEvent[] {
|
||||
function overflowHistorySeed(): readonly SessionEvent[] {
|
||||
const session = Session.create(SessionId('overflow-history-seed'))
|
||||
for (let turn = 1; turn <= 2; turn += 1) {
|
||||
const sentinel = turn === 1 ? 'OLD HISTORY SENTINEL' : 'RECENT HISTORY'
|
||||
@@ -215,7 +215,7 @@ function overflowHistorySeed(): SessionEvent[] {
|
||||
session.append('step/end', { turn, step: 1 })
|
||||
session.append('turn/end', { turn, reason: { kind: 'completed' } })
|
||||
}
|
||||
return [...session.events]
|
||||
return session.snapshotEvents()
|
||||
}
|
||||
|
||||
describe('CBR-001: a real-loop checkpoint is a valid boundary on both sides', () => {
|
||||
@@ -233,8 +233,8 @@ describe('CBR-001: a real-loop checkpoint is a valid boundary on both sides', ()
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
expect(agent.session.requestHeader()?.config.model).toBe('mock')
|
||||
expect(agent.session.events.some(event => event.type === 'compaction/summary')).toBe(true)
|
||||
expect(agent.session.events.at(-1)).toMatchObject({
|
||||
expect(agent.session.snapshotEvents().some(event => event.type === 'compaction/summary')).toBe(true)
|
||||
expect(agent.session.snapshotEvents().at(-1)).toMatchObject({
|
||||
type: 'turn/end',
|
||||
data: { reason: { kind: 'completed' } },
|
||||
})
|
||||
@@ -250,7 +250,7 @@ describe('CBR-001: a real-loop checkpoint is a valid boundary on both sides', ()
|
||||
agent.followup(createUserMessage({ content: [{ type: 'text', text: 'do tool work' }], source: { kind: 'user' } }))
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
const events = [...agent.session.events]
|
||||
const events = agent.session.snapshotEvents()
|
||||
const compactStart = events.find(event => event.type === 'compaction/start')
|
||||
expect(compactStart).toBeDefined()
|
||||
const precedingResult = events.findLast(event =>
|
||||
@@ -282,7 +282,7 @@ describe('CBR-001: a real-loop checkpoint is a valid boundary on both sides', ()
|
||||
agent.followup(createUserMessage({ content: [{ type: 'text', text: 'do a long multi-step task' }], source: { kind: 'user' } }))
|
||||
await waitForIdle(ctx, agent)
|
||||
|
||||
const events = [...agent.session.events]
|
||||
const events = agent.session.snapshotEvents()
|
||||
// A compaction ran: at least one checkpoint landed on the surface.
|
||||
const checkpoints = events.filter(
|
||||
(e): e is SurfaceEvent =>
|
||||
@@ -356,7 +356,7 @@ describe('context-overflow recovery across the real loop and compaction-basic',
|
||||
expect(retry).toContain('RECOVERY CHECKPOINT')
|
||||
expect(retry).not.toContain('OLD HISTORY SENTINEL')
|
||||
|
||||
const events = [...agent.session.events]
|
||||
const events = agent.session.snapshotEvents()
|
||||
const stepStart = events.find(event =>
|
||||
event.type === 'step/start' && event.data.turn === 3 && event.data.step === 1,
|
||||
)!
|
||||
@@ -419,11 +419,11 @@ describe('context-overflow recovery across the real loop and compaction-basic',
|
||||
|
||||
expect(adapter.conversationRequests).toHaveLength(3)
|
||||
expect(adapter.summaryRequests).toHaveLength(1)
|
||||
expect(agent.session.events.filter(event => event.type === 'llm/retry').map(event => event.data))
|
||||
expect(agent.session.snapshotEvents().filter(event => event.type === 'llm/retry').map(event => event.data))
|
||||
.toEqual([expect.objectContaining({ turn: 3, step: 1, retry: 1, failure: { message: 'temporary provider outage', code: 'SERVER' } })])
|
||||
expect(agent.session.events.filter(event => event.type === 'turn/start').slice(-1).map(event => event.data.turn))
|
||||
expect(agent.session.snapshotEvents().filter(event => event.type === 'turn/start').slice(-1).map(event => event.data.turn))
|
||||
.toEqual([3])
|
||||
expect(agent.session.events.at(-1)).toMatchObject({
|
||||
expect(agent.session.snapshotEvents().at(-1)).toMatchObject({
|
||||
type: 'turn/end',
|
||||
data: { reason: { kind: 'completed' } },
|
||||
})
|
||||
|
||||
@@ -232,8 +232,8 @@ function detachedService(): { ctx: Context; compact: GatedCompactionEngine; flus
|
||||
return { ctx, compact: new GatedCompactionEngine(ctx, { auto: false }), flushes: () => flushes }
|
||||
}
|
||||
|
||||
function compactEvents(session: Session): Array<Session['events'][number]> {
|
||||
return session.events.filter(event => event.type.startsWith('compaction/'))
|
||||
function compactEvents(session: Session): SessionEvent[] {
|
||||
return session.snapshotEvents().filter(event => event.type.startsWith('compaction/'))
|
||||
}
|
||||
|
||||
describe('compactNow through the real loop', () => {
|
||||
@@ -294,14 +294,14 @@ describe('compactNow through the real loop', () => {
|
||||
const result = await compact.compactNow(agent, SIGNAL)
|
||||
|
||||
expect(result).not.toBeNull()
|
||||
const start = agent.session.events.findLast(event => event.type === 'compaction/start')
|
||||
const start = agent.session.snapshotEvents().findLast(event => event.type === 'compaction/start')
|
||||
const injected = agent.inbox.nextStep.find(message =>
|
||||
message.source.kind === 'plugin' && message.source.plugin === 'test')
|
||||
const end = agent.session.events.findLast(event => event.type === 'compaction/end')
|
||||
const end = agent.session.snapshotEvents().findLast(event => event.type === 'compaction/end')
|
||||
expect(start).toBeDefined()
|
||||
expect(injected).toBeDefined()
|
||||
expect(end).toBeDefined()
|
||||
expect(agent.session.events.some(event => event.type === 'user/message'
|
||||
expect(agent.session.snapshotEvents().some(event => event.type === 'user/message'
|
||||
&& event.data.id === injected?.id)).toBe(false)
|
||||
|
||||
agent.followup(createUserMessage({
|
||||
@@ -333,7 +333,7 @@ describe('compactNow through the real loop', () => {
|
||||
expect(attempts).toEqual(['compaction/start', 'compaction/summary'])
|
||||
expect(result).not.toBeNull()
|
||||
expect(derivedText(agent.session)[0]).toContain('checkpoint')
|
||||
expect(agent.session.events.filter(event => event.type === 'user/message'
|
||||
expect(agent.session.snapshotEvents().filter(event => event.type === 'user/message'
|
||||
&& event.data.source.kind === 'plugin' && event.data.source.plugin === 'listener')).toHaveLength(0)
|
||||
const types = compactEvents(agent.session).map(event => event.type)
|
||||
expect(types).toEqual(['compaction/start', 'compaction/summary', 'compaction/end'])
|
||||
@@ -353,7 +353,7 @@ describe('compactNow through the real loop', () => {
|
||||
|
||||
await agent.whenIdle()
|
||||
expect(adapter.requests).toHaveLength(2)
|
||||
expect(agent.session.events.some(event => event.type === 'compaction/start')).toBe(false)
|
||||
expect(agent.session.snapshotEvents().some(event => event.type === 'compaction/start')).toBe(false)
|
||||
})
|
||||
|
||||
it('releases turn admission after a summarizer failure and records the failed attempt', async () => {
|
||||
@@ -403,14 +403,14 @@ describe('compactNow transaction and failure classification', () => {
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.sourceCommandId).toBe(commandId)
|
||||
expect(flushes()).toBe(1)
|
||||
expect(session.events.filter(event => event.type === 'turn/start').at(-1)?.data.turn).toBe(7)
|
||||
const start = session.events.findLast(event => event.type === 'compaction/start')
|
||||
const summaryEvent = session.events.findLast(event => event.type === 'compaction/summary')
|
||||
const checkpoint = session.events.findLast(
|
||||
expect(session.snapshotEvents().filter(event => event.type === 'turn/start').at(-1)?.data.turn).toBe(7)
|
||||
const start = session.snapshotEvents().findLast(event => event.type === 'compaction/start')
|
||||
const summaryEvent = session.snapshotEvents().findLast(event => event.type === 'compaction/summary')
|
||||
const checkpoint = session.snapshotEvents().findLast(
|
||||
(event): event is SessionEvent<'user/message'> => event.type === 'user/message'
|
||||
&& isCompactCheckpointSource(event.data.source),
|
||||
)
|
||||
const end = session.events.findLast(event => event.type === 'compaction/end')
|
||||
const end = session.snapshotEvents().findLast(event => event.type === 'compaction/end')
|
||||
const correlated = { compactionId: result?.compactionId, sourceCommandId: commandId }
|
||||
expect(start?.data).toEqual({ ...correlated, turn: null })
|
||||
expect(summaryEvent?.data.sourceCommandId).toBe(commandId)
|
||||
@@ -440,9 +440,9 @@ describe('compactNow transaction and failure classification', () => {
|
||||
compactionId: CompactionId('stale-manual-compaction'),
|
||||
turn: null,
|
||||
})
|
||||
const reloaded = Session.create(SessionId('stale-orphan'), [...original.events])
|
||||
const boundary = reloaded.events.findLast(event => event.type === 'session/end-seed')
|
||||
const orphan = reloaded.events.find(event => event.type === 'compaction/start')
|
||||
const reloaded = Session.create(SessionId('stale-orphan'), original.snapshotEvents())
|
||||
const boundary = reloaded.snapshotEvents().findLast(event => event.type === 'session/end-seed')
|
||||
const orphan = reloaded.snapshotEvents().find(event => event.type === 'compaction/start')
|
||||
const agent = fakeAgent(reloaded, () => () => undefined)
|
||||
|
||||
expect(boundary?.seq).toBeGreaterThan(orphan?.seq ?? Number.MAX_SAFE_INTEGER)
|
||||
@@ -459,7 +459,7 @@ describe('compactNow transaction and failure classification', () => {
|
||||
})
|
||||
original.append('turn/start', { turn: 3 })
|
||||
original.append('turn/end', { turn: 3, reason: { kind: 'interrupted' } })
|
||||
const reloaded = Session.create(SessionId('reloaded-orphan'), [...original.events])
|
||||
const reloaded = Session.create(SessionId('reloaded-orphan'), original.snapshotEvents())
|
||||
const agent = fakeAgent(reloaded, () => () => undefined)
|
||||
|
||||
await expect(compact.compactNow(agent, SIGNAL)).resolves.not.toBeNull()
|
||||
@@ -562,7 +562,7 @@ describe('compactNow transaction and failure classification', () => {
|
||||
expect(session.surface.replaceGeneration).toBe(generation + 1)
|
||||
expect(session.surface.nodes).not.toContain(head)
|
||||
expect(compactEvents(session).map(event => event.type)).toEqual(['compaction/start', 'compaction/end'])
|
||||
expect(session.events.some(event => event.type === 'user/message'
|
||||
expect(session.snapshotEvents().some(event => event.type === 'user/message'
|
||||
&& isCompactCheckpointSource(event.data.source))).toBe(false)
|
||||
})
|
||||
|
||||
@@ -581,7 +581,7 @@ describe('compactNow transaction and failure classification', () => {
|
||||
expect(causeOf(error).message).toBe('boundary rejected')
|
||||
vi.restoreAllMocks()
|
||||
expect(flushes()).toBe(0)
|
||||
expect(session.events.findLast(event => event.type.startsWith('compaction/'))?.type)
|
||||
expect(session.snapshotEvents().findLast(event => event.type.startsWith('compaction/'))?.type)
|
||||
.toBe('compaction/summary')
|
||||
expect(compactEvents(session).filter(event => event.type === 'compaction/start')).toHaveLength(1)
|
||||
|
||||
@@ -647,7 +647,7 @@ describe('compactNow transaction and failure classification', () => {
|
||||
vi.restoreAllMocks()
|
||||
expect(error.code).toBe('commit')
|
||||
expect(released).toBe(1)
|
||||
const end = session.events.findLast(event => event.type === 'compaction/end')
|
||||
const end = session.snapshotEvents().findLast(event => event.type === 'compaction/end')
|
||||
expect(end?.type === 'compaction/end' && end.data.error).toContain('summary record rejected')
|
||||
expect(end?.type === 'compaction/end' && end.data.turn).toBeNull()
|
||||
})
|
||||
@@ -683,8 +683,8 @@ describe('compactNow transaction and failure classification', () => {
|
||||
const result = await compact.compactNow(agent, SIGNAL)
|
||||
|
||||
expect(result).not.toBeNull()
|
||||
expect(session.events.some(event => event.type === 'turn/start')).toBe(false)
|
||||
expect(session.events.find(event => event.type === 'compaction/start')?.data)
|
||||
expect(session.snapshotEvents().some(event => event.type === 'turn/start')).toBe(false)
|
||||
expect(session.snapshotEvents().find(event => event.type === 'compaction/start')?.data)
|
||||
.toEqual({ compactionId: result?.compactionId, turn: null })
|
||||
})
|
||||
|
||||
@@ -696,9 +696,9 @@ describe('compactNow transaction and failure classification', () => {
|
||||
|
||||
expect((await rejection(compact.compactNow(agent, SIGNAL))).code).toBe('persistence')
|
||||
vi.restoreAllMocks()
|
||||
expect(session.events.some(event => event.type === 'compaction/summary')).toBe(true)
|
||||
const start = session.events.findLast(event => event.type === 'compaction/start')
|
||||
const end = session.events.findLast(event => event.type === 'compaction/end')
|
||||
expect(session.snapshotEvents().some(event => event.type === 'compaction/summary')).toBe(true)
|
||||
const start = session.snapshotEvents().findLast(event => event.type === 'compaction/start')
|
||||
const end = session.snapshotEvents().findLast(event => event.type === 'compaction/end')
|
||||
expect(end?.data).toEqual({ compactionId: start?.data.compactionId, turn: null })
|
||||
})
|
||||
|
||||
@@ -714,7 +714,7 @@ describe('compactNow transaction and failure classification', () => {
|
||||
const reserve = vi.fn(() => testCase.release)
|
||||
const measure = vi.spyOn(ctx.tokenMeter, 'measure')
|
||||
const agent = fakeAgent(testCase.session, reserve)
|
||||
const before = [...testCase.session.events]
|
||||
const before = testCase.session.snapshotEvents()
|
||||
const reason = Object.freeze({ kind: 'cancelled', case: testCase.name })
|
||||
const controller = new AbortController()
|
||||
controller.abort(reason)
|
||||
@@ -729,7 +729,7 @@ describe('compactNow transaction and failure classification', () => {
|
||||
expect(reserve).not.toHaveBeenCalled()
|
||||
expect(measure).not.toHaveBeenCalled()
|
||||
expect(compact.calls).toHaveLength(0)
|
||||
expect(testCase.session.events).toEqual(before)
|
||||
expect(testCase.session.snapshotEvents()).toEqual(before)
|
||||
vi.restoreAllMocks()
|
||||
}
|
||||
})
|
||||
@@ -778,7 +778,7 @@ describe('compactNow transaction and failure classification', () => {
|
||||
|
||||
await expect(compact.compactNow(agent, controller.signal)).rejects.toBe(reason)
|
||||
expect(compactEvents(session).map(event => event.type)).toEqual(['compaction/start', 'compaction/end'])
|
||||
expect(session.events.some(event => event.type === 'compaction/summary')).toBe(false)
|
||||
expect(session.snapshotEvents().some(event => event.type === 'compaction/summary')).toBe(false)
|
||||
})
|
||||
|
||||
it('waits for the durability checkpoint before cancellation wins and admission releases', async () => {
|
||||
@@ -822,7 +822,7 @@ describe('compactNow transaction and failure classification', () => {
|
||||
|
||||
await compact.compactNow(agent, SIGNAL)
|
||||
|
||||
const summary = session.events.find(event => event.type === 'compaction/summary')
|
||||
const summary = session.snapshotEvents().find(event => event.type === 'compaction/summary')
|
||||
expect(summary?.type === 'compaction/summary' && summary.data.rawOutput).toEqual(compact.rawOutput)
|
||||
expect(summary?.type === 'compaction/summary' && summary.data.usage).toEqual(compact.usage)
|
||||
})
|
||||
@@ -837,8 +837,8 @@ describe('compactNow transaction and failure classification', () => {
|
||||
|
||||
await compact.compactNow(agent, SIGNAL)
|
||||
|
||||
const start = session.events.findLast(event => event.type === 'compaction/start')
|
||||
const end = session.events.findLast(event => event.type === 'compaction/end')
|
||||
const start = session.snapshotEvents().findLast(event => event.type === 'compaction/start')
|
||||
const end = session.snapshotEvents().findLast(event => event.type === 'compaction/end')
|
||||
expect(start).toBeDefined()
|
||||
expect(end).toBeDefined()
|
||||
expect(end!.time - start!.time).toBeGreaterThan(0)
|
||||
|
||||
Reference in New Issue
Block a user