Parcourir la source

perf(session): reuse immutable event snapshots

_Kerman il y a 1 semaine
Parent
commit
bcfec8d1c3
43 fichiers modifiés avec 116 ajouts et 116 suppressions
  1. 1 1
      apps/cli/tests/profiles/headless/tests/coding-task.e2e.ts
  2. 1 1
      apps/cli/tests/profiles/headless/tests/compaction.e2e.ts
  3. 1 1
      apps/cli/tests/profiles/headless/tests/full-loop.e2e.ts
  4. 1 1
      apps/cli/tests/profiles/headless/tests/harness.ts
  5. 2 2
      apps/cli/tests/profiles/headless/tests/ptc.e2e.ts
  6. 1 1
      apps/cli/tests/profiles/headless/tests/resume.e2e.ts
  7. 1 1
      apps/cli/tests/profiles/headless/tests/todo-write.e2e.ts
  8. 2 2
      packages/api/session-controller/src/commands.ts
  9. 2 2
      packages/api/session-controller/src/index.ts
  10. 1 1
      packages/compaction/compaction-basic/tests/compaction-basic.spec.ts
  11. 5 5
      packages/compaction/compaction-basic/tests/compaction-loop-repro.spec.ts
  12. 3 3
      packages/compaction/compaction-basic/tests/manual-compaction.spec.ts
  13. 1 1
      packages/compaction/compaction-tool-result-pruner/tests/tool-result-pruner.spec.ts
  14. 2 2
      packages/context/agent-instructions/src/state.ts
  15. 4 4
      packages/context/agent-instructions/tests/agent-instructions.e2e.ts
  16. 20 20
      packages/context/agent-instructions/tests/agent-instructions.spec.ts
  17. 1 1
      packages/context/time-context/tests/time-context.spec.ts
  18. 21 21
      packages/core/agent-loop/tests/contract-regressions.spec.ts
  19. 2 2
      packages/core/agent-loop/tests/interception.spec.ts
  20. 1 1
      packages/core/agent-loop/tests/loop.spec.ts
  21. 1 1
      packages/core/agent-loop/tests/request-cache.e2e.ts
  22. 1 1
      packages/core/agent-loop/tests/request-reconstruction.spec.ts
  23. 1 1
      packages/core/agent-loop/tests/resume.spec.ts
  24. 2 2
      packages/core/agent-loop/tests/tool-calls.spec.ts
  25. 1 1
      packages/core/session/tests/derived-cache.spec.ts
  26. 3 3
      packages/core/session/tests/properties.spec.ts
  27. 2 2
      packages/core/session/tests/session.spec.ts
  28. 1 1
      packages/core/session/tests/surface.spec.ts
  29. 1 1
      packages/fs/tool-fs/tests/fs-tools.e2e.ts
  30. 3 3
      packages/guard/repeat-tool-reminder/tests/repeat-tool-reminder.spec.ts
  31. 9 9
      packages/hooks/hook-protocol/tests/events.spec.ts
  32. 2 2
      packages/hooks/hooks-claude-code/tests/bridge.spec.ts
  33. 1 1
      packages/hooks/hooks-claude-code/tests/coverage-cases.ts
  34. 1 1
      packages/hooks/hooks-codex/tests/bridge.spec.ts
  35. 1 1
      packages/hooks/hooks-codex/tests/coverage-cases.ts
  36. 3 3
      packages/session/session-telemetry/tests/telemetry.spec.ts
  37. 3 3
      packages/shell/tool-bash/tests/integration.spec.ts
  38. 1 1
      packages/subagent/subagent-in-process-driver/src/index.ts
  39. 1 1
      packages/subagent/subagent-in-process-driver/tests/inheritance.spec.ts
  40. 1 1
      packages/subagent/subagent-in-process-driver/tests/subagent-in-process-driver.spec.ts
  41. 1 1
      packages/subagent/subagent-spawn-in-process/tests/spawn-in-process.e2e.ts
  42. 2 2
      packages/subagent/subagent/src/descriptor-seed.ts
  43. 1 1
      packages/todo/tool-todo/tests/invariant.spec.ts

+ 1 - 1
apps/cli/tests/profiles/headless/tests/coding-task.e2e.ts

@@ -67,7 +67,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('coding task: fix a failing test
     await waitForIdle(ctx, agent)
 
     // The agent claims success…
-    const summary = finalText([...agent.session.snapshotEvents()]).toLowerCase()
+    const summary = finalText(agent.session.snapshotEvents()).toLowerCase()
     expect(summary.length).toBeGreaterThan(0)
 
     // …and the world agrees: the test passes when WE run it, and the test

+ 1 - 1
apps/cli/tests/profiles/headless/tests/compaction.e2e.ts

@@ -56,7 +56,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('compaction: a long session compa
       }], source: { kind: 'user' } }))
     await waitForIdle(ctx, agent)
 
-    const events = [...agent.session.snapshotEvents()]
+    const events = agent.session.snapshotEvents()
 
     // A compaction ran: the start…end bracket landed in the real log.
     const starts = events.filter(e => e.type === 'compaction/start')

+ 1 - 1
apps/cli/tests/profiles/headless/tests/full-loop.e2e.ts

@@ -34,7 +34,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('full loop: real model + real bas
     agent.followup(createUserMessage({ content: [{ type: 'text', text: 'Run `echo e2e-ok` with the bash tool and tell me its exact output.' }], source: { kind: 'user' } }))
     await waitForIdle(ctx, agent)
 
-    const events = [...agent.session.snapshotEvents()]
+    const events = agent.session.snapshotEvents()
     const calls = events.filter(event => event.type === 'tool/call')
     expect(calls.length).toBeGreaterThan(0)
     expect(calls.some(event => event.data.name === 'bash')).toBe(true)

+ 1 - 1
apps/cli/tests/profiles/headless/tests/harness.ts

@@ -96,7 +96,7 @@ export function waitForIdle(ctx: Context, agent: Agent): Promise<void> {
   })
 }
 
-export function finalText(events: SessionEvent[]): string {
+export function finalText(events: readonly SessionEvent[]): string {
   const message = events.findLast(event => event.type === 'assistant/message')
   if (message?.type !== 'assistant/message') return ''
   return message.data.message.content

+ 2 - 2
apps/cli/tests/profiles/headless/tests/ptc.e2e.ts

@@ -366,7 +366,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('PTC mode: real model writes a pr
         + 'and return only the joined string.',
       }], source: { kind: 'user' } }))
     await waitForIdle(ctx, agent)
-    const events: SessionEvent[] = [...agent.session.snapshotEvents()]
+    const events: readonly SessionEvent[] = agent.session.snapshotEvents()
 
     // The wire contract: every request this session made offered EXACTLY ONE
     // tool — run_code (the logged header snapshots the assembled list).
@@ -418,7 +418,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('PTC mode: real model writes a pr
       }], source: { kind: 'user' } }))
     await waitForIdle(ctx, handle.agent)
 
-    const events: SessionEvent[] = [...handle.agent.session.snapshotEvents()]
+    const events: readonly SessionEvent[] = handle.agent.session.snapshotEvents()
     const dispatch = events.find(event => event.type === 'tool/code-dispatch' && event.data.name === 'read')
     const outerResult = events.find(event => event.type === 'tool/result')
     const workspaceContext = await vi.waitFor(() => {

+ 1 - 1
apps/cli/tests/profiles/headless/tests/resume.e2e.ts

@@ -63,6 +63,6 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('resume: continue a persisted ses
     await waitForIdle(ctx, resumed)
 
     // The model recalls it — only possible from the resumed history.
-    expect(finalText([...resumed.session.snapshotEvents()])).toContain(SECRET)
+    expect(finalText(resumed.session.snapshotEvents())).toContain(SECRET)
   }, 180_000)
 })

