Sfoglia il codice sorgente

test(session): close canonical envelope CI gaps

Tianyi Cui 1 mese fa
parent
commit
53f42a781b

+ 4 - 4
packages/client/ui-trajectory/tests/conversation-definitions.client.spec.ts

@@ -610,7 +610,7 @@ describe('Trajectory conversation Definitions', () => {
         turn: 2,
         step: 1,
         message: systemMessage('second prompt'),
-      }, { surfaceOp: { op: 'replace', start: 3, end: 3 }, sourceEventSeqs: [3] }),
+      }, { surfaceOp: { op: 'replace', startSeq: 3, endSeq: 3 }, sourceEventSeqs: [3] }),
       at(11, 'request/header', {
         reason: 'series',
         header: { config: { provider: 'test', model: 'test' }, tools: [] },
@@ -790,7 +790,7 @@ describe('Trajectory conversation Definitions', () => {
       at(12, 'user/message', {
         turn: 1, step: 3, id: 'summary', role: 'user',
         content: [{ type: 'text', text: 'summary' }], source: { kind: 'plugin', plugin: 'compaction' },
-      }, { surfaceOp: { op: 'replace', start: 5, end: 9 }, sourceEventSeqs: [5, 8, 9] }),
+      }, { surfaceOp: { op: 'replace', startSeq: 5, endSeq: 9 }, sourceEventSeqs: [5, 8, 9] }),
       at(13, 'request/header', {
         reason: 'series', header: { config: { provider: 'test', model: 'test' }, tools: [] },
       }),
@@ -821,7 +821,7 @@ describe('Trajectory conversation Definitions', () => {
   it('withholds unknown replacement order in request headers until prepend', () => {
     const system = (seq: number, text: string, replaces?: number) => at(seq, 'system/message', {
       turn: 1, step: 1, message: systemMessage(text),
-    }, { surfaceOp: replaces === undefined ? 'append' : { op: 'replace', start: replaces, end: replaces } })
+    }, { surfaceOp: replaces === undefined ? 'append' : { op: 'replace', startSeq: replaces, endSeq: replaces } })
     const value = assembler([
       system(6, 'C', 3), system(7, 'D', 5),
       at(8, 'step/start', { turn: 1, step: 1 }),
@@ -843,7 +843,7 @@ describe('Trajectory conversation Definitions', () => {
       at(7, 'request/header', { reason: 'resume', header: { config: { provider: 'test', model: 'test' } } }),
       at(8, 'user/message', {
         ...systemMessage('summary'), role: 'user', source: { kind: 'plugin', plugin: 'compaction' },
-      }, { surfaceOp: { op: 'replace', start: 2, end: 6 } }),
+      }, { surfaceOp: { op: 'replace', startSeq: 2, endSeq: 6 } }),
       at(9, 'assistant/message', { turn: 1, step: 1, message: assistantMessage('reply', 'reply') }),
     ])
     expect(snapshot(value).requests.at(-1)).toMatchObject({ prompt: { system: '' } })

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

@@ -658,7 +658,7 @@ describe('agent loop', () => {
       expect(adapter.requests[1]?.messages.map(message => message.role)).toEqual(['system', 'user', 'assistant', 'user'])
       const replacement = agent.session.snapshotEvents().findLast(event => event.type === 'system/message')
       expect(replacement).toMatchObject({
-        surfaceOp: { op: 'replace', start: head?.seq, end: head?.seq },
+        surfaceOp: { op: 'replace', startSeq: head?.seq, endSeq: head?.seq },
         sourceEventSeqs: [head?.seq],
       })
       expect(agent.session.surface.nodes[0]).toBe(replacement?.seq)

+ 3 - 1
packages/core/session/tests/canonical-envelopes.spec.ts

@@ -286,7 +286,9 @@ describe('canonical event-local surface metadata', () => {
 
   it.each(['turn/start', 'assistant/attempt', 'request/context', 'session/title', 'tool/ptc-dispatch'])('rejects known log-only %s metadata even when ignorable', (type) => {
     for (const metadata of [{ surfaceOp: 'append' }, { sourceEventSeqs: [0] }]) {
-      const event = { type, seq: SessionSeq(0), time: 1, data: { turn: 1, step: 1, stream: [] }, ignorable: true, ...metadata } as unknown as SessionEvent
+      const event = {
+        type, seq: SessionSeq(0), time: 1, data: { turn: 1, step: 1, stream: [] }, ignorable: true, ...metadata,
+      } as unknown as SessionEvent
       for (const accept of Object.values(entryPaths)) expect(() => accept(event)).toThrow(/not surface-eligible/)
     }
   })

+ 1 - 1
packages/plan/plan-mode/tests/integration.spec.ts

@@ -159,7 +159,7 @@ describe('plan mode through the agent loop', () => {
     const systemNodes = log.filter(event => event.type === 'system/message')
     expect(systemNodes).toHaveLength(2)
     const second = systemNodes[1]
-    expect(second?.surfaceOp).toEqual({ op: 'replace', start: firstSystem.seq, end: firstSystem.seq })
+    expect(second?.surfaceOp).toEqual({ op: 'replace', startSeq: firstSystem.seq, endSeq: firstSystem.seq })
     expect(second?.sourceEventSeqs).toEqual([firstSystem.seq])
     expect(agent.session.surface.nodes[0]).toBe(second?.seq)
     expect(systemText(agent)).toContain('plan mode')

+ 20 - 0
packages/session/session-format-v2-to-v3/tests/canonical-envelopes.spec.ts

@@ -10,6 +10,7 @@ import type {
 import { releasedV0SessionFormatCodec, releasedV1SessionFormatCodec, sessionFormatV0ToV1 } from '@deepseek-ai/dsh-session-format-v0-to-v1'
 import { releasedV2SessionFormatCodec, sessionFormatV1ToV2 } from '@deepseek-ai/dsh-session-format-v1-to-v2'
 import { assertReleasedV3Header, releasedV3SessionFormatCodec, restoreReleasedV3Artifact, sessionFormatV2ToV3 } from '../src/index.ts'
+import { canonicalizeTransformedEvent } from '../src/payload.ts'
 
 const header: SessionFormatHeader = {
   version: 2, id: 'canonical-envelopes', createdAt: 1, isSeeded: false, delegationDepth: 0,
@@ -178,6 +179,21 @@ describe('canonical V2 to V3 envelopes', () => {
     },
   )
 
+  it.each([
+    ['extra field', { op: 'replace', start: 0, end: 0, extra: true }],
+    ['wrong op', { op: 'append', start: 0, end: 0 }],
+    ['missing start', { op: 'replace', startSeq: 0, end: 0 }],
+    ['missing end', { op: 'replace', start: 0, endSeq: 0 }],
+  ] satisfies [string, SessionFormatJsonObject][])(
+    'refuses malformed released markers at transformed-event admission: %s', (_name, surfaceOp) => {
+      const source = user(1, { surfaceOp, sourceEventSeqs: [0] })
+      const before = JSON.stringify(source)
+      expect(() => canonicalizeTransformedEvent(source))
+        .toThrow('format v2 user/message at seq 1 requires exact replace fields op/start/end')
+      expect(JSON.stringify(source)).toBe(before)
+    },
+  )
+
   it.each([
     ['V2 fields', { op: 'replace', start: 0, end: 0 }],
     ['mixed fields', { op: 'replace', start: 0, endSeq: 0 }],
@@ -339,6 +355,10 @@ describe('native V3 physical admission', () => {
   const invalidRows: [string, SessionFormatEvent, RegExp][] = [
     ['missing surface marker', user(1, {}), /requires a surfaceOp/],
     ['old replacement', user(1, { surfaceOp: { op: 'replace', start: 0, end: 0 } }), /exact replace fields/],
+    ['current replacement start', user(1, { surfaceOp: { op: 'replace', startSeq: 1, endSeq: 0 } }), /replacement endpoints must reference earlier events/],
+    ['future replacement start', user(1, { surfaceOp: { op: 'replace', startSeq: 2, endSeq: 0 } }), /replacement endpoints must reference earlier events/],
+    ['current replacement end', user(1, { surfaceOp: { op: 'replace', startSeq: 0, endSeq: 1 } }), /replacement endpoints must reference earlier events/],
+    ['future replacement end', user(1, { surfaceOp: { op: 'replace', startSeq: 0, endSeq: 2 } }), /replacement endpoints must reference earlier events/],
     ['empty header optional', event('request/header', 1, {
       reason: 'initial', header: { config: { provider: 'mock', model: 'mock' }, tools: [] },
     }), /empty optional header fields/],

+ 17 - 8
packages/session/session-log-deepseek/tests/upload.spec.ts

@@ -121,18 +121,27 @@ describe('incremental DeepSeek session-log upload', () => {
   })
 
   it.each(['extension/event', 'tool/code-dispatch', 'tool/code-dispatch-start'])('uploads opaque ignorable %s without interpreting its metadata', async (type) => {
-    const event = { type, seq: SessionSeq(0), time: 1, data: { nested: [null, true] }, ignorable: true,
-      surfaceOp: { opaque: ['retained'] }, sourceEventSeqs: { opaque: [null] },
-    } as unknown as SessionEvent
-    const { ctx, session } = await harness('wire-opaque', [event])
-    const prepared = await ctx.deepseekLlmApiExtensions.prepare({ body: body(), signal: SIGNAL, sessionId: session.id })
-    expect(prepared.fields.dsh_session_log?.events[0]).toEqual(event)
-    expect(session.deriveMessages()).toEqual([])
+    for (const metadata of [
+      {},
+      { surfaceOp: null },
+      { sourceEventSeqs: null },
+      { surfaceOp: { opaque: ['retained'] }, sourceEventSeqs: { opaque: [null] } },
+    ]) {
+      const event = {
+        type, seq: SessionSeq(0), time: 1, data: { nested: [null, true] }, ignorable: true, ...metadata,
+      } as unknown as SessionEvent
+      const { ctx, session } = await harness('wire-opaque', [event])
+      const prepared = await ctx.deepseekLlmApiExtensions.prepare({ body: body(), signal: SIGNAL, sessionId: session.id })
+      expect(prepared.fields.dsh_session_log?.events[0]).toStrictEqual(event)
+      expect(session.deriveMessages()).toEqual([])
+    }
   })
 
   it.each(['turn/start', 'assistant/attempt', 'request/context', 'tool/ptc-dispatch'])('rejects known log-only %s metadata before uploading', async (type) => {
     for (const metadata of [{ surfaceOp: 'append' }, { sourceEventSeqs: [0] }]) {
-      const event = { type, seq: SessionSeq(0), time: 1, data: { turn: 1, step: 1, stream: [] }, ignorable: true, ...metadata } as unknown as SessionEvent
+      const event = {
+        type, seq: SessionSeq(0), time: 1, data: { turn: 1, step: 1, stream: [] }, ignorable: true, ...metadata,
+      } as unknown as SessionEvent
       await expect(harness('wire-invalid', [event])).rejects.toThrow(/not surface-eligible/)
     }
   })

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

@@ -231,7 +231,7 @@ describe('startInProcessRun', () => {
     const child = ctx.agents.get(run.id)!
     const systemNodes = child.session.snapshotEvents().filter(event => event.type === 'system/message')
     expect(systemNodes.map(event => event.seq)).toEqual([seededSystem.seq, systemNodes[1]?.seq])
-    expect(systemNodes[1]?.surfaceOp).toEqual({ op: 'replace', start: seededSystem.seq, end: seededSystem.seq })
+    expect(systemNodes[1]?.surfaceOp).toEqual({ op: 'replace', startSeq: seededSystem.seq, endSeq: seededSystem.seq })
     expect(systemNodes[1]?.sourceEventSeqs).toEqual([seededSystem.seq])
     expect(child.session.surface.nodes[0]).toBe(systemNodes[1]?.seq)
     const childRequest = adapter.requests.at(-1)!

+ 0 - 1
packages/test-support/llm-replay/src/index.ts

@@ -312,7 +312,6 @@ function restoreProjectedRequestHeader(
   target: Readonly<Record<string, unknown>>,
   source: Readonly<Record<string, unknown>>,
 ): Readonly<Record<string, unknown>> {
-  if (target['type'] !== 'request/header' || source['type'] !== 'request/header') return target
   const targetData = target['data'] as Record<string, unknown>
   const sourceData = source['data'] as Record<string, unknown>
   const targetHeader = targetData['header'] as Record<string, unknown>

+ 1 - 1
packages/test-support/session-snapshot/tests/fixtures/suite/pin-turn/behavior.json

@@ -11,7 +11,7 @@
         {"type":"request/header","seq":3,"time":100,"data":{"header":{"config":{"provider":"fake","model":"fake"},"tools":[{"name":"t1","description":"D1","parameters":{"type":"object"}}]},"reason":"initial"}},
         {"type":"step/end","seq":4,"time":100,"data":{"turn":1,"step":1}},
         {"type":"step/start","seq":5,"time":100,"data":{"turn":1,"step":2}},
-        {"type":"system/message","seq":6,"time":100,"data":{"turn":1,"step":2,"message":{"id":"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb","role":"system","content":[{"type":"text","text":"SYS PROMPT\n\nNEW PROMPT LINE"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"}}},"surfaceOp":{"op":"replace","start":2,"end":2},"sourceEventSeqs":[2]},
+        {"type":"system/message","seq":6,"time":100,"data":{"turn":1,"step":2,"message":{"id":"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb","role":"system","content":[{"type":"text","text":"SYS PROMPT\n\nNEW PROMPT LINE"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"}}},"surfaceOp":{"op":"replace","startSeq":2,"endSeq":2},"sourceEventSeqs":[2]},
         {"type":"request/header","seq":7,"time":100,"data":{"header":{"config":{"provider":"fake","model":"fake"},"tools":[{"name":"t1","description":"D1","parameters":{"type":"object"}}]},"reason":"series"}},
         {"type":"request/header","seq":8,"time":100,"data":{"header":{"config":{"provider":"fake","model":"fake"},"tools":[{"name":"t1","description":"D1","parameters":{"type":"object"}},{"name":"t2","description":"D2","parameters":{"type":"object"}}]},"reason":"change"}}
       ]

+ 1 - 1
packages/test-support/session-snapshot/tests/fixtures/suite/pin-turn/session.v3.jsonl

@@ -5,6 +5,6 @@
 {"type":"request/header","data":{"header":{"config":{"provider":"fake","model":"fake"},"tools":"{{tools}}"},"reason":"initial"}}
 {"type":"step/end","data":{"turn":1,"step":1}}
 {"type":"step/start","data":{"turn":1,"step":2}}
-{"type":"system/message","data":{"turn":1,"step":2,"message":{"id":"{{message:2}}","role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"}}},"surfaceOp":{"op":"replace","start":2,"end":2},"sourceEventSeqs":[2]}
+{"type":"system/message","data":{"turn":1,"step":2,"message":{"id":"{{message:2}}","role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"}}},"surfaceOp":{"op":"replace","startSeq":2,"endSeq":2},"sourceEventSeqs":[2]}
 {"type":"request/header","data":{"header":{"config":{"provider":"fake","model":"fake"},"tools":"{{tools}}"},"reason":"series"}}
 {"type":"request/header","data":{"header":{"config":{"provider":"fake","model":"fake"},"tools":"{{tools}}"},"reason":"change"}}

+ 1 - 1
packages/test-support/session-snapshot/tests/fixtures/suite/shared-pin/behavior.json

@@ -9,7 +9,7 @@
         {"type":"step/start","seq":1,"time":100,"data":{"turn":1,"step":1}},
         {"type":"system/message","seq":2,"time":100,"data":{"turn":1,"step":1,"message":{"id":"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa","role":"system","content":[{"type":"text","text":"SYS PROMPT"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"}}},"surfaceOp":"append"},
         {"type":"request/header","seq":3,"time":100,"data":{"header":{"config":{"provider":"fake","model":"fake"},"tools":[{"name":"t1","description":"D1","parameters":{"type":"object"}}]},"reason":"initial"}},
-        {"type":"system/message","seq":4,"time":100,"data":{"turn":1,"step":1,"message":{"id":"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb","role":"system","content":[{"type":"text","text":"SYS PROMPT\n\nNEW PROMPT LINE"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"}}},"surfaceOp":{"op":"replace","start":2,"end":2},"sourceEventSeqs":[2]},
+        {"type":"system/message","seq":4,"time":100,"data":{"turn":1,"step":1,"message":{"id":"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb","role":"system","content":[{"type":"text","text":"SYS PROMPT\n\nNEW PROMPT LINE"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"}}},"surfaceOp":{"op":"replace","startSeq":2,"endSeq":2},"sourceEventSeqs":[2]},
         {"type":"request/header","seq":5,"time":100,"data":{"header":{"config":{"provider":"fake","model":"fake"},"tools":[{"name":"t1","description":"D1","parameters":{"type":"object"}},{"name":"t2","description":"D2","parameters":{"type":"object"}}]},"reason":"change"}}
       ]
     }

+ 1 - 1
packages/test-support/session-snapshot/tests/fixtures/suite/shared-pin/session.v3.jsonl

@@ -3,5 +3,5 @@
 {"type":"step/start","data":{"turn":1,"step":1}}
 {"type":"system/message","data":{"turn":1,"step":1,"message":{"id":"{{message:1}}","role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"}}},"surfaceOp":"append"}
 {"type":"request/header","data":{"header":{"config":{"provider":"fake","model":"fake"},"tools":"{{tools}}"},"reason":"initial"}}
-{"type":"system/message","data":{"turn":1,"step":1,"message":{"id":"{{message:2}}","role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"}}},"surfaceOp":{"op":"replace","start":2,"end":2},"sourceEventSeqs":[2]}
+{"type":"system/message","data":{"turn":1,"step":1,"message":{"id":"{{message:2}}","role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"}}},"surfaceOp":{"op":"replace","startSeq":2,"endSeq":2},"sourceEventSeqs":[2]}
 {"type":"request/header","data":{"header":{"config":{"provider":"fake","model":"fake"},"tools":"{{tools}}"},"reason":"change"}}

+ 1 - 1
packages/test-support/session-snapshot/tests/suite.spec.ts

@@ -224,7 +224,7 @@ describe('defineAcpSnapshotSuite: refresh write-back', () => {
     expect(readFileSync(join(refreshDir, 'pin-turn', 'session.jsonl'), 'utf8'))
       .not.toContain('"version"')
     expect(pinSession.match(/"text":"{{system}}"/g)).toHaveLength(2)
-    expect(pinSession).toContain('"surfaceOp":{"op":"replace","start":2,"end":2}')
+    expect(pinSession).toContain('"surfaceOp":{"op":"replace","startSeq":2,"endSeq":2}')
     expect(pinSession).not.toContain('NEW PROMPT LINE')
   })
 })