Jelajahi Sumber

Merge commit '8f7250f904' into worktree/session-format-06-v2-snapshot-rollout

# Conflicts:
#	apps/cli/tests/profiles/headless/tests/expected/subagent-settlement/child.expected.jsonl
Tianyi Cui 3 minggu lalu
induk
melakukan
71089f8df7

+ 1 - 1
apps/web/tests/navigation-panes.e2e.ts

@@ -28,7 +28,7 @@ const SEARCH_EXPECTED = join(SNAPSHOT_DIR, 'search-results.expected.md')
 const TERMINAL_EXPECTED = join(SNAPSHOT_DIR, 'terminal-card.expected.md')
 const MODE = webSnapshotMode()
 const SEED_ID = 'navigation-panes-web-e2e'
-const EXPORTED_LOG_FILE = 'session.jsonl'
+const EXPORTED_LOG_FILE = `session.v${SESSION_FORMAT_VERSION}.jsonl`
 
 // Turn 1 leads with a distinctive word: the session-title fallback takes the
 // first words of the first message, so the sidebar-search scenario has a

+ 13 - 7
apps/web/tests/scaffold-generation.spec.ts

@@ -18,12 +18,16 @@ describe('Web snapshot generation filenames', () => {
   it('selects the highest parent and child generations without counting retained inputs twice', async () => {
     const root = await mkdtemp(join(tmpdir(), 'dsh-web-fixture-generations-'))
     roots.push(root)
-    for (const name of [
-      'session.jsonl',
-      'session.v2.jsonl',
-      'session.1.jsonl',
-      'session.1.v1.jsonl',
-    ]) await writeFile(join(root, name), '')
+    for (const [name, version] of [
+      ['session.jsonl', 0],
+      ['session.v2.jsonl', 2],
+      ['session.1.jsonl', 0],
+      ['session.1.v1.jsonl', 1],
+    ] as const) {
+      await writeFile(join(root, name), `${JSON.stringify({
+        type: 'session', version, id: '{{session:1}}', createdAt: 0, delegationDepth: 0,
+      })}\n`)
+    }
 
     await expect(selectedSessionFixture(join(root, 'session.jsonl')))
       .resolves.toBe(join(root, 'session.v2.jsonl'))
@@ -37,8 +41,10 @@ describe('Web snapshot generation filenames', () => {
     const root = await mkdtemp(join(tmpdir(), 'dsh-web-fixture-generations-'))
     roots.push(root)
 
-    await expect(selectedSessionFixture(join(root, 'session.jsonl')))
+    await expect(selectedSessionFixture(join(root, 'session.jsonl'), true))
       .resolves.toBe(join(root, 'session.jsonl'))
+    await expect(selectedSessionFixture(join(root, 'session.jsonl')))
+      .rejects.toThrow('missing parent session fixture')
   })
 
   it('records beside an older generation and preserves the parent or child role', () => {

+ 2 - 2
docs/module-graph.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/module-graph.md
-module-graph.md: 150ae5bd945429c56b83141734871d6394bcd242
-module-graph.zh.md: 39820846bdf530c82a46a93fdca96bb23b4eae83
+module-graph.md: 6a5e4232f5a4da309b48c2e7c00dadf09e8b308a
+module-graph.zh.md: 2388a1d9843e5e2fe308e372f9d7db8150b48160

+ 2 - 1
docs/module-graph.md

@@ -457,6 +457,7 @@ flowchart TD
   pkg_fs --> pkg_llm
   pkg_fs --> pkg_sandbox
   pkg_spill_local --> pkg_spill
+  pkg_session_log_export --> pkg_session
   pkg_session_log_export --> pkg_session_persistence
   pkg_message_feedback --> pkg_brand
   pkg_message_feedback --> pkg_llm
@@ -1279,7 +1280,7 @@ flowchart TD
 | [`agent`](../packages/core/agent) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) |
 | [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
 | [`spill-local`](../packages/spill/spill-local) | `spill` | [`spill`](../packages/spill/spill) |
-| [`session-log-export`](../packages/session-query/session-log-export) | `session-query` | [`session-persistence`](../packages/session/session-persistence) |
+| [`session-log-export`](../packages/session-query/session-log-export) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) |
 | [`message-feedback`](../packages/feedback/message-feedback) | `feedback` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) |
 | [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session) |
 | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | `session` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) |

+ 2 - 1
docs/module-graph.zh.md

@@ -459,6 +459,7 @@ flowchart TD
   pkg_fs --> pkg_llm
   pkg_fs --> pkg_sandbox
   pkg_spill_local --> pkg_spill
+  pkg_session_log_export --> pkg_session
   pkg_session_log_export --> pkg_session_persistence
   pkg_message_feedback --> pkg_brand
   pkg_message_feedback --> pkg_llm
@@ -1281,7 +1282,7 @@ flowchart TD
 | [`agent`](../packages/core/agent) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) |
 | [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
 | [`spill-local`](../packages/spill/spill-local) | `spill` | [`spill`](../packages/spill/spill) |
-| [`session-log-export`](../packages/session-query/session-log-export) | `session-query` | [`session-persistence`](../packages/session/session-persistence) |
+| [`session-log-export`](../packages/session-query/session-log-export) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) |
 | [`message-feedback`](../packages/feedback/message-feedback) | `feedback` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) |
 | [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session) |
 | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | `session` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) |