+ 1 - 1
apps/cli/tests/profiles/headless/tests/todo-write.e2e.ts

@@ -37,7 +37,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('todo_write: real model records a
       + 'Send all three in one todo_write call, then reply with the single word DONE.' }], source: { kind: 'user' } }))
     await waitForIdle(ctx, agent)
 
-    const events = [...agent.session.snapshotEvents()]
+    const events = agent.session.snapshotEvents()
 
     // The model actually called the tool.
     const calls = events.filter(event => event.type === 'tool/call')

+ 2 - 2
packages/api/session-controller/src/commands.ts

@@ -48,7 +48,7 @@ import type {
 interface SessionReadState {
   readonly id: SessionId
   readonly header: SessionHeader
-  readonly events: SessionEvent[]
+  readonly events: readonly SessionEvent[]
 }
 
 /** Implements Session business commands delegated by the Session Controller Remote service. */
@@ -484,7 +484,7 @@ export class SessionCommandController {
   private async readSessionState(sessionId: SessionId): Promise<SessionReadState> {
     const attached = this.ctx.sessions.get(sessionId)
     if (attached !== undefined) {
-      return { id: attached.id, header: attached.header, events: [...attached.snapshotEvents()] }
+      return { id: attached.id, header: attached.header, events: attached.snapshotEvents() }
     }
     const inspected = await inspectApiSession(this.ctx, sessionId)
     return { id: inspected.meta.id, header: inspected.meta, events: inspected.events }

+ 2 - 2
packages/api/session-controller/src/index.ts

@@ -191,10 +191,10 @@ export class SessionController extends TypertRemoteService {
   inspect(
     sessionId: SessionId,
     signal?: AbortSignal,
-  ): Promise<{ meta: SessionHeader; events: SessionEvent[] }> {
+  ): Promise<{ meta: SessionHeader; events: readonly SessionEvent[] }> {
     const attached = this.ctx.sessions.get(sessionId)
     if (attached !== undefined) {
-      return Promise.resolve({ meta: attached.header, events: [...attached.snapshotEvents()] })
+      return Promise.resolve({ meta: attached.header, events: attached.snapshotEvents() })
     }
     return inspectApiSession(this.ctx, sessionId, signal)
   }

+ 1 - 1
packages/compaction/compaction-basic/tests/compaction-basic.spec.ts

@@ -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.snapshotEvents()])
+    const replay = Session.create(SessionId('replay'), session.snapshotEvents())
     expect(replay.deriveMessages()).toEqual(session.deriveMessages())
   })
 

+ 5 - 5
packages/compaction/compaction-basic/tests/compaction-loop-repro.spec.ts

@@ -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.snapshotEvents()]
+  return session.snapshotEvents()
 }
 
 describe('CBR-001: a real-loop checkpoint is a valid boundary on both sides', () => {
@@ -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.snapshotEvents()]
+      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.snapshotEvents()]
+      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.snapshotEvents()]
+        const events = agent.session.snapshotEvents()
         const stepStart = events.find(event =>
           event.type === 'step/start' && event.data.turn === 3 && event.data.step === 1,
         )!

+ 3 - 3
packages/compaction/compaction-basic/tests/manual-compaction.spec.ts

@@ -442,7 +442,7 @@ describe('compactNow transaction and failure classification', () => {
       compactionId: CompactionId('stale-manual-compaction'),
       turn: null,
     })
-    const reloaded = Session.create(SessionId('stale-orphan'), [...original.snapshotEvents()])
+    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)
@@ -461,7 +461,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.snapshotEvents()])
+    const reloaded = Session.create(SessionId('reloaded-orphan'), original.snapshotEvents())
     const agent = fakeAgent(reloaded, () => () => undefined)
 
     await expect(compact.compactNow(agent, SIGNAL)).resolves.not.toBeNull()
@@ -716,7 +716,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.snapshotEvents()]
+      const before = testCase.session.snapshotEvents()
       const reason = Object.freeze({ kind: 'cancelled', case: testCase.name })
       const controller = new AbortController()
       controller.abort(reason)

+ 1 - 1
packages/compaction/compaction-tool-result-pruner/tests/tool-result-pruner.spec.ts

@@ -254,7 +254,7 @@ describe('ToolResultPruner session transaction', () => {
       turn: 2,
     })
     service().pruneSession(session)
-    const replay = Session.create(session.id, [...session.snapshotEvents()])
+    const replay = Session.create(session.id, session.snapshotEvents())
     expect(replay.deriveMessages()).toEqual(session.deriveMessages())
     expect(replay.surface.replaceGeneration).toBe(session.surface.replaceGeneration)
   })

+ 2 - 2
packages/context/agent-instructions/src/state.ts

