review: address ds-review-bot findings on session-stats

- stateVersion starts at 1: the package is new, no persisted rows predate it
- pendingCalls pairs by own key so a provider callId naming a prototype
  property cannot fold toolMs to NaN on an unmatched crash-recovery result
- StatsLine folds the window fallback only when no sessionStats value is
  served, and gates the token group on actual token activity instead of
  steps, so failed-only sessions drop the zero-token group
- correct the crash-step counting semantics in the README and Agent Note:
  recovery closes interrupted steps with a synthetic step/end on reload
- reword the window-scoped alternative as a plain rejected option and name
  isTokenDelta's home beside the StreamChunk type
This commit is contained in:
07akioni
2026-08-12 21:43:35 +08:00
parent ae3f89e772
commit 62a437c082
20 changed files with 68 additions and 29 deletions

View File

@@ -237,6 +237,27 @@ describe('sessionStats wall-time fold (controlled timestamps)', () => {
expect(pruned).toEqual(totals({ turns: 1, steps: 1 }))
})
it('pairs only own pendingCalls keys: a prototype-name callId without a recorded call stays unmatched', () => {
const result = (callId: string): unknown =>
({ turn: 1, step: 1, message: { source: { kind: 'tool', callId } } })
// Crash recovery (TOOL_NOT_STARTED) emits results with no preceding
// tool/call; a provider-minted callId colliding with an Object prototype
// property must read as absent, not as an inherited function that would
// fold toolMs to NaN and fail the value schema.
expect(fold([
at(1_000, 'step/start', { turn: 1, step: 1 }),
at(1_500, 'tool/result', result('toString')),
at(2_000, 'step/end', { turn: 1, step: 1 }),
])).toEqual(totals({ turns: 1, steps: 1 }))
// The same name pairs normally once its call is recorded.
expect(fold([
at(1_000, 'step/start', { turn: 1, step: 1 }),
at(1_100, 'tool/call', { turn: 1, step: 1, callId: 'constructor', name: 'read', arguments: '{}' }),
at(1_600, 'tool/result', result('constructor')),
at(2_000, 'step/end', { turn: 1, step: 1 }),
])).toEqual(totals({ turns: 1, steps: 1, toolMs: 500 }))
})
it('skips decode for an invalid usage report and ignores a duplicate assembled message', () => {
const events = [
at(1_000, 'step/start', { turn: 1, step: 1 }),