+ 1 - 1
packages/llm/llm-deepseek/tests/loader-composition.spec.ts

@@ -182,7 +182,7 @@ describe('llm-deepseek real dynamic composition', () => {
       }
     }
     expect(request.dsh_session_log).toMatchObject({
-      version: 1,
+      version: 2,
       session: { id: 'extension-composition-enabled' },
       afterSeq: -1,
       throughSeq: 0,

+ 10 - 0
packages/llm/llm/tests/assistant-stream.spec.ts

@@ -159,6 +159,16 @@ describe('AssistantStreamAccumulator', () => {
         type: 'tool-call-delta', index: 0, id: ToolCallId('call'), name: '', argumentsDelta: '{}',
       },
     }).chunk).toMatchObject({ name: '' })
+    expect(() => accumulator.push({
+      time: 1,
+      chunk: { type: 'tool-call-delta', index: 0, id: 1, argumentsDelta: '{}' } as never,
+    })).toThrow(/id must be a string/)
+    expect(() => accumulator.push({
+      time: 1,
+      chunk: {
+        type: 'tool-call-delta', index: 0, id: ToolCallId('call'), name: 1, argumentsDelta: '{}',
+      } as never,
+    })).toThrow(/name must be a string/)
     expect(() => accumulator.push({
       time: 1,
       chunk: {

+ 12 - 0
packages/session/session-persistence-jsonl/tests/jsonl.spec.ts

@@ -821,6 +821,18 @@ describe('JsonlSessionPersistence: immutable format generations', () => {
       .rejects.toThrow(/released v0 physical header lacks required member "type"/)
   })
 
+  it('surfaces source-read storage faults and aborts unwrapped during migration', async () => {
+    const header = meta('source-read-fault', '/work')
+    const sourcePath = historicalLogPath(root, header.cwd, header.id)
+    await mkdir(dirname(sourcePath), { recursive: true })
+    await writeFile(sourcePath, `${JSON.stringify(releasedV0Header(header))}\n`)
+    statFailure.path = sourcePath
+    statFailure.error = Object.assign(new Error('EACCES: denied'), { code: 'EACCES' })
+    await expect(ctx.sessionPersistence.open(header.id, 'read')).rejects.toMatchObject({ code: 'EACCES' })
+    statFailure.error = new DOMException('source read aborted', 'AbortError')
+    await expect(ctx.sessionPersistence.open(header.id, 'read')).rejects.toMatchObject({ name: 'AbortError' })
+  })
+
   it('selects the highest opposite-encoding generation for its refusal', async () => {
     const header = meta('opposite-generations', '/work')
     const dir = sessionDir(root, header.cwd, header.id)

+ 2 - 0
packages/test-support/session-snapshot/tests/normalize.spec.ts

@@ -451,6 +451,7 @@ describe('normalizeSessionLog', () => {
           'opaque',
           { type: 'chunk', time: 8, chunk: { type: 'finish', reason: { kind: 'stop' } } },
           { type: 'usage', time: 7, time0: 6, dt: [5, 4], usage: { inputTokens: 1, outputTokens: 2 } },
+          { type: 'chunk', time: 8, chunk: { type: 'finish', reason: { kind: 'stop' } } },
         ],
       },
     })
@@ -468,6 +469,7 @@ describe('normalizeSessionLog', () => {
           'opaque',
           { time: 0 },
           { time: 0, time0: 0, dt: [0, 0] },
+          { type: 'chunk', time: 0, chunk: { type: 'finish', reason: { kind: 'stop' } } },
         ],
       },
     })

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

@@ -532,6 +532,8 @@ describe('Session generation filename helpers', () => {
     expect(assertPersistedSessionVersion('session.v1.jsonl', v1)).toBe(1)
     expect(() => assertSessionFixtureVersion('notes.jsonl', v0)).toThrow('not a session fixture name')
     expect(assertSessionFixtureVersion('session.jsonl', '{"type":"session"}\n')).toBe(0)
+    expect(() => assertSessionFixtureVersion('session.v1.jsonl', '{"type":"session"}\n'))
+      .toThrow('a versionless projected Session header is format v0')
     expect(() => assertSessionFixtureVersion('session.jsonl', '')).toThrow('session fixture is empty')
     expect(() => assertSessionFixtureVersion('session.jsonl', '{')).toThrow('session header contains invalid JSON')
     expect(() => assertPersistedSessionVersion('session.1.jsonl', v0)).toThrow('not a canonical Session persistence filename')