@@ -139,11 +139,11 @@ function visibleInstructionChanges(
 ): Map<string, AgentInstructionChange> {
   const visibleSeqs = new Set(agent.session.surface.nodes)
   const visible = new Map<string, AgentInstructionChange>()
-  for (const [seq, event] of agent.session.snapshotEvents().entries()) {
+  for (const event of agent.session.snapshotEvents()) {
     if (event.type !== 'user/message' || !isWorkspaceContextSource(event.data.source)) continue
     const changes = workspaceInstructionChanges(event.data.source)
     for (const change of changes) {
-      if (visibleSeqs.has(seq)) visible.set(change.scope, change)
+      if (visibleSeqs.has(event.seq)) visible.set(change.scope, change)
     }
   }
   for (const message of authorityMessages) {

+ 4 - 4
packages/context/agent-instructions/tests/agent-instructions.e2e.ts

@@ -68,7 +68,7 @@ function waitForIdle(ctx: Context, agent: Agent): Promise<void> {
   })
 }
 
-function finalText(events: SessionEvent[]): string {
+function finalText(events: readonly SessionEvent[]): string {
   const message = events.findLast(event => event.type === 'assistant/message')
   if (message?.type !== 'assistant/message') return ''
   return message.data.message.content
@@ -84,7 +84,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('workspace context e2e: real mode
     live.agent.followup(createUserMessage({ content: [{ type: 'text', text: 'Workspace context handshake?' }], source: { kind: 'user' } }))
     await waitForIdle(live.ctx, live.agent)
 
-    expect(finalText([...live.agent.session.snapshotEvents()])).toContain(PROBE)
+    expect(finalText(live.agent.session.snapshotEvents())).toContain(PROBE)
   }, 120_000)
 
   it('loads a nested AGENTS.md after the real read tool touches a descendant file', async () => {
@@ -96,7 +96,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('workspace context e2e: real mode
     live.agent.followup(createUserMessage({ content: [{ type: 'text', text: 'Use the read tool to inspect pkg/deep/file.txt. After reading it, answer: nested instruction handshake?' }], source: { kind: 'user' } }))
     await waitForIdle(live.ctx, live.agent)
 
-    expect(finalText([...live.agent.session.snapshotEvents()])).toContain(NESTED_PROBE)
+    expect(finalText(live.agent.session.snapshotEvents())).toContain(NESTED_PROBE)
   }, 120_000)
 
   it('appends changed baseline instructions after a real file-tool touch without rewriting the frozen prefix', async () => {
@@ -109,7 +109,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('workspace context e2e: real mode
     live.agent.followup(createUserMessage({ content: [{ type: 'text', text: 'You must use the read tool to inspect trigger.txt. After reading it, answer: updated workspace context handshake?' }], source: { kind: 'user' } }))
     await waitForIdle(live.ctx, live.agent)
 
-    const events = [...live.agent.session.snapshotEvents()]
+    const events = live.agent.session.snapshotEvents()
     const update = events.find(event => event.type === 'user/message'
       && event.data.source.kind === 'agent-instructions'
       && event.data.source.baseline !== true)

+ 20 - 20
packages/context/agent-instructions/tests/agent-instructions.spec.ts

@@ -187,7 +187,7 @@ async function mountFileToolsAndWorkspaceContext(ctx: Context, config: workspace
   return mountWorkspaceContextPlugin(ctx, config)
 }
 
-function stubAgent(cwd?: string, seed: SessionEvent[] = []): Agent {
+function stubAgent(cwd?: string, seed: readonly SessionEvent[] = []): Agent {
   const id = SessionId('s1')
   const session = Session.create(id, seed, cwd === undefined ? undefined : { version: SESSION_FORMAT_VERSION, id, createdAt: 0, cwd })
   return {
@@ -1115,9 +1115,9 @@ describe('workspace context request injection', () => {
       const original = stubAgent(root)
       await composeBaselinePrefix(ctx, original)
 
-      const firstResume = stubAgent(root, [...original.session.snapshotEvents()])
+      const firstResume = stubAgent(root, original.session.snapshotEvents())
       await composeBaselinePrefix(ctx, firstResume)
-      const secondResume = stubAgent(root, [...firstResume.session.snapshotEvents()])
+      const secondResume = stubAgent(root, firstResume.session.snapshotEvents())
       await composeBaselinePrefix(ctx, secondResume)
 
       expect(baselineEvents(firstResume)).toHaveLength(1)
@@ -1144,7 +1144,7 @@ describe('workspace context request injection', () => {
       await composeBaselinePrefix(ctx, original)
 
       fs.throwOnStat.add(join(root, 'AGENTS.md'))
-      const resumed = stubAgent(root, [...original.session.snapshotEvents()])
+      const resumed = stubAgent(root, original.session.snapshotEvents())
       await composeBaselinePrefix(ctx, resumed)
 
       expect(baselineEvents(resumed)).toHaveLength(1)
@@ -1170,9 +1170,9 @@ describe('workspace context request injection', () => {
       const original = stubAgent(cwd)
       await composeBaselinePrefix(ctx, original)
 
-      const firstResume = stubAgent(cwd, [...original.session.snapshotEvents()])
+      const firstResume = stubAgent(cwd, original.session.snapshotEvents())
       await composeBaselinePrefix(ctx, firstResume)
-      const secondResume = stubAgent(cwd, [...firstResume.session.snapshotEvents()])
+      const secondResume = stubAgent(cwd, firstResume.session.snapshotEvents())
       await composeBaselinePrefix(ctx, secondResume)
 
       expect(baselineEvents(secondResume)).toHaveLength(1)
@@ -1200,7 +1200,7 @@ describe('workspace context request injection', () => {
       await composeBaselinePrefix(ctx, original)
 
       await write(join(cwd, 'AGENTS.md'), 'package rule')
-      const resumed = stubAgent(cwd, [...original.session.snapshotEvents()])
+      const resumed = stubAgent(cwd, original.session.snapshotEvents())
       await composeBaselinePrefix(ctx, resumed)
 
       expect(baselineEvents(resumed)).toHaveLength(1)
@@ -1237,7 +1237,7 @@ describe('workspace context request injection', () => {
         maxBytes: 65536,
         instructionFileCandidates: ['CLAUDE.md', 'AGENTS.md'],
       })
-      const resumed = stubAgent(root, [...original.session.snapshotEvents()])
+      const resumed = stubAgent(root, original.session.snapshotEvents())
       await composeBaselinePrefix(resumedCtx, resumed)
 
       const baselines = baselineEvents(resumed)
@@ -1256,7 +1256,7 @@ describe('workspace context request injection', () => {
         : [])
       expect(new Set(baselineIdentities).size).toBe(2)
 
-      const repeated = stubAgent(root, [...resumed.session.snapshotEvents()])
+      const repeated = stubAgent(root, resumed.session.snapshotEvents())
       await composeBaselinePrefix(resumedCtx, repeated)
       expect(baselineEvents(repeated)).toHaveLength(2)
     } finally {
@@ -1290,7 +1290,7 @@ describe('workspace context request injection', () => {
         maxBytes: 65536,
         instructionFileCandidates: ['CLAUDE.md'],
       })
-      const claudeResume = stubAgent(root, [...original.session.snapshotEvents()])
+      const claudeResume = stubAgent(root, original.session.snapshotEvents())
       await composeBaselinePrefix(claudeCtx, claudeResume)
       const claudeBaseline = baselineEvents(claudeResume).at(-1)
       expect(claudeBaseline?.type === 'user/message' && claudeBaseline.data.source.kind === 'agent-instructions'
@@ -1305,7 +1305,7 @@ describe('workspace context request injection', () => {
         maxBytes: 65536,
         instructionFileCandidates: ['AGENTS.md'],
       })
-      const restored = stubAgent(root, [...claudeResume.session.snapshotEvents()])
+      const restored = stubAgent(root, claudeResume.session.snapshotEvents())
       await composeBaselinePrefix(restoredCtx, restored)
       const restoredBaseline = baselineEvents(restored).at(-1)
       expect(restoredBaseline?.type === 'user/message' && restoredBaseline.data.source.kind === 'agent-instructions'
@@ -1340,7 +1340,7 @@ describe('workspace context request injection', () => {
         maxBytes: 65536,
         instructionFileCandidates: ['POLICY.md'],
       })
-      const resumed = stubAgent(root, [...original.session.snapshotEvents()])
+      const resumed = stubAgent(root, original.session.snapshotEvents())
       await composeBaselinePrefix(resumedCtx, resumed)
 
       const baselines = baselineEvents(resumed)
@@ -1354,7 +1354,7 @@ describe('workspace context request injection', () => {
         { action: 'remove', scope: sk('.', 'AGENTS.md'), path: 'AGENTS.md' },
       ])
 
-      const repeated = stubAgent(root, [...resumed.session.snapshotEvents()])
+      const repeated = stubAgent(root, resumed.session.snapshotEvents())
       await composeBaselinePrefix(resumedCtx, repeated)
       expect(baselineEvents(repeated)).toHaveLength(2)
     } finally {
@@ -1384,7 +1384,7 @@ describe('workspace context request injection', () => {
 
       await fiber.dispose()
       await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 })
-      const resumed = stubAgent(root, [...original.session.snapshotEvents()])
+      const resumed = stubAgent(root, original.session.snapshotEvents())
       agentEvents(ctx, resumed).emit('agent/session-start', { source: 'resume' })
       const claimed = resumed.inbox.claim('next-step', 1)
       const decision = await agentEvents(ctx, resumed).waterfall(
@@ -1430,7 +1430,7 @@ describe('workspace context request injection', () => {
       await write(join(root, 'AGENTS.md'), 'new repo rule')
       await fiber.dispose()
       await mountWorkspaceContextPlugin(ctx, { dshHome: home, maxBytes: 65536 })
-      const resumed = stubAgent(root, [...original.session.snapshotEvents()])
+      const resumed = stubAgent(root, original.session.snapshotEvents())
       agentEvents(ctx, resumed).emit('agent/session-start', { source: 'resume' })
       const staleClaim = resumed.inbox.claim('next-step', 1)
       const staleDecision = await agentEvents(ctx, resumed).waterfall(
@@ -1483,7 +1483,7 @@ describe('workspace context request injection', () => {
       await originalCtx.fiber.dispose()
       if (provideFs) await resumedCtx.plugin(LocalFileSystem, { cwd: '/' })
       await mountWorkspaceContextPlugin(resumedCtx, { dshHome: home, maxBytes })
-      const resumed = stubAgent(root, [...original.session.snapshotEvents()])
+      const resumed = stubAgent(root, original.session.snapshotEvents())
       agentEvents(resumedCtx, resumed).emit('agent/session-start', { source: 'resume' })
       const claimed = resumed.inbox.claim('next-step', 1)
       const decision = await agentEvents(resumedCtx, resumed).waterfall(
@@ -1790,7 +1790,7 @@ describe('workspace context request injection', () => {
       // The first resumed pre-step retains the compatible visible baseline and
       // appends only the offline file transition needed to reach current state.
       await write(join(root, 'AGENTS.md'), 'new root rule after offline edit')
-      const resumed = stubAgent(root, [...original.session.snapshotEvents()])
+      const resumed = stubAgent(root, original.session.snapshotEvents())
 
       // Resume announces its lifecycle start before the first step.
       agentEvents(ctx, resumed).emit('agent/session-start', { source: 'resume' })
@@ -3540,7 +3540,7 @@ describe('dynamic nested workspace context injection', () => {
         agent,
       })
       await appendAdditionalContexts(ctx, agent)
-      const resumed = stubAgent(root, [...agent.session.snapshotEvents()])
+      const resumed = stubAgent(root, agent.session.snapshotEvents())
 
       const afterResume = await ctx.tools.execute({
         signal: testToolSignal,
@@ -3574,7 +3574,7 @@ describe('dynamic nested workspace context injection', () => {
       })
       await appendAdditionalContexts(ctx, original)
       await write(join(root, 'pkg/AGENTS.md'), 'new nested rule after resume')
-      const resumed = stubAgent(root, [...original.session.snapshotEvents()])
+      const resumed = stubAgent(root, original.session.snapshotEvents())
 
       await composeBaselinePrefix(ctx, resumed)
 
@@ -4608,7 +4608,7 @@ describe('workspace context inbox synchronization', () => {
         callId: ToolCallId('recover-pending-a'), name: 'read', arguments: { file_path: join('a', 'file.txt') }, agent: original,
       })
       await syncWorkspaceContext(ctx, original)
-      const resumed = stubAgent(root, [...original.session.snapshotEvents()])
+      const resumed = stubAgent(root, original.session.snapshotEvents())
 
       await ctx.tools.execute({
         signal: testToolSignal,

+ 1 - 1
packages/context/time-context/tests/time-context.spec.ts

@@ -304,7 +304,7 @@ describe('durable step context', () => {
     original.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
     expect(JSON.stringify(original.deriveMessages())).not.toContain('Time sampled while preparing')
 
-    const resumed = Session.create(SessionId('resumed'), [...original.snapshotEvents()])
+    const resumed = Session.create(SessionId('resumed'), original.snapshotEvents())
     const resumedAgent = sessionAgent(resumed)
     vi.setSystemTime(BASE + 999)
     openMessageTurn(resumed, 2)

+ 21 - 21
packages/core/agent-loop/tests/contract-regressions.spec.ts

@@ -175,7 +175,7 @@ describe('abort during tool execution ends the turn', () => {
     send(agent, 'go')
     await waitForIdle(ctx, agent)
 
-    const events = [...agent.session.snapshotEvents()]
+    const events = agent.session.snapshotEvents()
     expect(events
       .filter(event => event.type === 'tool/result'
         || (event.type === 'user/message' && event.data.source.kind === 'plugin')
@@ -534,7 +534,7 @@ describe('turn numbering continues across seeded sessions', () => {
     await ctx2.plugin(AgentLoop, { agents: [] })
     ctx2.llm.registerAdapter(['mock'], second)
 
-    const seeded = ctx2.sessions.create(SessionId('forked'), { seed: [...agent.session.snapshotEvents()] })
+    const seeded = ctx2.sessions.create(SessionId('forked'), { seed: agent.session.snapshotEvents() })
     const forked = new ReactLoopAgent(
       ctx2, SessionId('forked-agent'), { provider: 'mock', model: 'mock' }, seeded,
     )
@@ -603,7 +603,7 @@ describe('a finish-error stream chunk ends the turn as error, not completed', ()
     expect(errors[0]).toBeInstanceOf(LlmError)
     expect((errors[0] as LlmError).failure).toEqual(failure)
 
-    const events = [...agent.session.snapshotEvents()]
+    const events = agent.session.snapshotEvents()
     const turnEnd = events.find(event => event.type === 'turn/end')
     expect(turnEnd).toMatchObject({ data: { reason: { kind: 'error', error: failure } } })
     // A failed step must not synthesize an assistant message.
@@ -625,7 +625,7 @@ describe('a finish-error stream chunk ends the turn as error, not completed', ()
     await waitForIdle(ctx, agent)
 
     expect(reasons).toEqual([{ kind: 'error', error: { message: 'model stream aborted', code: 'ABORTED' } }])
-    expect([...agent.session.snapshotEvents()].some(event => event.type === 'assistant/message')).toBe(false)
+    expect(agent.session.snapshotEvents().some(event => event.type === 'assistant/message')).toBe(false)
   })
 
   it('handles a finish error without a code (code key omitted)', async () => {
@@ -655,7 +655,7 @@ describe('step boundary publication order', () => {
     const observed: { turn: number; step: number; lastEventType: string | undefined; sawStepStart: boolean }[] = []
     ctx.on('session/event', (subject, event) => {
       if (subject !== agent.session || event.type !== 'step/start') return
-      const events = [...subject.snapshotEvents()]
+      const events = subject.snapshotEvents()
       const last = events.at(-1)
       observed.push({
         turn: event.data.turn,
@@ -691,7 +691,7 @@ describe('turn and step boundary recovery', () => {
 
   /** Count turn/step boundary events for balance assertions. */
   function boundaryCounts(agent: Agent) {
-    const e = [...agent.session.snapshotEvents()]
+    const e = agent.session.snapshotEvents()
     return {
       turnStart: e.filter(x => x.type === 'turn/start').length,
       turnEnd: e.filter(x => x.type === 'turn/end').length,
@@ -721,7 +721,7 @@ describe('turn and step boundary recovery', () => {
     send(agent, 'go')
     await waitForIdle(ctx, agent)
 
-    const e = [...agent.session.snapshotEvents()]
+    const e = agent.session.snapshotEvents()
     const c = boundaryCounts(agent)
     expect(c).toMatchObject({ turnStart: 1, turnEnd: 1, stepStart: 1, stepEnd: 1, errors: 0 })
     expect(errors).toEqual([])
@@ -878,7 +878,7 @@ describe('turn and step boundary recovery', () => {
     await fiber.dispose() // dispose during the hanging step
     await driverDone(agent)
 
-    const e = [...agent.session.snapshotEvents()]
+    const e = agent.session.snapshotEvents()
     const turnStarts = e.filter(x => x.type === 'turn/start').length
     const turnEnds = e.filter(x => x.type === 'turn/end').length
     expect(turnStarts).toBe(1)
@@ -911,7 +911,7 @@ describe('turn and step boundary recovery', () => {
     send(agent, 'go')
     await agent.whenIdle()
 
-    const e = [...agent.session.snapshotEvents()]
+    const e = agent.session.snapshotEvents()
     expect(e.filter(x => x.type === 'turn/start' || x.type === 'turn/end').map(x => x.type))
       .toEqual(['turn/start', 'turn/end'])
     expect(e.find(x => x.type === 'turn/end')?.data.reason)
@@ -940,10 +940,10 @@ describe('turn and step boundary recovery', () => {
     expect(errors).toEqual([])
     // Session contains the observer failure per listener, so the committed turn
     // remains visible to later observers and executes normally.
-    const types = [...agent.session.snapshotEvents()].map(e => e.type)
+    const types = agent.session.snapshotEvents().map(e => e.type)
     expect(types.filter(t => t === 'turn/start')).toHaveLength(1)
     expect(types.filter(t => t === 'turn/end')).toHaveLength(1)
-    const lastBoundary = [...agent.session.snapshotEvents()].reverse().find(e => e.type === 'turn/start' || e.type === 'turn/end')
+    const lastBoundary = agent.session.snapshotEvents().findLast(e => e.type === 'turn/start' || e.type === 'turn/end')
     expect(lastBoundary?.type).toBe('turn/end')
     expect(agent.session.snapshotEvents().at(-1)?.type).toBe('turn/end')
 
@@ -977,7 +977,7 @@ describe('turn and step boundary recovery', () => {
       .toEqual({ kind: 'completed' })
 
     // step/end precedes turn/end (ordering contract)
-    const e = [...agent.session.snapshotEvents()]
+    const e = agent.session.snapshotEvents()
     const stepEndIdx = e.findIndex(x => x.type === 'step/end')
     const turnEndIdx = e.findIndex(x => x.type === 'turn/end')
     expect(stepEndIdx).toBeGreaterThanOrEqual(0)
@@ -1011,7 +1011,7 @@ describe('turn and step boundary recovery', () => {
     send(agent, 'go')
     await waitForIdle(ctx, agent)
 
-    const e = [...agent.session.snapshotEvents()]
+    const e = agent.session.snapshotEvents()
     // Both step/end and turn/end are present — finalization ran to completion.
     expect(e.some(x => x.type === 'step/end')).toBe(true)
     expect(e.some(x => x.type === 'turn/end')).toBe(true)
@@ -1041,7 +1041,7 @@ describe('turn and step boundary recovery', () => {
     send(agent, 'go')
     await waitForIdle(ctx, agent)
     // turn 1 is balanced despite the throwing turn/end listener.
-    const e1 = [...agent.session.snapshotEvents()]
+    const e1 = agent.session.snapshotEvents()
     expect(e1.filter(x => x.type === 'turn/start')).toHaveLength(1)
     expect(e1.filter(x => x.type === 'turn/end')).toHaveLength(1)
     expect(e1.at(-1)?.type).toBe('turn/end')
@@ -1050,7 +1050,7 @@ describe('turn and step boundary recovery', () => {
     send(agent, 'again')
     await waitForIdle(ctx, agent)
     expect(adapter.requests).toHaveLength(2)
-    expect([...agent.session.snapshotEvents()].filter(x => x.type === 'turn/end')).toHaveLength(2)
+    expect(agent.session.snapshotEvents().filter(x => x.type === 'turn/end')).toHaveLength(2)
   })
 })
 
@@ -1082,7 +1082,7 @@ describe('tool result call identity', () => {
     await waitForIdle(ctx, agent)
 
     // The logged tool/result.callId is the originating call.id.
-    const resultEvent = [...agent.session.snapshotEvents()].find(e => e.type === 'tool/result')
+    const resultEvent = agent.session.snapshotEvents().find(e => e.type === 'tool/result')
     expect(resultEvent?.type).toBe('tool/result')
     if (resultEvent?.type === 'tool/result') {
       expect(resultEvent.data.message.source.callId).toBe(ToolCallId('c1'))
@@ -1146,7 +1146,7 @@ describe('disposal and cancellation during pre-step assembly', () => {
     await driverDone(agent)
     unlisten()
 
-    const e = [...agent.session.snapshotEvents()]
+    const e = agent.session.snapshotEvents()
     expect(e.filter(x => x.type === 'turn/start' || x.type === 'turn/end').map(x => x.type))
       .toEqual(['turn/start', 'turn/end'])
     expect(e.some(x => x.type === 'step/start')).toBe(false)
@@ -1194,7 +1194,7 @@ describe('disposal and cancellation during pre-step assembly', () => {
     await driverDone(agent)
     unlisten()
 
-    const e = [...agent.session.snapshotEvents()]
+    const e = agent.session.snapshotEvents()
     expect(e.filter(x => x.type === 'turn/start' || x.type === 'turn/end').map(x => x.type))
       .toEqual(['turn/start', 'turn/end'])
     expect(e.some(x => x.type === 'step/start')).toBe(false)
@@ -1244,7 +1244,7 @@ describe('disposal and cancellation during pre-step assembly', () => {
     await driverDone(agent)
 
     // The post-listener cancellation check catches disposal before any step or LLM call.
-    const e = [...agent.session.snapshotEvents()]
+    const e = agent.session.snapshotEvents()
     expect(e.filter(x => x.type === 'turn/start' || x.type === 'turn/end').map(x => x.type))
       .toEqual(['turn/start', 'turn/end'])
     expect(e.some(x => x.type === 'step/start')).toBe(false)
@@ -1291,7 +1291,7 @@ describe('disposal and cancellation during pre-step assembly', () => {
     await fiber.dispose()
     await driverDone(agent)
 
-    const e = [...agent.session.snapshotEvents()]
+    const e = agent.session.snapshotEvents()
     expect(e.filter(x => x.type === 'turn/start' || x.type === 'turn/end').map(x => x.type))
       .toEqual(['turn/start', 'turn/end'])
     expect(e.some(x => x.type === 'step/start')).toBe(false)
@@ -1336,7 +1336,7 @@ describe('disposal and cancellation during pre-step assembly', () => {
     await disposalDone
     await driverDone(agent)
 
-    const e = [...agent.session.snapshotEvents()]
+    const e = agent.session.snapshotEvents()
     expect(e.filter(x => x.type === 'turn/start' || x.type === 'turn/end').map(x => x.type))
       .toEqual(['turn/start', 'turn/end'])
     expect(e.find(x => x.type === 'turn/end')?.data.reason)

+ 2 - 2
packages/core/agent-loop/tests/interception.spec.ts

@@ -56,8 +56,8 @@ function send(agent: Agent, text: string) {
   agent.followup(createUserMessage({ content: [{ type: 'text', text }], source: { kind: 'user' } }))
 }
 
-function events(agent: Agent): SessionEvent[] {
-  return [...agent.session.snapshotEvents()]
+function events(agent: Agent): readonly SessionEvent[] {
+  return agent.session.snapshotEvents()
 }
 
 describe('agent/pre-step', () => {

+ 1 - 1
packages/core/agent-loop/tests/loop.spec.ts

@@ -1526,7 +1526,7 @@ describe('agent loop', () => {
     send(agent, 'run')
     await waitForIdle(ctx, agent)
 
-    const replayed = ctx.sessions.create(SessionId('replayed'), { seed: [...agent.session.snapshotEvents()] })
+    const replayed = ctx.sessions.create(SessionId('replayed'), { seed: agent.session.snapshotEvents() })
     expect(replayed.deriveMessages()).toEqual(agent.session.deriveMessages())
     // event-by-event identity of types over the inherited prefix
     expect(replayed.snapshotEvents().slice(0, agent.session.seq).map(e => e.type)).toEqual(

+ 1 - 1
packages/core/agent-loop/tests/request-cache.e2e.ts

@@ -82,7 +82,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('log-derived request cache hits (
     agent.followup(createUserMessage({ content: [{ type: 'text', text: 'Thanks. Repeat that value one more time.' }], source: { kind: 'user' } }))
     await waitForIdle(ctx, agent)
 
-    const usages = [...agent.session.snapshotEvents()]
+    const usages = agent.session.snapshotEvents()
       .filter(e => e.type === 'assistant/message')
       .map(e => e.data.usage)
     expect(usages.length).toBeGreaterThanOrEqual(3) // 2 steps in turn 1 + ≥1 in turn 2

+ 1 - 1
packages/core/agent-loop/tests/request-reconstruction.spec.ts

@@ -650,7 +650,7 @@ describe('request stability across the loop', () => {
     const ctx2 = await harness(adapter2)
     const handle = await ctx2.agents.create({
       sessionId: SessionId('gen2-session'),
-      seed: [...agent.session.snapshotEvents()],
+      seed: agent.session.snapshotEvents(),
       agentOptions: { provider: 'mock', model: 'mock' },
     })
     const agent2 = handle.agent

+ 1 - 1
packages/core/agent-loop/tests/resume.spec.ts

@@ -650,7 +650,7 @@ describe('the session-persistence Agent Note: AgentLoop factory create/resume',
     const a1 = (await ctx1.agents.create({ sessionId: SessionId('sess-resume'), meta: { cwd: '/w' } })).agent
     a1.followup(createUserMessage({ content: [{ type: 'text', text: 'first question' }], source: { kind: 'user' } }))
     await waitForIdle(ctx1, a1)
-    const events1 = [...a1.session.snapshotEvents()]
+    const events1 = a1.session.snapshotEvents()
     const seqs1 = events1.map(e => e.seq)
     expect(seqs1).toEqual([...seqs1].sort((x, y) => x - y)) // contiguous
     await ctx1.fiber.dispose()

+ 2 - 2
packages/core/agent-loop/tests/tool-calls.spec.ts

@@ -41,8 +41,8 @@ function waitForIdle(ctx: Context, agent: Agent): Promise<void> {
   })
 }
 
-function events(agent: Agent): SessionEvent[] {
-  return [...agent.session.snapshotEvents()]
+function events(agent: Agent): readonly SessionEvent[] {
+  return agent.session.snapshotEvents()
 }
 
 /** Build one assistant response containing the supplied tool calls. */

+ 1 - 1
packages/core/session/tests/derived-cache.spec.ts

@@ -16,7 +16,7 @@ function userText(session: Session, text: string): void {
 
 /** From-scratch oracle: replay the log into a fresh session and derive. */
 function scratch(session: Session): unknown {
-  return Session.create(SessionId(`${session.id}-scratch-${session.seq}`), [...session.snapshotEvents()]).deriveMessages()
+  return Session.create(SessionId(`${session.id}-scratch-${session.seq}`), session.snapshotEvents()).deriveMessages()
 }
 
 describe('derived-message cache', () => {

+ 3 - 3
packages/core/session/tests/properties.spec.ts

@@ -110,7 +110,7 @@ describe('Session properties', () => {
   it('replay-from-seed reproduces the derivation identically', () => {
     fc.assert(fc.property(logArb, (events) => {
       const original = build(events)
-      const replayed = Session.create(SessionId(`replay-${counter++}`), [...original.snapshotEvents()])
+      const replayed = Session.create(SessionId(`replay-${counter++}`), original.snapshotEvents())
       expect(replayed.deriveMessages()).toEqual(original.deriveMessages())
       // Every explicit replay grows by exactly one log-only boundary.
       expect(replayed.snapshotEvents().slice(0, original.seq)).toEqual(original.snapshotEvents())
@@ -121,8 +121,8 @@ describe('Session properties', () => {
   it('replaying a log that already ends in end-seed adds no further marker', () => {
     fc.assert(fc.property(logArb, (events) => {
       const original = build(events)
-      const once = Session.create(SessionId(`idem-a-${counter++}`), [...original.snapshotEvents()])
-      const twice = Session.create(SessionId(`idem-b-${counter++}`), [...once.snapshotEvents()])
+      const once = Session.create(SessionId(`idem-a-${counter++}`), original.snapshotEvents())
+      const twice = Session.create(SessionId(`idem-b-${counter++}`), once.snapshotEvents())
       // Lazy resume makes browsing a pickup, so this must not grow per open.
       expect(twice.snapshotEvents()).toEqual(once.snapshotEvents())
     }))

+ 2 - 2
packages/core/session/tests/session.spec.ts

@@ -132,7 +132,7 @@ describe('Session', () => {
     }, { surfaceOp: 'append' })
     original.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
 
-    const replayed = Session.create(SessionId('s3-replay'), [...original.snapshotEvents()])
+    const replayed = Session.create(SessionId('s3-replay'), original.snapshotEvents())
     expect(replayed.deriveMessages()).toEqual(original.deriveMessages())
     // The seed verbatim, plus the end-seed event the constructor appends.
     expect(replayed.snapshotEvents().slice(0, original.seq)).toEqual(original.snapshotEvents())
@@ -1141,7 +1141,7 @@ describe('SessionStore', () => {
     a.append('user/message', createUserMessage({
       content: [{ type: 'text', text: 'q' }], source: { kind: 'user' },
     }), { surfaceOp: 'append' })
-    const forked = ctx.sessions.create(SessionId('fork'), { seed: [...a.snapshotEvents()] })
+    const forked = ctx.sessions.create(SessionId('fork'), { seed: a.snapshotEvents() })
     expect(forked.deriveMessages()).toEqual(a.deriveMessages())
   })
 

+ 1 - 1
packages/core/session/tests/surface.spec.ts

@@ -479,7 +479,7 @@ describe('SurfaceManager', () => {
         isError: false,
       }),
     }, { surfaceOp: 'append' })
-    const replayed = Session.create(SessionId('replay'), [...original.snapshotEvents()])
+    const replayed = Session.create(SessionId('replay'), original.snapshotEvents())
     expect(replayed.surface.nodes).toEqual([1, 2, 4])
     expect(replayed.deriveMessages()).toEqual(original.deriveMessages())
   })

+ 1 - 1
packages/fs/tool-fs/tests/fs-tools.e2e.ts

@@ -43,7 +43,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('fs tools with-key smoke', () =>
     expect(content).not.toContain('draft')
 
     // The log records real read/write/edit tool calls (not bash).
-    const calls = [...agent.session.snapshotEvents()].filter(e => e.type === 'tool/call').map(e => e.data.name)
+    const calls = agent.session.snapshotEvents().filter(e => e.type === 'tool/call').map(e => e.data.name)
     expect(calls).toContain('write')
     expect(calls).toContain('read')
     expect(calls).toContain('edit')

+ 3 - 3
packages/guard/repeat-tool-reminder/tests/repeat-tool-reminder.spec.ts

@@ -40,7 +40,7 @@ function waitForIdle(ctx: Context, agent: Agent): Promise<void> {
 
 /** Every injected-context user message in the agent's log, flattened to joined text + source for terse assertions. */
 function reminders(agent: Agent): { text: string; source: unknown }[] {
-  return [...agent.session.snapshotEvents()]
+  return agent.session.snapshotEvents()
     .filter((e): e is SessionEvent<'user/message'> => e.type === 'user/message' && e.data.source.kind !== 'user')
     .map(e => ({
       text: e.data.content.map(block => block.type === 'text' ? block.text : '').join('|'),
@@ -340,7 +340,7 @@ describe('fold onto the downstream decision', () => {
     expect(found[1]!.source).toEqual(guardSource('probe', 2))
     expect(found[2]).toEqual({ text: 'downstream-ctx', source: { kind: 'plugin', plugin: 'test' } })
     // The block's feedback reached the tool result unchanged.
-    const results = [...agent.session.snapshotEvents()].filter((e): e is SessionEvent<'tool/result'> => e.type === 'tool/result')
+    const results = agent.session.snapshotEvents().filter((e): e is SessionEvent<'tool/result'> => e.type === 'tool/result')
     expect(results.every(r => r.data.message.content[0].isError)).toBe(true)
     expect(results[1]!.data.message.content[0].content).toEqual([{ type: 'text', text: 'nope' }])
   })
@@ -364,7 +364,7 @@ describe('fold onto the downstream decision', () => {
     const found = reminders(agent)
     expect(found).toHaveLength(1)
     expect(found[0]!.text).toContain('repeating the exact same tool call')
-    const results = [...agent.session.snapshotEvents()].filter((e): e is SessionEvent<'tool/result'> => e.type === 'tool/result')
+    const results = agent.session.snapshotEvents().filter((e): e is SessionEvent<'tool/result'> => e.type === 'tool/result')
     expect(results[1]!.data.message.content[0].content).toEqual([{ type: 'text', text: 'replaced' }])
   })
 })

+ 9 - 9
packages/hooks/hook-protocol/tests/events.spec.ts

@@ -12,7 +12,7 @@ describe('hook/* session events', () => {
     const session = Session.create(SessionId('s'))
     appendHookInvoked(session, { turn: 1, point: 'PreToolUse', dialect: 'claude-code', handlerId: 'h1', matcher: 'Bash' })
 
-    const ev = [...session.snapshotEvents()].find(e => e.type === 'hook/invoked')
+    const ev = session.snapshotEvents().find(e => e.type === 'hook/invoked')
     expect(ev?.type).toBe('hook/invoked')
     if (ev?.type === 'hook/invoked') {
       expect(ev.data).toMatchObject({ turn: 1, point: 'PreToolUse', dialect: 'claude-code', handlerId: 'h1', matcher: 'Bash' })
@@ -25,7 +25,7 @@ describe('hook/* session events', () => {
     const session = Session.create(SessionId('s'))
     appendHookInvoked(session, { turn: 2, point: 'Stop', dialect: 'codex', handlerId: 'h2' })
 
-    const ev = [...session.snapshotEvents()].find(e => e.type === 'hook/invoked')
+    const ev = session.snapshotEvents().find(e => e.type === 'hook/invoked')
     if (ev?.type === 'hook/invoked') {
       expect('matcher' in ev.data).toBe(false)
     }
@@ -37,7 +37,7 @@ describe('hook/* session events', () => {
       turn: 1, point: 'PreToolUse', handlerId: 'h1',
       stderrSummaryMaxChars: 500, durationMs: 5, output: output({ exitCode: 2, stderr: 'blocked', decision: 'deny' }),
     })
-    const full = [...session.snapshotEvents()].find(e => e.type === 'hook/result')
+    const full = session.snapshotEvents().find(e => e.type === 'hook/result')
     if (full?.type === 'hook/result') {
       expect(full.data).toEqual({ turn: 1, point: 'PreToolUse', handlerId: 'h1', decision: 'deny', exitCode: 2, stderrSummary: 'blocked', durationMs: 5 })
     }
@@ -48,7 +48,7 @@ describe('hook/* session events', () => {
       turn: 1, point: 'Stop', handlerId: 'h3',
       stderrSummaryMaxChars: 500, durationMs: 5, output: output({ exitCode: undefined, decision: 'allow' }),
     })
-    const sparse = [...session2.snapshotEvents()].find(e => e.type === 'hook/result')
+    const sparse = session2.snapshotEvents().find(e => e.type === 'hook/result')
     if (sparse?.type === 'hook/result') {
       expect('exitCode' in sparse.data).toBe(false)
       expect('stderrSummary' in sparse.data).toBe(false)
@@ -63,7 +63,7 @@ describe('hook/* session events', () => {
     // An explicit decision wins over the continue:false fallback.
     appendHookResult(session, { turn: 1, point: 'Stop', handlerId: 'both', stderrSummaryMaxChars: 500, durationMs: 5, output: output({ continue: false, decision: 'block' }) })
 
-    const decisions = [...session.snapshotEvents()]
+    const decisions = session.snapshotEvents()
       .filter(e => e.type === 'hook/result')
       .map(e => e.type === 'hook/result' ? [e.data.handlerId, e.data.decision] : [])
     expect(decisions).toEqual([['halt', 'stop'], ['noop', 'pass'], ['both', 'block']])
@@ -75,7 +75,7 @@ describe('hook/* session events', () => {
       turn: 1, point: 'PreToolUse', handlerId: 'long',
       stderrSummaryMaxChars: 500, durationMs: 5, output: output({ exitCode: 2, stderr: `  ${'x'.repeat(600)}  ` }),
     })
-    const ev = [...session.snapshotEvents()].find(e => e.type === 'hook/result')
+    const ev = session.snapshotEvents().find(e => e.type === 'hook/result')
     if (ev?.type === 'hook/result') {
       expect(ev.data.stderrSummary).toBe('x'.repeat(500) + '…')
     }
@@ -87,7 +87,7 @@ describe('hook/* session events', () => {
       turn: 1, point: 'PreToolUse', handlerId: 'edge',
       stderrSummaryMaxChars: 500, durationMs: 5, output: output({ exitCode: 2, stderr: 'y'.repeat(500) }),
     })
-    const ev = [...session.snapshotEvents()].find(e => e.type === 'hook/result')
+    const ev = session.snapshotEvents().find(e => e.type === 'hook/result')
     if (ev?.type === 'hook/result') {
       expect(ev.data.stderrSummary).toBe('y'.repeat(500))
     }
@@ -98,8 +98,8 @@ describe('hook/* session events', () => {
     appendHookInvoked(session, { turn: 1, point: 'PreToolUse', dialect: 'claude-code', handlerId: 'pair-1' })
     appendHookResult(session, { turn: 1, point: 'PreToolUse', handlerId: 'pair-1', stderrSummaryMaxChars: 500, durationMs: 5, output: output({ decision: 'allow' }) })
 
-    const invoked = [...session.snapshotEvents()].find(e => e.type === 'hook/invoked')
-    const result = [...session.snapshotEvents()].find(e => e.type === 'hook/result')
+    const invoked = session.snapshotEvents().find(e => e.type === 'hook/invoked')
+    const result = session.snapshotEvents().find(e => e.type === 'hook/result')
     expect(invoked?.type === 'hook/invoked' && invoked.data.handlerId).toBe('pair-1')
     expect(result?.type === 'hook/result' && result.data.handlerId).toBe('pair-1')
   })

+ 2 - 2
packages/hooks/hooks-claude-code/tests/bridge.spec.ts

@@ -72,8 +72,8 @@ function waitForIdle(_ctx: Context, agent: Agent): Promise<void> {
   return agent.whenIdle()
 }
 
-function events(agent: Agent): SessionEvent[] {
-  return [...agent.session.snapshotEvents()]
+function events(agent: Agent): readonly SessionEvent[] {
+  return agent.session.snapshotEvents()
 }
 
 /**

+ 1 - 1
packages/hooks/hooks-claude-code/tests/coverage-cases.ts

@@ -54,7 +54,7 @@ async function harness(configPath: string, adapter: MockAdapter, opts: HarnessOp
 function waitForIdle(_ctx: Context, agent: Agent): Promise<void> {
   return agent.whenIdle()
 }
-function events(agent: Agent): SessionEvent[] { return [...agent.session.snapshotEvents()] }
+function events(agent: Agent): readonly SessionEvent[] { return agent.session.snapshotEvents() }
 /** Poll until `predicate` holds or the deadline passes — robust to detached
  * emit-listener hooks firing on a `.then` (a fixed sleep flakes under load). */
 async function waitFor(predicate: () => boolean, timeout = 5000, interval = 10): Promise<void> {

+ 1 - 1
packages/hooks/hooks-codex/tests/bridge.spec.ts

@@ -56,7 +56,7 @@ async function harness(dir: string, adapter: MockAdapter, beforeHooks?: (ctx: Co
 function waitForIdle(_ctx: Context, agent: Agent): Promise<void> {
   return agent.whenIdle()
 }
-function events(agent: Agent): SessionEvent[] { return [...agent.session.snapshotEvents()] }
+function events(agent: Agent): readonly SessionEvent[] { return agent.session.snapshotEvents() }
 
 /** Poll `predicate` until true or the deadline passes (detached hook effects can't be awaited directly). */
 async function waitFor(predicate: () => boolean, timeout = 5000, interval = 10): Promise<void> {

+ 1 - 1
packages/hooks/hooks-codex/tests/coverage-cases.ts

@@ -44,7 +44,7 @@ async function harness(configPath: string, adapter: MockAdapter, opts: HarnessOp
 function waitForIdle(_ctx: Context, agent: Agent): Promise<void> {
   return agent.whenIdle()
 }
-function events(agent: Agent): SessionEvent[] { return [...agent.session.snapshotEvents()] }
+function events(agent: Agent): readonly SessionEvent[] { return agent.session.snapshotEvents() }
 /** Poll until `predicate` holds or the deadline passes — robust to detached
  * emit-listener hooks firing on a `.then` (a fixed sleep flakes under load). */
 async function waitFor(predicate: () => boolean, timeout = 5000, interval = 10): Promise<void> {

+ 3 - 3
packages/session/session-telemetry/tests/telemetry.spec.ts

@@ -288,7 +288,7 @@ describe('SessionTelemetryCoordinator adoption', () => {
       inject: ['sessions'],
       apply: (inner: Context) => void new SessionTelemetryCoordinator(inner, backend),
     })
-    const child = ctx.sessions.prepare(SessionId('seeded'), { seed: [...parent.snapshotEvents()], meta: {} })
+    const child = ctx.sessions.prepare(SessionId('seeded'), { seed: parent.snapshotEvents(), meta: {} })
     child.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
     ctx.sessions.enter(child)
     ctx.sessions.announce(child)
@@ -307,7 +307,7 @@ describe('SessionTelemetryCoordinator adoption', () => {
     const donor = ctx.sessions.create(SessionId('donor'), { meta: {} })
     donor.append('turn/start', { turn: 1 })
     donor.append('assistant/chunk', { turn: 1, step: 1, chunk: { type: 'text-delta', index: 0, text: 'first' } })
-    const resumed = ctx.sessions.create(SessionId('resumed'), { seed: [...donor.snapshotEvents()], meta: {} })
+    const resumed = ctx.sessions.create(SessionId('resumed'), { seed: donor.snapshotEvents(), meta: {} })
     await ctx.plugin({
       name: 'fake-telemetry',
       inject: ['sessions'],
@@ -335,7 +335,7 @@ describe('SessionTelemetryCoordinator adoption', () => {
     const parent = liveSession(ctx, 'stitch-parent')
     appendTurn(parent)
     const child = ctx.sessions.create(SessionId('stitch-child'), {
-      seed: [...parent.snapshotEvents()],
+      seed: parent.snapshotEvents(),
       meta: { parentSession: SessionId('stitch-parent'), seedLength: 2 },
     })
     await ctx.plugin({

+ 3 - 3
packages/shell/tool-bash/tests/integration.spec.ts

@@ -60,13 +60,13 @@ function waitForIdle(ctx: Context, agent: Agent): Promise<void> {
   })
 }
 
-function events(agent: Agent): SessionEvent[] {
-  return [...agent.session.snapshotEvents()]
+function events(agent: Agent): readonly SessionEvent[] {
+  return agent.session.snapshotEvents()
 }
 
 /** Find a session event by type, narrowed; throws when absent. */
 function findEvent<T extends SessionEvent['type']>(
-  log: SessionEvent[],
+  log: readonly SessionEvent[],
   type: T,
   position: 'first' | 'last' = 'first',
 ): Extract<SessionEvent, { type: T }> {

+ 1 - 1
packages/subagent/subagent-in-process-driver/src/index.ts

@@ -67,7 +67,7 @@ function toStopReason(reason: TurnEndReason | undefined): SubagentStopReason {
 /** Extra inputs the spawn and fork providers supply to the shared driver. */
 export interface InProcessRunOptions {
   /** Completed-turn seed for fork, or undefined for a fresh spawn. */
-  readonly seed?: SessionEvent[]
+  readonly seed?: readonly SessionEvent[]
 }
 
 /** Error used when cancellation wins before the child publication boundary. */

+ 1 - 1
packages/subagent/subagent-in-process-driver/tests/inheritance.spec.ts

@@ -141,7 +141,7 @@ describe('in-process policy inheritance', () => {
     const { ctx, parent } = await setupWalled(script)
     const blocked = join(workspace, 'fork-blocked.txt')
     setSandboxMode(parent.session, 'workspace-write')
-    const seed = [...parent.session.snapshotEvents()]
+    const seed = parent.session.snapshotEvents()
     setSandboxMode(parent.session, 'read-only')
     script.push(
       toolCallResponse('write', 'write', { file_path: blocked, content: 'escaped' }),

+ 1 - 1
packages/subagent/subagent-in-process-driver/tests/subagent-in-process-driver.spec.ts

@@ -201,7 +201,7 @@ describe('startInProcessRun', () => {
     const { ctx, parent } = await setup([textResponse('parent answer'), textResponse('child answer')])
     parent.followup(createUserMessage({ content: [{ type: 'text', text: 'parent question' }], source: { kind: 'user' } }))
     await parent.whenIdle()
-    const seed = parent.session.snapshotEvents().slice()
+    const seed = parent.session.snapshotEvents()
     const run = await startInProcessRun(request(parent), { seed })
     const result = await run.result
     expect(text(result.output)).toBe('child answer')

+ 1 - 1
packages/subagent/subagent-spawn-in-process/tests/spawn-in-process.e2e.ts

@@ -38,7 +38,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('spawn backend with-key smoke', (
 
     // The parent's log records the subagent tool/call + its result (not the
     // child's internal steps).
-    const events = [...parent.session.snapshotEvents()]
+    const events = parent.session.snapshotEvents()
     const subagentCalls = events.filter(e => e.type === 'tool/call' && e.data.name === 'subagent')
     expect(subagentCalls.length).toBeGreaterThan(0)
   }, 180_000)

+ 2 - 2
packages/subagent/subagent/src/descriptor-seed.ts

@@ -24,8 +24,8 @@ export function seedDescriptorTurn(
   childId: SessionId,
   seed: readonly SessionEvent[] | undefined,
   descriptor: SubagentDescriptorData,
-): SessionEvent[] {
+): readonly SessionEvent[] {
   const staged = Session.create(childId, seed)
   staged.append('subagent/descriptor', descriptor)
-  return [...staged.snapshotEvents()]
+  return staged.snapshotEvents()
 }

+ 1 - 1
packages/todo/tool-todo/tests/invariant.spec.ts

@@ -66,7 +66,7 @@ describe('todo snapshot invariants', () => {
     const session = ctx.sessions.create()
     session.append('turn/start', { turn: 1 })
     session.append('turn/end', { turn: 1, reason: { kind: 'completed' } })
-    const before = [...session.snapshotEvents()]
+    const before = session.snapshotEvents()
 
     expect(() => session.append('todo/write', { todos: [] })).toThrow(/outside any open turn/)
     expect(session.snapshotEvents()).toEqual(before)