Merge branch 'codex/simp-agent-entry-state' into codex/simp-unify-agent-session-id

This commit is contained in:
Tianyi Cui
2026-07-15 17:24:07 +08:00
5 changed files with 29 additions and 7 deletions

View File

@@ -426,9 +426,13 @@ export class BasicCompactService extends CompactService {
// compact/start and here leaves a detectable orphaned lock (a compact/start // compact/start and here leaves a detectable orphaned lock (a compact/start
// with no matching compact/end) rather than a compact/end that falsely // with no matching compact/end) rather than a compact/end that falsely
// claims compaction finished before the surface replacement landed. // claims compaction finished before the surface replacement landed.
session.append('compact/end', { turn: openTurn }) const endEvent = session.append('compact/end', { turn: openTurn })
return { return {
startSeq: startEvent.seq,
summarySeq: summaryEvent.seq,
endSeq: endEvent.seq,
summary,
shadowedRange: { start, end }, shadowedRange: { start, end },
shadowedSeqs, shadowedSeqs,
shadowedTokenCount, shadowedTokenCount,

View File

@@ -346,6 +346,7 @@ describe('BasicCompactService.compactRegion', () => {
expect(result.shadowedSeqs).toEqual([firstSeq, secondSeq]) expect(result.shadowedSeqs).toEqual([firstSeq, secondSeq])
expect(result.shadowedRange.start).toBe(firstSeq) expect(result.shadowedRange.start).toBe(firstSeq)
expect(result.shadowedRange.end).toBe(secondSeq) expect(result.shadowedRange.end).toBe(secondSeq)
expect(result.summary).toEqual(svc.mockSummary)
expect(result.shadowedTokenCount).toBe(20) expect(result.shadowedTokenCount).toBe(20)
const events = session.events const events = session.events
@@ -1057,7 +1058,8 @@ describe('BasicCompactService.summarize (real ctx.llm.stream)', () => {
const session = multiTurnSession(2, 1) const session = multiTurnSession(2, 1)
const nodes = session.surface.nodes const nodes = session.surface.nodes
await compactRegion(svc, session, nodes[0]!, nodes[1]!, 'test-model') const result = await compactRegion(svc, session, nodes[0]!, nodes[1]!, 'test-model')
expect(result.summary).toEqual([{ type: 'text', text: 'CONDENSED' }])
const summaryEvent = session.events.findLast(e => e.type === 'compact/summary')! const summaryEvent = session.events.findLast(e => e.type === 'compact/summary')!
expect(summaryEvent.data.summary).toEqual([{ type: 'text', text: 'CONDENSED' }]) expect(summaryEvent.data.summary).toEqual([{ type: 'text', text: 'CONDENSED' }])
// The raw summary is wrapped in the checkpoint framing on the surface. // The raw summary is wrapped in the checkpoint framing on the surface.

View File

@@ -73,7 +73,7 @@ export abstract class CompactService extends Service {
* @param agent - context whose session is mutated and whose routing options guide summarization. * @param agent - context whose session is mutated and whose routing options guide summarization.
* @param signal - optional cancellation; model-backed implementations must forward it. * @param signal - optional cancellation; model-backed implementations must forward it.
* @throws when compaction is active or the range is missing, reversed, or unbalanced. * @throws when compaction is active or the range is missing, reversed, or unbalanced.
* @returns the replaced range and token accounting; the durable event owns the summary. * @returns the appended event seqs, summary, replaced range, and token accounting.
*/ */
abstract compactRegion( abstract compactRegion(
start: number, start: number,

View File

@@ -41,6 +41,14 @@ declare module '@deepseek-ai/dsh-session' {
/** Result of a successful compaction operation. */ /** Result of a successful compaction operation. */
export interface CompactionResult { export interface CompactionResult {
/** The seq of the appended `compact/start` event. */
startSeq: number
/** The seq of the appended `compact/summary` event. */
summarySeq: number
/** The seq of the appended `compact/end` event. */
endSeq: number
/** The summary content blocks produced by the backend. */
summary: ContentBlock[]
/** /**
* The surface-boundary pair that was shadowed: the seqs of the first * The surface-boundary pair that was shadowed: the seqs of the first
* (`start`) and last (`end`) surface nodes of the replaced range. A * (`start`) and last (`end`) surface nodes of the replaced range. A

View File

@@ -34,17 +34,22 @@ class StubCompactService extends CompactService {
): Promise<CompactionResult> { ): Promise<CompactionResult> {
this.lastSignal = signal this.lastSignal = signal
const session = agent.session const session = agent.session
const summary = [{ type: 'text' as const, text: 'stub' }]
// Minimal stub honoring the lock + log-only event contract. // Minimal stub honoring the lock + log-only event contract.
session.append('compact/start', { turn: 0 }) const startEvent = session.append('compact/start', { turn: 0 })
session.append('compact/summary', { const summaryEvent = session.append('compact/summary', {
summary: [{ type: 'text', text: 'stub' }], summary,
shadowedRange: { start, end }, shadowedRange: { start, end },
shadowedSeqs: [], shadowedSeqs: [],
shadowedTokenCount: 0, shadowedTokenCount: 0,
model: 'stub', model: 'stub',
}) })
session.append('compact/end', { turn: 0 }) const endEvent = session.append('compact/end', { turn: 0 })
return { return {
startSeq: startEvent.seq,
summarySeq: summaryEvent.seq,
endSeq: endEvent.seq,
summary,
shadowedRange: { start, end }, shadowedRange: { start, end },
shadowedSeqs: [], shadowedSeqs: [],
shadowedTokenCount: 0, shadowedTokenCount: 0,
@@ -92,6 +97,9 @@ describe('CompactService seam', () => {
// verify the runtime value is absent. // verify the runtime value is absent.
const raw = startEvent as unknown as { surfaceOp?: unknown } const raw = startEvent as unknown as { surfaceOp?: unknown }
expect(raw.surfaceOp).toBeUndefined() expect(raw.surfaceOp).toBeUndefined()
expect(result.summary).toEqual([{ type: 'text', text: 'stub' }])
expect(result.summarySeq).toBeGreaterThan(result.startSeq)
expect(result.endSeq).toBeGreaterThan(result.summarySeq)
expect(result.shadowedRange).toEqual({ start: 0, end: 0 }) expect(result.shadowedRange).toEqual({ start: 0, end: 0 })
expect(session.events.filter(e => e.type.startsWith('compact/')).map(e => e.type)) expect(session.events.filter(e => e.type.startsWith('compact/')).map(e => e.type))
.toEqual(['compact/start', 'compact/summary', 'compact/end']) .toEqual(['compact/start', 'compact/summary', 'compact/end'])