mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Merge branch 'codex/simp-agent-entry-state' into codex/simp-unify-agent-session-id
This commit is contained in:
@@ -337,13 +337,7 @@ export class BasicCompactService extends CompactService {
|
||||
signal?: AbortSignal,
|
||||
): Promise<CompactionResult> {
|
||||
const session = agent.session
|
||||
// Resolve the range by surface POSITION, not numeric seq interval. A prior
|
||||
// replace lands a fresh high-seq summary node AT the shadowed range's
|
||||
// position, so the surface order (head→tail) no longer tracks seq order —
|
||||
// `[newSummarySeq, olderRetainedSeq, …]` is normal. Indexing into the
|
||||
// ordered node list and slicing it is the only correct way to read a range;
|
||||
// a `seq >= start && seq <= end` interval test would mis-collect
|
||||
// nodes (and `start > end` would falsely reject) once that happens.
|
||||
// Resolve by surface position: a newer replacement seq may occupy an older slot.
|
||||
const nodes = session.surface.nodes
|
||||
const startIdx = nodes.indexOf(start)
|
||||
const endIdx = nodes.indexOf(end)
|
||||
@@ -510,14 +504,8 @@ export class BasicCompactService extends CompactService {
|
||||
// The whole surface fits the retain budget — nothing to compact.
|
||||
if (keepFromIdx === 0) return null
|
||||
|
||||
// Round the cutoff to a tool-pairing boundary: if the cut before
|
||||
// `nodes[keepFromIdx]` is unbalanced (an unanswered tool-call sits before
|
||||
// it — i.e. it is mid-step), extend the retained side head-ward until the
|
||||
// cut is balanced, so the compacted range ends without splitting an
|
||||
// assistant↔result pair. A node that belongs to no step is already a
|
||||
// balanced (free) boundary. Decline if no balanced cut exists at or below
|
||||
// `keepFromIdx` (the compactable range is only an un-splittable open tail
|
||||
// step — retry once it closes).
|
||||
// Round the cutoff head-ward to a tool-pairing boundary; decline when no
|
||||
// safe compactable prefix exists.
|
||||
while (keepFromIdx > 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
if (isToolPairingBalanced(nodes, events, nodes[keepFromIdx]!)) break
|
||||
|
||||
@@ -1516,42 +1516,25 @@ describe('BasicCompactService edge cases', () => {
|
||||
|
||||
describe('BasicCompactService positional range (surface seqs are not monotonic after a replace)', () => {
|
||||
it('compacts a second region after the first replace lands a high-seq summary at the head position', async () => {
|
||||
// A replace inserts the new summary node (a high seq) AT the shadowed
|
||||
// range's surface position, so the surface becomes
|
||||
// [highSeqSummary, …olderRetainedLowerSeqs]. A second compaction over a
|
||||
// range whose start node has a HIGHER seq than its end node must still
|
||||
// succeed — the range is positional, not a numeric seq interval.
|
||||
// Replacement can make surface seqs non-monotonic; ranges remain positional.
|
||||
const svc = createTestService({ auto: false })
|
||||
const session = multiTurnSession(4, 1)
|
||||
|
||||
// First compaction: shadow the two oldest surface nodes.
|
||||
const nodes0 = session.surface.nodes
|
||||
await compactRegion(svc, session, nodes0[0]!, nodes0[1]!, 'm')
|
||||
const firstSummarySeq = session.events.findLast(e => e.type === 'compact/summary')!.seq
|
||||
|
||||
// The summary node now sits at the head with a seq HIGHER than the
|
||||
// retained older nodes that follow it — the non-monotonic surface. (The
|
||||
// head is the user/message replace node, appended after the compact/summary
|
||||
// provenance event.
|
||||
const nodes1 = session.surface.nodes
|
||||
expect(nodes1[0]!).toBeGreaterThan(firstSummarySeq)
|
||||
expect(nodes1[0]!).toBeGreaterThan(nodes1[1]!)
|
||||
|
||||
// Second compaction: shadow [summary(head) … turn-2's step end]. The start
|
||||
// seq (the head summary node) is GREATER than the end seq (an older retained
|
||||
// node), so the range is a SURFACE-POSITION span, not a numeric seq interval.
|
||||
// The end must land on a step boundary (turn-2's assistant message closes
|
||||
// its step).
|
||||
const startSeq = nodes1[0]!
|
||||
const endSeq = nodes1[2]!
|
||||
expect(startSeq).toBeGreaterThan(endSeq)
|
||||
const second = await compactRegion(svc, session, startSeq, endSeq, 'm')
|
||||
const secondSummarySeq = session.events.findLast(e => e.type === 'compact/summary')!.seq
|
||||
|
||||
// Exactly the three nodes at surface positions [0..2] are shadowed, in
|
||||
// surface order — the positional slice, regardless of their seq values.
|
||||
expect(second.shadowedSeqs).toEqual([nodes1[0]!, nodes1[1]!, nodes1[2]!])
|
||||
// The surface still derives cleanly: a new head replace node + the rest.
|
||||
const finalNodes = session.surface.nodes
|
||||
expect(finalNodes[0]!).toBeGreaterThan(secondSummarySeq)
|
||||
expect(session.deriveMessages().length).toBe(finalNodes.length)
|
||||
@@ -1561,20 +1544,13 @@ describe('BasicCompactService positional range (surface seqs are not monotonic a
|
||||
const svc = createTestService({ auto: false })
|
||||
const session = multiTurnSession(3, 1)
|
||||
|
||||
// First compaction shadows the oldest two surface nodes, landing a high-seq
|
||||
// summary node at the head.
|
||||
const n0 = session.surface.nodes
|
||||
await compactRegion(svc, session, n0[0]!, n0[1]!, 'm')
|
||||
|
||||
// Second compaction spans [head summary … turn-2's step end]. The head's seq
|
||||
// is higher than the older retained nodes' seqs, so a log-seq-order walk
|
||||
// would emit the older messages BEFORE the checkpoint.
|
||||
const n1 = session.surface.nodes
|
||||
svc.summarizeCalls = []
|
||||
await compactRegion(svc, session, n1[0]!, n1[2]!, 'm')
|
||||
|
||||
// The extracted transcript follows surface order: the checkpoint (head)
|
||||
// first, then the older retained messages — matching deriveMessages().
|
||||
const { text } = svc.summarizeCalls[0]!
|
||||
const checkpointIdx = text.indexOf('compacted-summary')
|
||||
const olderIdx = text.indexOf('turn 2 user')
|
||||
|
||||
Reference in New Issue
Block a user