@xneog/dsh-session-format-v1-to-v2 0.1.3-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
+ # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
+ # after editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/session/session-format-v1-to-v2/README.md
5
+ README.md: a4600d2d83356a99e33119adcb70d1c6065c5b15
6
+ README.zh.md: d03054435e30d281c328df68035fb122d365f2a0
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ ---
2
+ description: "Frozen released-v1 Session reader and cardinality-changing migration that embeds Assistant streams in released v2 events."
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @xneog/dsh-session-format-v1-to-v2
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ `dsh-session-format-v1-to-v2` converts a complete released-v1 Session into the released-v2 event model. It consumes top-level `assistant/chunk` events, embeds their exact timed stream in the matching `assistant/message`, and records an `assistant/attempt` when a failed, retried, cancelled, or stream-error attempt reached settlement without a surface message. The edge densely remaps surviving events and every declared same-Session sequence reference, while the v2 codec stores one event per row and derives the inherited cut from a tagged `session/end-seed` marker.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ ### When to use it
29
+
30
+ Persistence obtains this edge through `dsh-session-format-catalog`; feature compositions do not mount it. Import it directly only when assembling or testing the static released-format catalog or inspecting the exact v1-to-v2 transformation. No runtime invariant companion is published because every codec and migration call validates its complete source or target artifact and retains no runtime state.
31
+
32
+ ### Entry point
33
+
34
+ ```text
35
+ const decodedV1 = releasedV1SessionFormatCodec.decodeArtifact(header, rows)
36
+ const migratedV2 = sessionFormatV1ToV2.migrate(decodedV1)
37
+ ```
38
+
39
+ `releasedV1SessionFormatCodec` reads the frozen v1 physical language. `sessionFormatV1ToV2` validates that complete source, performs the cardinality-changing transformation, remaps declared references, and validates the exact v2 result. `releasedV2SessionFormatCodec` then encodes or decodes the current physical representation.
40
+
41
+ A successful v1 `assistant/message` must cite its complete ordered attempt. The migration removes the cited top-level chunks and obsolete message provenance, compacts the chunks without joining token boundaries, and stores the stream on that message. An unclaimed attempt becomes one log-only `assistant/attempt` at its final chunk position. Unrelated interleaved events keep their relative order.
42
+
43
+ The migration refuses a reference to a consumed chunk instead of redirecting it to a different semantic event. It remaps declared event provenance, surface replacements, command source events, compaction ranges and lists, and title message lists. The already model-visible `session/title-llm-request.messages` text remains byte-identical after source validation, so target validation does not reinterpret the old sequence numbers embedded in that prompt. A seeded source also refuses an inherited cut that splits an Assistant attempt; the target marks the exact cut with `session/end-seed { inherited: true }`.
44
+
45
+ The v2 physical header requires `isSeeded` and does not store a numeric cut. The codec derives the cut from the last inherited end-seed marker, writes one event per row, range-encodes only `sourceEventSeqs`, and remains neutral to ordinary event vocabulary and payload growth. Strict migration-target validation freezes the released-v2 inventory and rejects unknown types or members. Current restoration instead admits event types known to the installed Session package plus unknown events carrying `ignorable: true`, then delegates payload and stream semantics to the installed current restorer. All paths retain strict header, event-envelope, sequence, and inherited-cut validation.
46
+
47
+ -----
48
+
49
+ <a id="understand-the-implementation"></a>
50
+ ## Understand the implementation
51
+
52
+ <details>
53
+ <summary>Implementation internals — click to expand</summary>
54
+
55
+ The edge first groups v1 chunks by turn, step, terminal finish, and explicit message provenance. It stages survivors in source order, substitutes one settlement for each group, computes a dense old-to-new sequence map, and rewrites only the reference fields declared by the frozen event inventory. Source and target validators bracket the transformation so a partially understood artifact is never admitted.
56
+
57
+ | File | Role |
58
+ |---|---|
59
+ | [`src/migration.ts`](src/migration.ts) | Attempt grouping, settlement substitution, dense sequence mapping, and reference rewriting |
60
+ | [`src/codec.ts`](src/codec.ts) | Released-v2 header, one-event-per-row encoding, provenance ranges, and recoverable prefix decoding |
61
+ | [`src/validation.ts`](src/validation.ts) | Physical v2 envelope/cut validation, exact migration-target policy, and vocabulary-neutral current restoration |
62
+ | [`src/dispositions.ts`](src/dispositions.ts) | Frozen released-v2 event and payload-member inventory |
63
+
64
+ </details>
65
+
66
+ -----
67
+
68
+ <a id="further-exploration"></a>
69
+ ## Further Exploration
70
+
71
+ - [Released v0 to v1 edge](../session-format-v0-to-v1/README.md) — the source codec and frozen historical vocabulary reused here.
72
+ - [Static catalog](../session-format-catalog/README.md) — build-owned codec and migration ordering.
73
+ - [Session persistence subsystem](../../../docs/subsystems/persistence.md) — immutable generation selection and publication.
74
+ - [Embedded Assistant stream decision](../../../.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.md) — rationale, alternatives, and consequences.
75
+
76
+ -----
77
+
78
+ <a id="model-experience"></a>
79
+ ## Model Experience
80
+
81
+ ### Historical restoration
82
+
83
+ #### What the model sees
84
+
85
+ Successful Assistant messages retain the content, provider, model, usage, and replay state assembled from the same v1 stream. Failed or abandoned attempts remain durable diagnostics through `assistant/attempt` but do not enter `deriveMessages()`.
86
+
87
+ #### Token effect
88
+
89
+ The migration adds no model-visible content. It preserves the derived message history and removes only top-level chunk envelopes from the current logical event sequence.
90
+
91
+ #### KV Cache effect
92
+
93
+ The restored model-message sequence stays unchanged, so the migration alone does not alter request-prefix cache identity.
94
+
95
+ ## Known Limitations and Deferred Work
96
+
97
+ <a id="known-limitations-and-deferred-work"></a>
98
+
99
+ - **Closed first-party source inventory** — an unknown v1 event refuses migration, including an event marked `ignorable: true`.
100
+ - **Whole-artifact transformation** — the edge materializes the source, target, and sequence map in memory; it does not stream the rewrite.
101
+ - **No publication or compatibility fallback** — persistence owns exclusive successor publication, and retained v1 generations are not automatic downgrade or restore inputs.
102
+
103
+ <a id="dev-note"></a>
104
+ ### Dev Note
105
+
106
+ <details>
107
+ <summary>Working context for maintainers — click to expand</summary>
108
+
109
+ None.
110
+
111
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,111 @@
1
+ ---
2
+ description: "冻结的已发布 v1 Session 读取器,以及把 Assistant 流嵌入已发布 v2 事件的基数变化迁移。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @xneog/dsh-session-format-v1-to-v2
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ `dsh-session-format-v1-to-v2` 把完整的已发布 v1 Session 转换为已发布 v2 事件模型。它会消费顶层 `assistant/chunk` 事件,把精确的带时间流嵌入匹配的 `assistant/message`,并在失败、重试、取消或 stream error attempt 已到达 settlement、但没有产生 surface message 时记录 `assistant/attempt`。该迁移边会密集重映射存活事件和每个已声明的同 Session 序号引用;v2 编解码器则让每行只存一个事件,并从带标记的 `session/end-seed` 事件推导继承切点。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ ### 何时使用
29
+
30
+ 持久化通过 `dsh-session-format-catalog` 获取该迁移边;功能组合不会挂载它。只有在装配或测试静态已发布格式目录,或检查精确的 v1 到 v2 转换时,才直接导入本包。它不发布运行时不变式伴生入口,因为每次 codec 与迁移调用都会校验完整的源或目标 artifact,且不保留运行时状态。
31
+
32
+ ### 入口
33
+
34
+ ```text
35
+ const decodedV1 = releasedV1SessionFormatCodec.decodeArtifact(header, rows)
36
+ const migratedV2 = sessionFormatV1ToV2.migrate(decodedV1)
37
+ ```
38
+
39
+ `releasedV1SessionFormatCodec` 读取冻结的 v1 物理语言。`sessionFormatV1ToV2` 校验完整源产物、执行基数变化转换、重映射已声明引用,并校验精确的 v2 结果。`releasedV2SessionFormatCodec` 随后编码或解码当前物理表示。
40
+
41
+ 成功的 v1 `assistant/message` 必须引用其完整有序 attempt。迁移会移除这些顶层 chunk 和已停用的 message provenance,在不合并 token 边界的前提下压缩 chunk,并把 stream 存到该 message 上。未被 message 认领的 attempt 会在其最后一个 chunk 的位置变成一个仅日志可见的 `assistant/attempt`。无关的交错事件保持相对顺序。
42
+
43
+ 如果引用指向被消费的 chunk,迁移会失败,而不会把它重定向到语义不同的事件。它会重映射已声明的事件 provenance、surface replacement、command source event、compaction range 与 list,以及 title message list。已经对模型可见的 `session/title-llm-request.messages` 文本会在源校验后保持逐字节不变,因此目标校验不会重新解释该 prompt 中嵌入的旧序号。带 seed 的源若让继承切点切开一个 Assistant attempt,也会迁移失败;目标会用 `session/end-seed { inherited: true }` 标出精确切点。
44
+
45
+ v2 物理 header 要求 `isSeeded`,且不存储数值切点。编解码器从最后一个 inherited end-seed marker 推导切点,每行写入一个事件,只对 `sourceEventSeqs` 做范围编码,并对普通事件词汇与 payload 扩展保持中立。严格的迁移目标校验会冻结 released-v2 清单并拒绝未知 type 或 member。当前恢复则准入 installed Session package 已知的事件 type,以及携带 `ignorable: true` 的未知事件,再把 payload 与 stream 语义交给 installed current restorer。所有路径仍严格校验 header、event envelope、sequence 与 inherited cut。
46
+
47
+ -----
48
+
49
+ <a id="understand-the-implementation"></a>
50
+ ## 理解实现
51
+
52
+ <details>
53
+ <summary>实现细节——点击展开</summary>
54
+
55
+ 该迁移边先按 turn、step、terminal finish 和显式 message provenance 对 v1 chunk 分组。它按源顺序暂存存活事件,为每组替换一个 settlement,计算密集的旧序号到新序号映射,并且只改写冻结事件清单声明的引用字段。源与目标校验器包围整个转换,因此部分理解的产物绝不会被接纳。
56
+
57
+ | 文件 | 职责 |
58
+ |---|---|
59
+ | [`src/migration.ts`](src/migration.ts) | Attempt 分组、settlement 替换、密集序号映射与引用重写 |
60
+ | [`src/codec.ts`](src/codec.ts) | 已发布 v2 header、每行一个事件的编码、provenance 范围与可恢复前缀解码 |
61
+ | [`src/validation.ts`](src/validation.ts) | v2 物理 envelope/cut 校验、精确 migration-target 策略与 vocabulary-neutral current restoration |
62
+ | [`src/dispositions.ts`](src/dispositions.ts) | 冻结的已发布 v2 事件与 payload 成员清单 |
63
+
64
+ </details>
65
+
66
+ -----
67
+
68
+ <a id="further-exploration"></a>
69
+ ## 进一步探索
70
+
71
+ - [已发布 v0 到 v1 迁移边](../session-format-v0-to-v1/README.zh.md)——本包复用的源编解码器与冻结历史词表。
72
+ - [静态目录](../session-format-catalog/README.zh.md)——构建拥有的编解码器与迁移顺序。
73
+ - [Session 持久化子系统](../../../docs/subsystems/persistence.zh.md)——不可变 generation 选择与发布。
74
+ - [嵌入式 Assistant stream 决策](../../../.agents/notes/implemented/architecture/2026-09-01-v2-embedded-assistant-streams.zh.md)——理由、替代方案与后果。
75
+
76
+ -----
77
+
78
+ <a id="model-experience"></a>
79
+ ## 模型体验
80
+
81
+ ### 历史还原
82
+
83
+ #### 模型看到什么
84
+
85
+ 成功的 Assistant message 会保留从同一 v1 stream 组装出的 content、provider、model、usage 与 replay state。失败或放弃的 attempt 会通过 `assistant/attempt` 保留为持久诊断事实,但不会进入 `deriveMessages()`。
86
+
87
+ #### Token 影响
88
+
89
+ 迁移不会添加模型可见内容。它会保留派生 message history,只从当前逻辑事件序列中移除顶层 chunk 信封。
90
+
91
+ #### KV Cache 影响
92
+
93
+ 还原后的模型 message 序列保持不变,因此迁移本身不会改变请求前缀的缓存身份。
94
+
95
+ ## 已知限制与延期工作
96
+
97
+ <a id="known-limitations-and-deferred-work"></a>
98
+
99
+ - **封闭的第一方源清单**——未知 v1 事件会使迁移失败,包括带有 `ignorable: true` 的事件。
100
+ - **全产物转换**——该迁移边会在内存中物化源、目标和序号映射;它不会流式改写。
101
+ - **不负责发布或兼容回退**——持久化拥有排他 successor 发布,保留的 v1 generation 不是自动 downgrade 或 restore 输入。
102
+
103
+ <a id="dev-note"></a>
104
+ ### 开发备注
105
+
106
+ <details>
107
+ <summary>维护者的工作上下文——点击展开</summary>
108
+
109
+ 无。
110
+
111
+ </details>
package/lib/index.js ADDED
@@ -0,0 +1,656 @@
1
+ import { RELEASED_V0_EVENT_DISPOSITIONS, assertReleasedArtifactRelationships, assertReleasedPayloadSemantics, assertReleasedSurfaceMetadata, assertReleasedV1Artifact, assertReleasedV1Header, defineReleasedPayloadDisposition, releasedV1SessionFormatCodec } from "@xneog/dsh-session-format-v0-to-v1";
2
+ import { SessionFormatError, SessionFormatUnsupportedMigrationError, defineSessionFormatMigration, sessionFormatCount, sessionFormatSafeInteger, snapshotSessionFormatArtifact, snapshotSessionFormatJson } from "@xneog/dsh-session-format";
3
+ import { isAbsolute } from "node:path";
4
+ import { AssistantStreamAccumulator, BlockAssembler, expandAssistantStream } from "@xneog/dsh-llm";
5
+ import { deepEqualJson } from "@xneog/dsh-util-values";
6
+ //#region lib/types/dispositions.js
7
+ const retained = Object.fromEntries(Object.entries(RELEASED_V0_EVENT_DISPOSITIONS).filter(([type]) => type !== "assistant/chunk" && type !== "assistant/message" && type !== "session-log-deepseek/delivery-accepted" && type !== "session/end-seed"));
8
+ /** Exact top-level event and payload-member inventory frozen for released v2. */
9
+ const RELEASED_V2_EVENT_DISPOSITIONS = Object.freeze({
10
+ ...retained,
11
+ "assistant/attempt": defineReleasedPayloadDisposition([
12
+ "turn",
13
+ "step",
14
+ "stream"
15
+ ]),
16
+ "assistant/message": defineReleasedPayloadDisposition([
17
+ "turn",
18
+ "step",
19
+ "message",
20
+ "stream"
21
+ ], ["usage", "interrupted"]),
22
+ "session-log-deepseek/delivery-accepted": defineReleasedPayloadDisposition(["sessionId", "throughSeq"], ["sessionFormatVersion"]),
23
+ "session/end-seed": defineReleasedPayloadDisposition([], ["inherited"])
24
+ });
25
+ /** Stable sorted released-v2 event inventory. */
26
+ const RELEASED_V2_EVENT_TYPES = Object.freeze(Object.keys(RELEASED_V2_EVENT_DISPOSITIONS).sort((left, right) => left.localeCompare(right, "en")));
27
+ //#endregion
28
+ //#region lib/types/validation.js
29
+ const HEADER_REQUIRED$1 = [
30
+ "version",
31
+ "id",
32
+ "createdAt",
33
+ "isSeeded",
34
+ "delegationDepth"
35
+ ];
36
+ const HEADER_OPTIONAL$1 = [
37
+ "cwd",
38
+ "parentSession",
39
+ "origin",
40
+ "agentPreset"
41
+ ];
42
+ const EVENT_REQUIRED = [
43
+ "type",
44
+ "seq",
45
+ "time",
46
+ "data"
47
+ ];
48
+ const SURFACE_TYPES = new Set([
49
+ "user/message",
50
+ "assistant/message",
51
+ "tool/result"
52
+ ]);
53
+ const SURFACE_OPTIONAL = [
54
+ "ignorable",
55
+ "sourceEventSeqs",
56
+ "surfaceOp"
57
+ ];
58
+ const LOG_OPTIONAL = ["ignorable"];
59
+ const RELEASED_V2_EVENT_TYPE_SET = new Set(RELEASED_V2_EVENT_TYPES);
60
+ const RELEASED_V2_RELATIONSHIP_EXTENSIONS = {
61
+ stepEvents: new Set(["assistant/attempt"]),
62
+ preservedSourceTitleRequestText: true
63
+ };
64
+ /**
65
+ * Validate the exact logical header written by released v2.
66
+ * @param header - decoded released-v2 Session header.
67
+ * @throws {SessionFormatError} when the header is not an exact released-v2 value.
68
+ */
69
+ function assertReleasedV2Header(header) {
70
+ const record = jsonRecord$1(header, "format v2 header");
71
+ exactKeys$1(record, HEADER_REQUIRED$1, HEADER_OPTIONAL$1, "format v2 header");
72
+ if (record["version"] !== 2) throw new SessionFormatError("expected format v2 header");
73
+ if (typeof record["id"] !== "string") throw new SessionFormatError("format v2 header id must be a string");
74
+ sessionFormatCount(record["createdAt"], "format v2 header createdAt");
75
+ sessionFormatCount(record["delegationDepth"], "format v2 header delegationDepth");
76
+ if (typeof record["isSeeded"] !== "boolean") throw new SessionFormatError("format v2 header isSeeded must be boolean");
77
+ if (record["cwd"] !== void 0 && (typeof record["cwd"] !== "string" || !isAbsolute(record["cwd"]))) throw new SessionFormatError("format v2 header cwd must be absolute");
78
+ for (const key of ["parentSession", "agentPreset"]) if (record[key] !== void 0 && typeof record[key] !== "string") throw new SessionFormatError(`format v2 header ${key} must be a string`);
79
+ if (record["origin"] !== void 0 && record["origin"] !== "subagent") throw new SessionFormatError("format v2 header origin must be \"subagent\"");
80
+ }
81
+ /**
82
+ * Validate the exact logical image emitted by the released v2 writer.
83
+ * @param artifact - complete decoded released-v2 Session artifact.
84
+ * @throws {SessionFormatError} when an envelope, payload, relationship, or inherited cut is invalid.
85
+ * @throws {SessionFormatUnsupportedMigrationError} when the artifact contains an unknown event type.
86
+ */
87
+ function assertReleasedV2Artifact(artifact) {
88
+ validateReleasedV2Artifact(artifact, "target", RELEASED_V2_EVENT_TYPE_SET);
89
+ }
90
+ /**
91
+ * Validate only the released-v2 physical header, event envelopes, and inherited cut.
92
+ * Event vocabulary and payload semantics belong to target or installed-current restoration.
93
+ * @param artifact - complete physical-codec output.
94
+ */
95
+ function assertReleasedV2PhysicalArtifact(artifact) {
96
+ validateReleasedV2Artifact(artifact, "physical");
97
+ }
98
+ function validateReleasedV2Artifact(artifact, mode, knownEventTypes) {
99
+ assertReleasedV2Header(artifact.header);
100
+ const cut = sessionFormatCount(artifact.inheritedEventCount, "format v2 inherited event count");
101
+ if (cut > artifact.events.length) throw new SessionFormatError("format v2 inherited event count exceeds its events");
102
+ if (!artifact.header.isSeeded && cut !== 0) throw new SessionFormatError("unseeded format v2 Session has inherited events");
103
+ let lastInheritedMarker;
104
+ for (const [index, event] of artifact.events.entries()) {
105
+ const record = jsonRecord$1(event, `format v2 event ${index}`);
106
+ const type = record["type"];
107
+ if (typeof type !== "string") throw new SessionFormatError(`format v2 event ${index} type must be a string`);
108
+ const disposition = RELEASED_V2_EVENT_DISPOSITIONS[type];
109
+ const installed = knownEventTypes?.has(type) === true;
110
+ const ignorableUnknown = disposition === void 0 && mode === "current" && record["ignorable"] === true;
111
+ if (mode !== "physical" && disposition === void 0 && !installed && !ignorableUnknown) throw new SessionFormatUnsupportedMigrationError(`format v2 contains unknown event type ${JSON.stringify(type)} at seq ${index}`);
112
+ const surface = disposition !== void 0 && SURFACE_TYPES.has(type);
113
+ exactKeys$1(record, EVENT_REQUIRED, mode === "physical" || disposition === void 0 ? SURFACE_OPTIONAL : surface ? SURFACE_OPTIONAL : LOG_OPTIONAL, `format v2 event ${index}`);
114
+ if (record["seq"] !== index) throw new SessionFormatError(`format v2 event ${index} is not dense`);
115
+ sessionFormatSafeInteger(record["time"], `format v2 event ${index} time`);
116
+ if (record["ignorable"] !== void 0 && record["ignorable"] !== true) throw new SessionFormatError(`format v2 event ${index} ignorable must be true when present`);
117
+ if (mode === "target" && surface) assertReleasedSurfaceMetadata(record, index, type, "forbid-assistant");
118
+ if (mode === "target" && disposition !== void 0) assertPayload(event, disposition);
119
+ if (type === "session/end-seed") {
120
+ if (jsonRecord$1(event.data, `session/end-seed ${index} data`)["inherited"] === true) lastInheritedMarker = index;
121
+ }
122
+ }
123
+ if (artifact.header.isSeeded && lastInheritedMarker !== cut) throw new SessionFormatError("format v2 seeded header disagrees with its last inherited end-seed marker");
124
+ if (!artifact.header.isSeeded && lastInheritedMarker !== void 0) throw new SessionFormatError("format v2 unseeded Session contains an inherited end-seed marker");
125
+ if (mode === "target") assertReleasedArtifactRelationships(artifact, RELEASED_V2_RELATIONSHIP_EXTENSIONS);
126
+ }
127
+ function assertPayload(event, disposition) {
128
+ const data = jsonRecord$1(event.data, `${event.type} ${event.seq} data`);
129
+ exactKeys$1(data, disposition.required, disposition.optional, `${event.type} ${event.seq} data`);
130
+ for (const key of disposition.opaque) if (Object.hasOwn(data, key)) snapshotSessionFormatJson(data[key], `${event.type} ${event.seq} opaque ${key}`);
131
+ if (event.type === "assistant/attempt" || event.type === "assistant/message") {
132
+ const turn = sessionFormatCount(data["turn"], `${event.type} ${event.seq} turn`);
133
+ const step = sessionFormatCount(data["step"], `${event.type} ${event.seq} step`);
134
+ const assembler = new BlockAssembler();
135
+ let timed;
136
+ try {
137
+ timed = expandAssistantStream(data["stream"]);
138
+ for (const member of timed) {
139
+ assertReleasedPayloadSemantics({
140
+ type: "assistant/chunk",
141
+ seq: event.seq,
142
+ time: member.time,
143
+ data: {
144
+ turn,
145
+ step,
146
+ chunk: member.chunk
147
+ }
148
+ }, 2);
149
+ assembler.push(member.chunk);
150
+ }
151
+ } catch (error) {
152
+ throw new SessionFormatError(`${event.type} ${event.seq} has an invalid embedded stream`, { cause: error });
153
+ }
154
+ if (event.type === "assistant/attempt") return;
155
+ assertReleasedPayloadSemantics(event, 2);
156
+ if (timed.length > 0) {
157
+ const message = jsonRecord$1(data["message"], `assistant/message ${event.seq} message`);
158
+ const content = data["interrupted"] === true ? assembler.interruptedBlocks() : assembler.blocks();
159
+ if (!deepEqualJson(message["content"], content)) throw new SessionFormatError(`assistant/message ${event.seq} message content disagrees with its embedded stream`);
160
+ if (!deepEqualJson(data["usage"], assembler.usage)) throw new SessionFormatError(`assistant/message ${event.seq} usage disagrees with its embedded stream`);
161
+ if (!deepEqualJson(jsonRecord$1(message["source"], `assistant/message ${event.seq} source`)["replayState"], assembler.replayState)) throw new SessionFormatError(`assistant/message ${event.seq} replay state disagrees with its embedded stream`);
162
+ }
163
+ return;
164
+ }
165
+ if (event.type === "session/end-seed") {
166
+ if (data["inherited"] !== void 0 && data["inherited"] !== true) throw new SessionFormatError(`session/end-seed ${event.seq} inherited must be true when present`);
167
+ return;
168
+ }
169
+ assertReleasedPayloadSemantics(event, 2);
170
+ }
171
+ function jsonRecord$1(value, label) {
172
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new SessionFormatError(`${label} must be an object`);
173
+ return value;
174
+ }
175
+ function exactKeys$1(value, required, optional, label) {
176
+ const allowed = new Set([...required, ...optional]);
177
+ const missing = required.find((key) => !Object.hasOwn(value, key));
178
+ if (missing !== void 0) throw new SessionFormatError(`${label} lacks required field ${missing}`);
179
+ const unexpected = Object.keys(value).find((key) => !allowed.has(key));
180
+ if (unexpected !== void 0) throw new SessionFormatError(`${label} has unexpected field ${unexpected}`);
181
+ }
182
+ /**
183
+ * Restore and validate one decoded released-v2 artifact.
184
+ * @param artifact - detached vocabulary-restored artifact.
185
+ * @param knownEventTypes - event types understood by the installed current Session package.
186
+ * @returns the same validated artifact.
187
+ */
188
+ function restoreReleasedV2Artifact(artifact, knownEventTypes) {
189
+ validateReleasedV2Artifact(artifact, "current", knownEventTypes);
190
+ return artifact;
191
+ }
192
+ //#endregion
193
+ //#region lib/types/codec.js
194
+ const HEADER_REQUIRED = [
195
+ "type",
196
+ "version",
197
+ "id",
198
+ "createdAt",
199
+ "isSeeded",
200
+ "delegationDepth"
201
+ ];
202
+ const HEADER_OPTIONAL = [
203
+ "cwd",
204
+ "parentSession",
205
+ "origin",
206
+ "agentPreset"
207
+ ];
208
+ /** Frozen physical JSON codec for released v2. */
209
+ const releasedV2SessionFormatCodec = Object.freeze({
210
+ version: 2,
211
+ decodeHeader(value) {
212
+ return decodePhysicalHeader(value);
213
+ },
214
+ decodeArtifact(headerValue, rowValues) {
215
+ return decodeArtifact(headerValue, rowValues, false);
216
+ },
217
+ decodeRecoverableArtifact(headerValue, rowValues) {
218
+ return decodeArtifact(headerValue, rowValues, true);
219
+ },
220
+ encodeArtifact(artifact) {
221
+ return encodeArtifact(artifact);
222
+ }
223
+ });
224
+ function decodePhysicalHeader(value) {
225
+ const record = jsonRecord(snapshotSessionFormatJson(value, "released v2 physical header"), "released v2 physical header");
226
+ exactKeys(record, HEADER_REQUIRED, HEADER_OPTIONAL, "released v2 physical header");
227
+ if (record["type"] !== "session" || record["version"] !== 2) throw new SessionFormatError("expected released v2 physical Session header");
228
+ if (typeof record["id"] !== "string") throw new SessionFormatError("released v2 header id must be a string");
229
+ const createdAt = sessionFormatCount(record["createdAt"], "released v2 header createdAt");
230
+ const delegationDepth = sessionFormatCount(record["delegationDepth"], "released v2 header delegationDepth");
231
+ if (typeof record["isSeeded"] !== "boolean") throw new SessionFormatError("released v2 header isSeeded must be boolean");
232
+ for (const key of [
233
+ "cwd",
234
+ "parentSession",
235
+ "agentPreset"
236
+ ]) if (record[key] !== void 0 && typeof record[key] !== "string") throw new SessionFormatError(`released v2 header ${key} must be a string`);
237
+ if (record["origin"] !== void 0 && record["origin"] !== "subagent") throw new SessionFormatError("released v2 header origin must be \"subagent\"");
238
+ const header = snapshotSessionFormatJson({
239
+ version: 2,
240
+ id: record["id"],
241
+ createdAt,
242
+ ...record["cwd"] === void 0 ? {} : { cwd: record["cwd"] },
243
+ ...record["parentSession"] === void 0 ? {} : { parentSession: record["parentSession"] },
244
+ isSeeded: record["isSeeded"],
245
+ ...record["origin"] === void 0 ? {} : { origin: record["origin"] },
246
+ delegationDepth,
247
+ ...record["agentPreset"] === void 0 ? {} : { agentPreset: record["agentPreset"] }
248
+ }, "released v2 logical header");
249
+ assertReleasedV2Header(header);
250
+ return header;
251
+ }
252
+ function decodeArtifact(headerValue, rowValues, recoverable) {
253
+ const header = decodePhysicalHeader(headerValue);
254
+ const events = [];
255
+ let issue;
256
+ for (const [rowIndex, value] of rowValues.entries()) {
257
+ let event;
258
+ try {
259
+ event = decodeEvent(value, rowIndex);
260
+ } catch (error) {
261
+ const current = error instanceof SessionFormatError ? error : new SessionFormatError(`released v2 row ${rowIndex} is malformed`, { cause: error });
262
+ if (!recoverable) throw current;
263
+ issue ??= current;
264
+ continue;
265
+ }
266
+ if (issue !== void 0) {
267
+ if (event.type === "turn/end") throw issue;
268
+ continue;
269
+ }
270
+ if (event.seq !== events.length) {
271
+ const gap = new SessionFormatError(`released v2 row ${rowIndex} has seq gap (expected ${events.length}, got ${event.seq})`);
272
+ if (!recoverable) throw gap;
273
+ issue = gap;
274
+ if (event.type === "turn/end") throw issue;
275
+ continue;
276
+ }
277
+ events.push(event);
278
+ }
279
+ const artifact = snapshotSessionFormatArtifact({
280
+ header,
281
+ inheritedEventCount: deriveInheritedEventCount(header, events),
282
+ events
283
+ }, "released v2 artifact");
284
+ assertReleasedV2PhysicalArtifact(artifact);
285
+ return artifact;
286
+ }
287
+ function decodeEvent(value, rowIndex) {
288
+ const record = jsonRecord(snapshotSessionFormatJson(value, `released v2 row ${rowIndex}`), `released v2 row ${rowIndex}`);
289
+ if (record["sourceEventSeqs"] === void 0) return record;
290
+ const seq = sessionFormatCount(record["seq"], `released v2 row ${rowIndex} seq`);
291
+ return snapshotSessionFormatJson({
292
+ ...record,
293
+ sourceEventSeqs: decodeSeqRanges(record["sourceEventSeqs"], seq)
294
+ }, `released v2 row ${rowIndex} provenance`);
295
+ }
296
+ function deriveInheritedEventCount(header, events) {
297
+ let cut;
298
+ for (const event of events) {
299
+ if (event.type !== "session/end-seed") continue;
300
+ if (jsonRecord(event.data, `session/end-seed ${event.seq} data`)["inherited"] === true) cut = event.seq;
301
+ }
302
+ if (header.isSeeded && cut === void 0) throw new SessionFormatError("released v2 seeded Session lacks an inherited end-seed marker");
303
+ if (!header.isSeeded && cut !== void 0) throw new SessionFormatError("released v2 unseeded Session contains an inherited end-seed marker");
304
+ return cut ?? 0;
305
+ }
306
+ function encodeArtifact(artifact) {
307
+ assertReleasedV2PhysicalArtifact(artifact);
308
+ const header = artifact.header;
309
+ const physicalHeader = snapshotSessionFormatJson({
310
+ type: "session",
311
+ version: 2,
312
+ id: header.id,
313
+ createdAt: header.createdAt,
314
+ ...header.cwd === void 0 ? {} : { cwd: header.cwd },
315
+ ...header.parentSession === void 0 ? {} : { parentSession: header.parentSession },
316
+ isSeeded: header.isSeeded,
317
+ ...header.origin === void 0 ? {} : { origin: header.origin },
318
+ delegationDepth: header.delegationDepth,
319
+ ...header.agentPreset === void 0 ? {} : { agentPreset: header.agentPreset }
320
+ }, "released v2 encoded header");
321
+ const rows = Object.freeze(artifact.events.map((event) => encodeProvenance(event)));
322
+ return Object.freeze({
323
+ header: physicalHeader,
324
+ rows
325
+ });
326
+ }
327
+ function encodeProvenance(event) {
328
+ if (event.sourceEventSeqs === void 0) return event;
329
+ return snapshotSessionFormatJson({
330
+ ...event,
331
+ sourceEventSeqs: encodeSeqRanges(event.sourceEventSeqs)
332
+ }, `released v2 event ${event.seq} provenance`);
333
+ }
334
+ function decodeSeqRanges(value, maxEntries) {
335
+ if (!Array.isArray(value)) throw new SessionFormatError("sourceEventSeqs must be an array");
336
+ const output = [];
337
+ let hasRange = false;
338
+ for (const entry of value) {
339
+ if (!Array.isArray(entry)) {
340
+ output.push(sessionFormatCount(entry, "sourceEventSeqs member"));
341
+ continue;
342
+ }
343
+ if (entry.length !== 2) throw new SessionFormatError("sourceEventSeqs range must be a [start, end] pair");
344
+ const start = sessionFormatCount(entry[0], "sourceEventSeqs range start");
345
+ const end = sessionFormatCount(entry[1], "sourceEventSeqs range end");
346
+ if (start > end || end >= maxEntries || end - start + 1 > maxEntries - output.length) throw new SessionFormatError("sourceEventSeqs range exceeds its event seq");
347
+ for (let current = start; current <= end; current += 1) output.push(current);
348
+ hasRange = true;
349
+ }
350
+ const seen = /* @__PURE__ */ new Set();
351
+ for (const source of output) {
352
+ if (source >= maxEntries || seen.has(source)) throw new SessionFormatError("sourceEventSeqs ranges must contain unique earlier seqs");
353
+ seen.add(source);
354
+ }
355
+ if (hasRange && output.some((source, index) => index > 0 && source <= output[index - 1])) throw new SessionFormatError("sourceEventSeqs ranges must be strictly increasing");
356
+ return output;
357
+ }
358
+ function encodeSeqRanges(values) {
359
+ if (values.some((value, index) => index > 0 && value <= values[index - 1])) return [...values];
360
+ const output = [];
361
+ for (let index = 0; index < values.length;) {
362
+ const start = values[index];
363
+ let end = start;
364
+ while (index + 1 < values.length && values[index + 1] === end + 1) {
365
+ index += 1;
366
+ end += 1;
367
+ }
368
+ output.push(end - start >= 2 ? [start, end] : start);
369
+ if (end - start === 1) output.push(end);
370
+ index += 1;
371
+ }
372
+ return output;
373
+ }
374
+ function jsonRecord(value, label) {
375
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new SessionFormatError(`${label} must be an object`);
376
+ return value;
377
+ }
378
+ function exactKeys(record, required, optional, label) {
379
+ const allowed = new Set([...required, ...optional]);
380
+ const missing = required.find((key) => !Object.hasOwn(record, key));
381
+ if (missing !== void 0) throw new SessionFormatError(`${label} lacks ${missing}`);
382
+ const unexpected = Object.keys(record).find((key) => !allowed.has(key));
383
+ if (unexpected !== void 0) throw new SessionFormatError(`${label} has unexpected field ${unexpected}`);
384
+ }
385
+ //#endregion
386
+ //#region lib/types/migration.js
387
+ /** Adjacent migration that embeds released-v1 top-level Assistant chunks into v2 attempt events. */
388
+ const sessionFormatV1ToV2 = defineSessionFormatMigration({
389
+ name: "@xneog/dsh-session-format-v1-to-v2",
390
+ fromVersion: 1,
391
+ toVersion: 2,
392
+ migrateHeader(header) {
393
+ assertReleasedV1Header(header);
394
+ return {
395
+ ...header,
396
+ version: 2
397
+ };
398
+ },
399
+ migrate(source) {
400
+ assertReleasedV1Artifact(source);
401
+ const unknown = source.events.find((event) => RELEASED_V0_EVENT_DISPOSITIONS[event.type] === void 0);
402
+ if (unknown !== void 0) throw refusal(`format v1 contains unknown event type ${JSON.stringify(unknown.type)} at seq ${unknown.seq}`);
403
+ const groups = collectAttemptGroups(source.events);
404
+ const groupByChunk = /* @__PURE__ */ new Map();
405
+ const groupByMessage = /* @__PURE__ */ new Map();
406
+ for (const group of groups) {
407
+ for (const chunk of group.chunks) groupByChunk.set(chunk.seq, group);
408
+ if (group.messageSeq !== void 0) groupByMessage.set(group.messageSeq, group);
409
+ }
410
+ const staged = [];
411
+ const oldToNew = /* @__PURE__ */ new Map();
412
+ for (const sourceEvent of source.events) {
413
+ const group = groupByChunk.get(sourceEvent.seq);
414
+ if (group !== void 0) {
415
+ if (group.messageSeq === void 0 && sourceEvent.seq === group.chunks.at(-1)?.seq) stage(staged, oldToNew, sourceEvent.seq, attemptEvent(group));
416
+ continue;
417
+ }
418
+ const messageGroup = groupByMessage.get(sourceEvent.seq);
419
+ if (messageGroup !== void 0) {
420
+ stage(staged, oldToNew, sourceEvent.seq, messageEvent(sourceEvent, messageGroup));
421
+ continue;
422
+ }
423
+ const event = source.header.isSeeded && sourceEvent.seq === source.inheritedEventCount && sourceEvent.type === "session/end-seed" ? {
424
+ ...sourceEvent,
425
+ data: { inherited: true }
426
+ } : sourceEvent;
427
+ stage(staged, oldToNew, sourceEvent.seq, event);
428
+ }
429
+ const inheritedEventCount = remapInheritedCut(source, groups, staged);
430
+ if (source.header.isSeeded && source.events[source.inheritedEventCount]?.type !== "session/end-seed") {
431
+ const next = source.events[source.inheritedEventCount];
432
+ const previous = source.events[source.inheritedEventCount - 1];
433
+ staged.splice(inheritedEventCount, 0, {
434
+ origin: -1,
435
+ event: {
436
+ type: "session/end-seed",
437
+ seq: inheritedEventCount,
438
+ time: next?.time ?? previous?.time ?? source.header.createdAt,
439
+ data: { inherited: true }
440
+ }
441
+ });
442
+ oldToNew.clear();
443
+ for (const [seq, candidate] of staged.entries()) if (candidate.origin >= 0) oldToNew.set(candidate.origin, seq);
444
+ }
445
+ for (const group of groups) for (const chunk of group.chunks) oldToNew.delete(chunk.seq);
446
+ const target = snapshotSessionFormatArtifact({
447
+ header: {
448
+ ...source.header,
449
+ version: 2
450
+ },
451
+ inheritedEventCount,
452
+ events: staged.map(({ event }, seq) => remapReferences(event, seq, oldToNew))
453
+ }, "released v1-to-v2 target");
454
+ assertReleasedV2Artifact(target);
455
+ return target;
456
+ },
457
+ validateTarget: assertReleasedV2Artifact,
458
+ validateTargetHeader: assertReleasedV2Header
459
+ });
460
+ function collectAttemptGroups(events) {
461
+ const groups = [];
462
+ const current = /* @__PURE__ */ new Map();
463
+ for (const event of events) {
464
+ if (event.type === "assistant/chunk") {
465
+ const data = record(event.data);
466
+ const turn = coordinate(data["turn"]);
467
+ const step = coordinate(data["step"]);
468
+ const key = `${turn}:${step}`;
469
+ let group = current.get(key);
470
+ if (group === void 0 || group.terminal) {
471
+ group = {
472
+ turn,
473
+ step,
474
+ chunks: [],
475
+ terminal: false
476
+ };
477
+ groups.push(group);
478
+ current.set(key, group);
479
+ }
480
+ group.chunks.push(event);
481
+ if (record(data["chunk"])["type"] === "finish") group.terminal = true;
482
+ continue;
483
+ }
484
+ if (event.type !== "assistant/message") {
485
+ closeAttemptAtBoundary(event, current);
486
+ continue;
487
+ }
488
+ const data = record(event.data);
489
+ const turn = coordinate(data["turn"]);
490
+ const step = coordinate(data["step"]);
491
+ const sources = event.sourceEventSeqs;
492
+ if (!Array.isArray(sources)) {
493
+ if (groups.some((candidate) => candidate.messageSeq === void 0 && candidate.turn === turn && candidate.step === step)) throw refusal(`assistant/message ${event.seq} does not cite its complete v1 chunk attempt`);
494
+ groups.push({
495
+ turn,
496
+ step,
497
+ chunks: [],
498
+ terminal: true,
499
+ messageSeq: event.seq
500
+ });
501
+ continue;
502
+ }
503
+ if (sources.length === 0) {
504
+ groups.push({
505
+ turn,
506
+ step,
507
+ chunks: [],
508
+ terminal: true,
509
+ messageSeq: event.seq
510
+ });
511
+ continue;
512
+ }
513
+ const group = groups.find((candidate) => candidate.messageSeq === void 0 && candidate.turn === turn && candidate.step === step && sameNumbers(candidate.chunks.map((chunk) => chunk.seq), sources));
514
+ if (group === void 0) throw refusal(`assistant/message ${event.seq} chunk provenance is not one complete ordered attempt`);
515
+ group.messageSeq = event.seq;
516
+ group.terminal = true;
517
+ }
518
+ return groups;
519
+ }
520
+ function closeAttemptAtBoundary(event, current) {
521
+ if (event.type === "turn/end") {
522
+ const turn = coordinate(record(event.data)["turn"]);
523
+ for (const group of current.values()) if (group.turn === turn) group.terminal = true;
524
+ return;
525
+ }
526
+ if (event.type !== "step/end" && event.type !== "llm/retry" && event.type !== "llm/retry-started") return;
527
+ const data = record(event.data);
528
+ const turn = coordinate(data["turn"]);
529
+ const step = coordinate(data["step"]);
530
+ const group = current.get(`${turn}:${step}`);
531
+ if (group !== void 0) group.terminal = true;
532
+ }
533
+ function streamOf(group) {
534
+ const accumulator = new AssistantStreamAccumulator();
535
+ for (const event of group.chunks) {
536
+ const data = record(event.data);
537
+ accumulator.push({
538
+ time: event.time,
539
+ chunk: data["chunk"]
540
+ });
541
+ }
542
+ return accumulator.snapshot();
543
+ }
544
+ function messageEvent(source, group) {
545
+ const data = record(source.data);
546
+ const { sourceEventSeqs: _sourceEventSeqs, ...event } = source;
547
+ return {
548
+ ...event,
549
+ data: {
550
+ ...data,
551
+ stream: streamOf(group)
552
+ }
553
+ };
554
+ }
555
+ function attemptEvent(group) {
556
+ const last = group.chunks.at(-1);
557
+ return {
558
+ type: "assistant/attempt",
559
+ seq: last.seq,
560
+ time: last.time,
561
+ data: {
562
+ turn: group.turn,
563
+ step: group.step,
564
+ stream: streamOf(group)
565
+ }
566
+ };
567
+ }
568
+ function stage(staged, oldToNew, origin, event) {
569
+ oldToNew.set(origin, staged.length);
570
+ staged.push({
571
+ origin,
572
+ event
573
+ });
574
+ }
575
+ function remapInheritedCut(source, groups, staged) {
576
+ const cut = source.inheritedEventCount;
577
+ for (const group of groups) {
578
+ const members = group.messageSeq === void 0 ? group.chunks.map((chunk) => chunk.seq) : [...group.chunks.map((chunk) => chunk.seq), group.messageSeq];
579
+ const before = members.some((seq) => seq < cut);
580
+ const after = members.some((seq) => seq >= cut);
581
+ if (before && after) throw refusal(`inherited Session cut ${cut} splits one Assistant attempt`);
582
+ }
583
+ return staged.filter((candidate) => candidate.origin < cut).length;
584
+ }
585
+ function remapReferences(source, targetSeq, mapping) {
586
+ const { sourceEventSeqs, surfaceOp, ...event } = source;
587
+ const sources = sourceEventSeqs === void 0 ? {} : { sourceEventSeqs: mapList(numberArray(sourceEventSeqs), mapping, `${source.type} ${source.seq} sources`) };
588
+ let operation = surfaceOp;
589
+ if (surfaceOp !== void 0 && surfaceOp !== "append") {
590
+ const replacement = record(surfaceOp);
591
+ operation = {
592
+ op: "replace",
593
+ start: mapOne(coordinate(replacement["start"]), mapping, `${source.type} ${source.seq} surface start`),
594
+ end: mapOne(coordinate(replacement["end"]), mapping, `${source.type} ${source.seq} surface end`)
595
+ };
596
+ }
597
+ return {
598
+ ...event,
599
+ seq: targetSeq,
600
+ data: remapPayloadReferences(source, mapping),
601
+ ...sources,
602
+ ...operation === void 0 ? {} : { surfaceOp: operation }
603
+ };
604
+ }
605
+ function remapPayloadReferences(event, mapping) {
606
+ const data = record(event.data);
607
+ switch (event.type) {
608
+ case "command/done": return data["sourceEventSeq"] === void 0 ? data : {
609
+ ...data,
610
+ sourceEventSeq: mapOne(coordinate(data["sourceEventSeq"]), mapping, `command/done ${event.seq} sourceEventSeq`)
611
+ };
612
+ case "compaction/prune":
613
+ case "compaction/summary": {
614
+ const range = record(data["shadowedRange"]);
615
+ return {
616
+ ...data,
617
+ shadowedRange: {
618
+ start: mapOne(coordinate(range["start"]), mapping, `${event.type} ${event.seq} shadowedRange start`),
619
+ end: mapOne(coordinate(range["end"]), mapping, `${event.type} ${event.seq} shadowedRange end`)
620
+ },
621
+ shadowedSeqs: mapList(numberArray(data["shadowedSeqs"]), mapping, `${event.type} ${event.seq} shadowedSeqs`)
622
+ };
623
+ }
624
+ case "session/title":
625
+ case "session/title-llm-request": return {
626
+ ...data,
627
+ messageSeqs: mapList(numberArray(data["messageSeqs"]), mapping, `${event.type} ${event.seq} messageSeqs`)
628
+ };
629
+ default: return data;
630
+ }
631
+ }
632
+ function mapList(values, mapping, label) {
633
+ return values.map((value) => mapOne(value, mapping, label));
634
+ }
635
+ function mapOne(value, mapping, label) {
636
+ const mapped = mapping.get(value);
637
+ if (mapped === void 0) throw refusal(`${label} targets consumed assistant/chunk ${value}`);
638
+ return mapped;
639
+ }
640
+ function record(value) {
641
+ return value;
642
+ }
643
+ function numberArray(value) {
644
+ return value;
645
+ }
646
+ function coordinate(value) {
647
+ return value;
648
+ }
649
+ function sameNumbers(left, right) {
650
+ return left.length === right.length && left.every((value, index) => value === right[index]);
651
+ }
652
+ function refusal(message) {
653
+ return new SessionFormatUnsupportedMigrationError(message);
654
+ }
655
+ //#endregion
656
+ export { RELEASED_V2_EVENT_DISPOSITIONS, RELEASED_V2_EVENT_TYPES, assertReleasedV2Artifact, assertReleasedV2Header, assertReleasedV2PhysicalArtifact, releasedV1SessionFormatCodec, releasedV2SessionFormatCodec, restoreReleasedV2Artifact, sessionFormatV1ToV2 };
@@ -0,0 +1,10 @@
1
+ import type { EncodedSessionFormatArtifact, SessionFormatArtifact, SessionFormatHeader } from '@xneog/dsh-session-format';
2
+ /** Frozen physical JSON codec for released v2. */
3
+ export declare const releasedV2SessionFormatCodec: Readonly<{
4
+ version: number;
5
+ decodeHeader(value: unknown): SessionFormatHeader;
6
+ decodeArtifact(headerValue: unknown, rowValues: readonly unknown[]): SessionFormatArtifact;
7
+ decodeRecoverableArtifact(headerValue: unknown, rowValues: readonly unknown[]): SessionFormatArtifact;
8
+ encodeArtifact(artifact: SessionFormatArtifact): EncodedSessionFormatArtifact;
9
+ }>;
10
+ //# sourceMappingURL=codec.d.ts.map
@@ -0,0 +1,6 @@
1
+ import { type ReleasedV0PayloadDisposition } from '@xneog/dsh-session-format-v0-to-v1';
2
+ /** Exact top-level event and payload-member inventory frozen for released v2. */
3
+ export declare const RELEASED_V2_EVENT_DISPOSITIONS: Readonly<Record<string, ReleasedV0PayloadDisposition>>;
4
+ /** Stable sorted released-v2 event inventory. */
5
+ export declare const RELEASED_V2_EVENT_TYPES: readonly string[];
6
+ //# sourceMappingURL=dispositions.d.ts.map
@@ -0,0 +1,7 @@
1
+ /** Frozen released-v1 physical codec and assistant-stream migration into v2. */
2
+ export { releasedV1SessionFormatCodec } from '@xneog/dsh-session-format-v0-to-v1';
3
+ export * from './codec.ts';
4
+ export * from './dispositions.ts';
5
+ export * from './migration.ts';
6
+ export * from './validation.ts';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,3 @@
1
+ /** Adjacent migration that embeds released-v1 top-level Assistant chunks into v2 attempt events. */
2
+ export declare const sessionFormatV1ToV2: import("@xneog/dsh-session-format").SessionFormatMigration;
3
+ //# sourceMappingURL=migration.d.ts.map
@@ -0,0 +1,28 @@
1
+ import type { SessionFormatArtifact, SessionFormatHeader } from '@xneog/dsh-session-format';
2
+ /**
3
+ * Validate the exact logical header written by released v2.
4
+ * @param header - decoded released-v2 Session header.
5
+ * @throws {SessionFormatError} when the header is not an exact released-v2 value.
6
+ */
7
+ export declare function assertReleasedV2Header(header: SessionFormatHeader): void;
8
+ /**
9
+ * Validate the exact logical image emitted by the released v2 writer.
10
+ * @param artifact - complete decoded released-v2 Session artifact.
11
+ * @throws {SessionFormatError} when an envelope, payload, relationship, or inherited cut is invalid.
12
+ * @throws {SessionFormatUnsupportedMigrationError} when the artifact contains an unknown event type.
13
+ */
14
+ export declare function assertReleasedV2Artifact(artifact: SessionFormatArtifact): void;
15
+ /**
16
+ * Validate only the released-v2 physical header, event envelopes, and inherited cut.
17
+ * Event vocabulary and payload semantics belong to target or installed-current restoration.
18
+ * @param artifact - complete physical-codec output.
19
+ */
20
+ export declare function assertReleasedV2PhysicalArtifact(artifact: SessionFormatArtifact): void;
21
+ /**
22
+ * Restore and validate one decoded released-v2 artifact.
23
+ * @param artifact - detached vocabulary-restored artifact.
24
+ * @param knownEventTypes - event types understood by the installed current Session package.
25
+ * @returns the same validated artifact.
26
+ */
27
+ export declare function restoreReleasedV2Artifact(artifact: SessionFormatArtifact, knownEventTypes: ReadonlySet<string>): SessionFormatArtifact;
28
+ //# sourceMappingURL=validation.d.ts.map
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@xneog/dsh-session-format-v1-to-v2",
3
+ "description": "Frozen released-v1 Session codec and assistant-stream migration to v2",
4
+ "version": "0.1.3-alpha.1",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/gomes007-alt/xneog-harness.git",
11
+ "directory": "packages/session/session-format-v1-to-v2"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./src/*": "./src/*",
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "lib/index.js",
26
+ "lib/types/**/*.d.ts"
27
+ ],
28
+ "license": "MIT",
29
+ "dsh": {
30
+ "sessionFormatMigration": {
31
+ "from": 1,
32
+ "to": 2,
33
+ "export": ".",
34
+ "migration": "sessionFormatV1ToV2",
35
+ "sourceCodec": "releasedV1SessionFormatCodec",
36
+ "targetCodec": "releasedV2SessionFormatCodec",
37
+ "targetHeaderValidator": "assertReleasedV2Header",
38
+ "targetRestorer": "restoreReleasedV2Artifact"
39
+ }
40
+ },
41
+ "dependencies": {
42
+ "@xneog/dsh-llm": "^0.1.3-alpha.1",
43
+ "@xneog/dsh-session-format-v0-to-v1": "^0.1.3-alpha.1",
44
+ "@xneog/dsh-util-values": "^0.1.3-alpha.1",
45
+ "@xneog/dsh-session-format": "^0.1.3-alpha.1"
46
+ },
47
+ "peerDependencies": {
48
+ "@xneog/cordis": "^4.0.2"
49
+ },
50
+ "devDependencies": {
51
+ "@xneog/cordis": "^4.0.2",
52
+ "@xneog/dsh-session": "^0.1.3-alpha.1"
53
+ }
54
+ }