Browse Source

fix(test): cover indexed session reads

_Kerman 1 week ago
parent
commit
febafc7ee0

+ 4 - 1
packages/acp/acp/tests/approval.spec.ts

@@ -68,10 +68,13 @@ describe('ACP machine permission policy', () => {
   it('delegates a same-id foreign agent', async () => {
     harness = await makeBridgeHarness()
     const request = await ownedRequest()
+    const events = [{ type: 'turn/start', seq: 0, time: 0, data: { turn: 1 } }]
     const foreign = {
       session: {
         id: request.agent.session.id,
-        snapshotEvents: () => [{ type: 'turn/start', seq: 0, time: 0, data: { turn: 1 } }],
+        seq: events.length,
+        eventAt: (seq: number) => events[seq],
+        snapshotEvents: () => events,
         append: () => ({}),
       },
     } as unknown as Agent

+ 15 - 0
packages/bundle/headless/tests/headless.spec.ts

@@ -310,6 +310,21 @@ describe('headless runner', () => {
     await test.ctx.fiber.dispose()
   })
 
+  it('fails when an event below the captured Session length cannot be read', async () => {
+    const test = await bench({
+      afterPrompt(session, message) {
+        appendTurn(session, 1, message, 'unreachable', true)
+        Object.defineProperty(session, 'eventAt', { value: () => undefined })
+      },
+    })
+    expect(await test.run()).toMatchObject({
+      code: 1,
+      out: '',
+      err: 'dsh: headless summary cannot read seq 0 below captured length 7\n',
+    })
+    await test.ctx.fiber.dispose()
+  })
+
   it('reports a direct Agent creation failure', async () => {
     const ctx = new Context()
     let err = ''

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

@@ -540,7 +540,7 @@ describe('adapter registration, routing, and accepted-input ownership', () => {
     send(agent, 'go')
     await waitForIdle(ctx, agent)
 
-    const events = agent.session.events
+    const events = agent.session.snapshotEvents()
     const claims = events.flatMap(event => event.type === 'agent/inbox/spliced'
       && event.data.target === 'next-step'
       && event.data.outcome !== 'canceled'

+ 8 - 2
packages/fs/tool-fs/tests/tools.spec.ts

@@ -806,12 +806,18 @@ describe('sandbox escalation API (write/edit)', () => {
 
   /** A fake agent whose session records appends (the approval audit trail), mid-turn, carrying the given events for the fold. */
   function escalationAgent(events: Array<{ type: string; data?: Record<string, unknown> }> = []): object {
+    const log = [
+      { type: 'turn/start', data: { turn: 1 }, seq: 0 },
+      ...events.map((event, index) => ({ ...event, seq: index + 1 })),
+    ]
     return {
       id: 'agent-fs-esc',
       session: {
         header: { version: 0, id: 'sess-fs-esc', createdAt: 0, cwd: '/session-project' },
-        snapshotEvents: () => [{ type: 'turn/start', data: { turn: 1 } }, ...events],
-        append: (type: string, data: Record<string, unknown>) => { events.push({ type, data }) },
+        get seq() { return log.length },
+        eventAt: (seq: number) => log[seq],
+        snapshotEvents: () => log,
+        append: (type: string, data: Record<string, unknown>) => { log.push({ type, data, seq: log.length }) },
       },
     }
   }

+ 11 - 0
packages/session/session-log-deepseek/tests/upload.spec.ts

@@ -133,6 +133,17 @@ describe('incremental DeepSeek session-log upload', () => {
     expect(reads).toBe(2)
   })
 
+  it('rejects a missing event below the captured Session length', () => {
+    const session = {
+      id: SessionId('missing-event'),
+      seq: 1,
+      eventAt: () => undefined,
+    } as unknown as Session
+
+    expect(() => SessionLogDeepSeek.acceptedThrough(session))
+      .toThrow('session-log-deepseek: missing event 0 below captured length 1')
+  })
+
   it('omits the field for direct or stale requests and uploads the prior acceptance marker next', async () => {
     const { ctx, session } = await harness('edges')
     await expect(ctx.deepseekLlmApiExtensions.prepare({ body: body(), signal: SIGNAL }))

+ 7 - 4
packages/shell/tool-bash/tests/tools.spec.ts

@@ -205,8 +205,8 @@ function sandboxAgent(
   ctx?: Context,
   onAppend?: (type: string) => void,
 ): Agent {
-  const events: Array<{ type: string; data?: Record<string, unknown> }> = [{ type: 'turn/start', data: { turn: 1 } }]
-  if (mode !== undefined) events.push({ type: 'sandbox/mode', data: { mode } })
+  const events: Array<{ type: string; data?: Record<string, unknown>; seq: number }> = [{ type: 'turn/start', seq: 0, data: { turn: 1 } }]
+  if (mode !== undefined) events.push({ type: 'sandbox/mode', seq: events.length, data: { mode } })
   const id = SessionId('sandbox-session')
   return {
     id,
@@ -214,9 +214,11 @@ function sandboxAgent(
     session: {
       id,
       header: { version: 0, id, createdAt: 0 },
+      get seq() { return events.length },
+      eventAt: (seq: number) => events[seq],
       snapshotEvents: () => events,
       append: (type: string, data: Record<string, unknown>) => {
-        const event = { type, data }
+        const event = { type, data, seq: events.length }
         events.push(event)
         onAppend?.(type)
         return event
@@ -625,9 +627,10 @@ describe('sandbox escalation through the generic task producer', () => {
     expect(prompted).not.toHaveBeenCalled()
 
     const malformed = sandboxAgent()
-    ;(malformed.session.snapshotEvents() as unknown as Array<{ type: string; data: { mode: string } }>).push({
+    ;(malformed.session.snapshotEvents() as unknown as Array<{ type: string; data: { mode: string }; seq: number }>).push({
       type: 'sandbox/mode',
       data: { mode: 'unknown-mode' },
+      seq: malformed.session.seq,
     })
     expect(text(await call(ctx, 'bash', escalate, malformed))).toContain('not strictly wider')
   })

+ 12 - 3
packages/shell/tool-pwsh/tests/tools.spec.ts

@@ -247,9 +247,11 @@ function sandboxAgent(
     session: {
       id,
       header: { version: 0, id, createdAt: 0 },
+      get seq() { return events.length },
+      eventAt: (seq: number) => events[seq],
       snapshotEvents: () => events,
       append: (type: string, data: Record<string, unknown>) => {
-        const event = { type, data }
+        const event = { type, data, seq: events.length }
         events.push(event)
         onAppend?.(type)
         return event
@@ -270,7 +272,13 @@ function registerFakeAgent(ctx: Context, sessionId: string): Agent {
   const agent = {
     id,
     ctx: scopeFiber.ctx,
-    session: { id, header: { version: 0, id, createdAt: 0 }, snapshotEvents: () => [] },
+    session: {
+      id,
+      header: { version: 0, id, createdAt: 0 },
+      seq: 0,
+      eventAt: () => undefined,
+      snapshotEvents: () => [],
+    },
   } as unknown as Agent
   ctx.agents.register(agent)
   return agent
@@ -602,9 +610,10 @@ describe('sandbox escalation through ctx.approval', () => {
     expect(prompted).not.toHaveBeenCalled()
 
     const malformed = sandboxAgent()
-    ;(malformed.session.snapshotEvents() as unknown as Array<{ type: string; data: { mode: string } }>).push({
+    ;(malformed.session.snapshotEvents() as unknown as Array<{ type: string; data: { mode: string }; seq: number }>).push({
       type: 'sandbox/mode',
       data: { mode: 'unknown-mode' },
+      seq: malformed.session.seq,
     })
     expect(text(await call(ctx, 'pwsh', escalate, malformed))).toContain('not strictly wider')
   })

+ 12 - 0
packages/skill/tool-skill/tests/tool-skill.spec.ts

@@ -585,6 +585,18 @@ describe('dsh-tool-skill', () => {
     expect(JSON.stringify(published[0]?.data.content)).toContain('live-skill')
   })
 
+  it('rejects a missing event below the current Session length', async () => {
+    const home = await tempDir('tool-catalog-missing-event')
+    const ctx = await setup(home)
+    const session = Session.create(SessionId('catalog-missing-event'))
+    const agent = sessionAgent(session)
+    openMessageTurn(session)
+    Object.defineProperty(session, 'eventAt', { value: () => undefined })
+
+    await expect(fireStep(ctx, agent, 1, 1))
+      .rejects.toThrow('skill catalog cannot read seq 1 below the current Session length')
+  })
+
   it('re-establishes the current catalog after compaction hides its durable message', async () => {
     const home = await tempDir('tool-catalog-compaction')
     const ctx = await setup(home)