@deepseek-ai/dsh-session-persistence-jsonl 0.1.5-rc.2 → 0.1.6-alpha.2
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/README.i18n.yaml +2 -2
- package/README.md +4 -2
- package/README.zh.md +9 -7
- package/lib/index.js +2 -1
- package/lib/worker.cjs +445 -287
- package/package.json +12 -12
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/session/session-persistence-jsonl/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: fc431ff27bcd236c4d44b8d3e7b20d4291b936d4
|
|
6
|
+
README.zh.md: 3b265bf43f058d4c0711568df156825febed4d24
|
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a
|
|
|
53
53
|
|
|
54
54
|
### On-disk layout
|
|
55
55
|
|
|
56
|
-
Each session gets a session-owned directory under a readable project directory. Every canonical generation starts with a physical header whose version equals its filename. The current format stores one physical row per durable event; the frozen v0 and v1 readers also understand their historical packed Assistant-delta rows. The current format stores `isSeeded` in the header and derives the inherited cut from the last tagged `session/end-seed` marker, while historical codecs translate their numeric `seedLength`. The format catalog completes that translation before a handle exposes current logical values. Current storage records use the lossless
|
|
56
|
+
Each session gets a session-owned directory under a readable project directory. Every canonical generation starts with a physical header whose version equals its filename. The current format stores one physical row per durable event; the frozen v0 and v1 readers also understand their historical packed Assistant-delta rows. The current format stores `isSeeded` in the header and derives the inherited cut from the last tagged `session/end-seed` marker, while historical codecs translate their numeric `seedLength`. The format catalog completes that translation before a handle exposes current logical values. Current storage records use the lossless source-event representation described below:
|
|
57
57
|
|
|
58
58
|
```text
|
|
59
59
|
<root>/
|
|
@@ -62,9 +62,11 @@ Each session gets a session-owned directory under a readable project directory.
|
|
|
62
62
|
session.jsonl.zstd # released v0, compressed root
|
|
63
63
|
session.v1.jsonl.zstd # released v1, compressed root
|
|
64
64
|
session.v2.jsonl.zstd # released v2, compressed root
|
|
65
|
+
session.v3.jsonl.zstd # released v3/current, compressed root
|
|
65
66
|
session.jsonl # released v0, raw root
|
|
66
67
|
session.v1.jsonl # released v1, raw root
|
|
67
|
-
session.v2.jsonl # released v2, raw root
|
|
68
|
+
session.v2.jsonl # released v2, raw root
|
|
69
|
+
session.v3.jsonl # released v3/current, raw root; later versions use vN
|
|
68
70
|
```
|
|
69
71
|
|
|
70
72
|
Session ids are injectively escaped to one safe path segment before use (no traversal, no collision). The normalized cwd keeps the project directory readable for navigation; cwd strings that normalize alike share a project directory while session ids still select distinct session directories. Runtime operations select the numerically highest canonical generation, and format-refusal diagnostics name that absolute path so an operator can find the raw log a build refused to interpret.
|
package/README.zh.md
CHANGED
|
@@ -29,7 +29,7 @@ kind: "package-reference"
|
|
|
29
29
|
|
|
30
30
|
### 何时选择
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
当消费方受益于每会话一份产物——导航、外部工具或可逐行读取的原始日志——时选择此后端。它是唯一的第一方会话持久化提供方。后端把会话保存在部署控制的根下:项目本地、共享、临时或集中式。
|
|
33
33
|
|
|
34
34
|
### 最小配置
|
|
35
35
|
|
|
@@ -62,12 +62,14 @@ kind: "package-reference"
|
|
|
62
62
|
session.jsonl.zstd # released v0, compressed root
|
|
63
63
|
session.v1.jsonl.zstd # released v1, compressed root
|
|
64
64
|
session.v2.jsonl.zstd # released v2, compressed root
|
|
65
|
+
session.v3.jsonl.zstd # released v3/current, compressed root
|
|
65
66
|
session.jsonl # released v0, raw root
|
|
66
67
|
session.v1.jsonl # released v1, raw root
|
|
67
|
-
session.v2.jsonl # released v2, raw root
|
|
68
|
+
session.v2.jsonl # released v2, raw root
|
|
69
|
+
session.v3.jsonl # released v3/current, raw root; later versions use vN
|
|
68
70
|
```
|
|
69
71
|
|
|
70
|
-
会话 id 在使用前被单射转义为一个安全路径段(无遍历、无冲突)。规范化 cwd 让项目目录保持可读、便于导航;规范化相同的 cwd 字符串共享项目目录,而会话 id
|
|
72
|
+
会话 id 在使用前被单射转义为一个安全路径段(无遍历、无冲突)。规范化 cwd 让项目目录保持可读、便于导航;规范化相同的 cwd 字符串共享项目目录,而会话 id 仍选择不同会话目录。运行时操作选择数值最高的规范 generation,格式拒绝诊断会点名该绝对路径,让操作者能找到构建拒绝解读的原始日志。
|
|
71
73
|
|
|
72
74
|
### 持久性与崩溃语义
|
|
73
75
|
|
|
@@ -77,7 +79,7 @@ kind: "package-reference"
|
|
|
77
79
|
|
|
78
80
|
### 读取日志
|
|
79
81
|
|
|
80
|
-
`open(id, 'read'|'write')` 选择最高规范 generation。当前格式输入走普通快速路径。对于历史输入,只读 open 会单遍解码并迁移源、校验当前逻辑结果,然后在不发布后继的情况下返回。写 open 会在可用时复用按 revision 为键的 preparation,否则执行同一套 preparation,再按有界分片编码同目录临时文件、在 Worker Thread 中校验、复查源修订,并在返回前以不覆盖方式发布当前后继。源保持逐字节不变。如果源在 preparation 后发生变化,该次写 open 会失败,已经返回给读方的逻辑历史不会被替换;后续写 open 会针对新的 revision 重新执行 preparation
|
|
82
|
+
`open(id, 'read'|'write')` 选择最高规范 generation。当前格式输入走普通快速路径。对于历史输入,只读 open 会单遍解码并迁移源、校验当前逻辑结果,然后在不发布后继的情况下返回。写 open 会在可用时复用按 revision 为键的 preparation,否则执行同一套 preparation,再按有界分片编码同目录临时文件、在 Worker Thread 中校验、复查源修订,并在返回前以不覆盖方式发布当前后继。源保持逐字节不变。如果源在 preparation 后发生变化,该次写 open 会失败,已经返回给读方的逻辑历史不会被替换;后续写 open 会针对新的 revision 重新执行 preparation。后端在 memo 化前冻结已解码的 event graph,并在此时将其标记为 `shared-frozen`;句柄读取和 slice 即使为空也保留该状态。只有尚未实体化的 pending 空日志报告 `detached`。`stat(id)` 与 `list()` 只选择并转换最高 generation 的 header,不读取事件行,也不启动迁移;快照携带所选文件的 `sizeBytes` 与尽力而为的 stat 派生修订号。选择 `compression: 'none'` 后,日志是外部读取方可直接消费的换行分隔文本;压缩默认值必须经后端读取。
|
|
81
83
|
|
|
82
84
|
-----
|
|
83
85
|
|
|
@@ -108,7 +110,7 @@ kind: "package-reference"
|
|
|
108
110
|
| [`src/migration-verifier.ts`](src/migration-verifier.ts) | stage 与竞争 generation 校验的 Worker 生命周期 |
|
|
109
111
|
| [`src/zstd.ts`](src/zstd.ts) | Zstandard 帧压缩、解码与帧扫描 |
|
|
110
112
|
| [`src/win32.ts`](src/win32.ts) | Windows write-through 发布与目录创建 |
|
|
111
|
-
| — |
|
|
113
|
+
| — | 不发布运行时不变式伴生入口;身份在存储层强制;持久化正确性依赖后端往返与崩溃尾部测试,本包不公开可持续观察的进程内关系。 |
|
|
112
114
|
|
|
113
115
|
</details>
|
|
114
116
|
|
|
@@ -151,11 +153,11 @@ JSONL 存储不修改实时请求前缀。只有重建历史、当前 envelope
|
|
|
151
153
|
|
|
152
154
|
这些限制说明本后端何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是任务积压。
|
|
153
155
|
|
|
154
|
-
- **格式迁移保留已配置编码,且只支持 catalog 中的链**——本 build
|
|
156
|
+
- **格式迁移保留已配置编码,且只支持 catalog 中的链**——本 build 把受支持的历史代迁移到当前格式;更改压缩需要独立根,保留的旧版本不提供自动 fallback 或 downgrade 支持。
|
|
155
157
|
- **平铺文件存储布局不加载**——加载前使用独立根,或将预发布产物移入项目/会话目录布局。
|
|
156
158
|
- **压缩文件不能直接按行读取**——使用后端加载;或在写入新根前选择 `compression: 'none'`,供外部行读取方使用。
|
|
157
159
|
- **不删除会话文件**——日志在 `root` 下累积,直到外部移除;seam 无删除接口。
|
|
158
|
-
- **每会话一个活动写入方**——写句柄认领在所属后端实例内排除第二个写入方,内核锁(`session.lock` 上的非阻塞 `flock(2)`;Windows
|
|
160
|
+
- **每会话一个活动写入方**——写句柄认领在所属后端实例内排除第二个写入方,内核锁(`session.lock` 上的非阻塞 `flock(2)`;Windows 上为由该路径派生的命名内核信号量,零文件系统足迹)排除其他所有实例与进程;锁在以写模式打开既有产物时立即获取,新建会话则仅在首次实体化写入之前获取,因此未实体化的会话不留任何文件系统足迹。崩溃持有者的锁随其进程消亡,会话立即可再写入,而活着但卡死的持有者会阻塞写入方直到其进程退出(POSIX 上删除锁文件即放弃该排他;释放本身从不删除它)。咨询式 `flock` 在部分网络文件系统(NFSv3)上不可靠,Windows 信号量名按登录会话隔离。
|
|
159
161
|
- **POSIX 实体化需要硬链接支持**——第一次 append 使用 `link()`,使同 id 竞态失败而不覆盖已提交日志;Windows 使用无替换 write-through rename。
|
|
160
162
|
- **POSIX 写入需要匹配的预编译系统 addon**——[`node-addon-system`](../../../native/system/README.zh.md) 提供异步 flock,无须在用户侧编译。addon 缺失时拒绝写入所有权;Windows 保留其信号量实现。
|
|
161
163
|
|
package/lib/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { constants, createZstdCompress, createZstdDecompress, zstdCompress, zstd
|
|
|
16
16
|
import { isDeepStrictEqual, promisify } from "node:util";
|
|
17
17
|
import { constants as constants$1 } from "node:buffer";
|
|
18
18
|
import { Worker } from "node:worker_threads";
|
|
19
|
+
import { currentSessionMessageProjections } from "@deepseek-ai/dsh-session-format-catalog/message-projections";
|
|
19
20
|
import { Readable, pipeline } from "node:stream";
|
|
20
21
|
/**
|
|
21
22
|
* The JSONL session handle. Mutations serialize on a per-handle promise
|
|
@@ -1819,7 +1820,7 @@ async function verifyCurrentGeneration(path, compression, expectedId, expectedEv
|
|
|
1819
1820
|
});
|
|
1820
1821
|
if (generation.meta.id !== expectedId) throw new Error(`current session generation contains id "${generation.meta.id}", expected "${expectedId}"`);
|
|
1821
1822
|
if (generation.events.length !== expectedEventCount) throw new Error(`current session generation contains ${generation.events.length} events, expected ${expectedEventCount}`);
|
|
1822
|
-
Session.fromRestore(generation.meta.id, generation.events, generation.meta, generation.inheritedEventCount, "detached");
|
|
1823
|
+
Session.fromRestore(generation.meta.id, generation.events, generation.meta, generation.inheritedEventCount, "detached", currentSessionMessageProjections);
|
|
1823
1824
|
assertCurrentAssistantStreams(generation.events);
|
|
1824
1825
|
return {
|
|
1825
1826
|
identity: snapshot.identity,
|
package/lib/worker.cjs
CHANGED
|
@@ -9,6 +9,320 @@ let node_util = require("node:util");
|
|
|
9
9
|
let node_zlib = require("node:zlib");
|
|
10
10
|
let node_module = require("node:module");
|
|
11
11
|
let node_buffer = require("node:buffer");
|
|
12
|
+
//#region ../../util/values/src/index.ts
|
|
13
|
+
/**
|
|
14
|
+
* Mark an unreachable closed-union branch.
|
|
15
|
+
* @param value - impossible value; an unhandled typed variant fails at the call site.
|
|
16
|
+
* @param context - optional switch-site label included in the failure message.
|
|
17
|
+
* @returns never; a runtime value that escaped its type always throws.
|
|
18
|
+
*/
|
|
19
|
+
function assertNever(value, context) {
|
|
20
|
+
const rendered = JSON.stringify(value) ?? String(value);
|
|
21
|
+
throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
|
|
22
|
+
}
|
|
23
|
+
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
24
|
+
function hasIntrinsicConstructor(prototype, name) {
|
|
25
|
+
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
26
|
+
if (typeof constructor !== "function") return false;
|
|
27
|
+
try {
|
|
28
|
+
return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Whether a candidate is one realm's intrinsic `Object.prototype`. */
|
|
34
|
+
function isIntrinsicObjectPrototype(value) {
|
|
35
|
+
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
|
|
36
|
+
}
|
|
37
|
+
/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
|
|
38
|
+
function hasPlainArrayPrototype(value) {
|
|
39
|
+
const prototype = Object.getPrototypeOf(value);
|
|
40
|
+
if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
|
|
41
|
+
const objectPrototype = Object.getPrototypeOf(prototype);
|
|
42
|
+
return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);
|
|
43
|
+
}
|
|
44
|
+
/** Whether an object is a plain or null-prototype record from any JavaScript realm. */
|
|
45
|
+
function hasPlainObjectPrototype(value) {
|
|
46
|
+
const prototype = Object.getPrototypeOf(value);
|
|
47
|
+
return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype(prototype);
|
|
48
|
+
}
|
|
49
|
+
/** Return every JSON-visible object key, or reject own data JSON would discard. */
|
|
50
|
+
function enumerableStringKeys(value) {
|
|
51
|
+
const keys = Reflect.ownKeys(value);
|
|
52
|
+
if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
|
|
53
|
+
return keys;
|
|
54
|
+
}
|
|
55
|
+
/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
|
|
56
|
+
function walkJsonValue(value, detach) {
|
|
57
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
58
|
+
let root;
|
|
59
|
+
const assign = (destination, item) => {
|
|
60
|
+
if (destination === void 0) return;
|
|
61
|
+
if (destination.kind === "root") root = item;
|
|
62
|
+
else if (destination.kind === "array") destination.target[destination.index] = item;
|
|
63
|
+
else Object.defineProperty(destination.target, destination.key, {
|
|
64
|
+
value: item,
|
|
65
|
+
enumerable: true,
|
|
66
|
+
configurable: true,
|
|
67
|
+
writable: true
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
const tasks = [{
|
|
71
|
+
kind: "visit",
|
|
72
|
+
value,
|
|
73
|
+
...detach ? { destination: { kind: "root" } } : {}
|
|
74
|
+
}];
|
|
75
|
+
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
76
|
+
if (task.kind === "leave") {
|
|
77
|
+
ancestors.delete(task.source);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (task.kind === "array-item") {
|
|
81
|
+
if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
|
|
82
|
+
tasks.push({
|
|
83
|
+
kind: "visit",
|
|
84
|
+
value: task.source[task.index],
|
|
85
|
+
...task.target === void 0 ? {} : { destination: {
|
|
86
|
+
kind: "array",
|
|
87
|
+
target: task.target,
|
|
88
|
+
index: task.index
|
|
89
|
+
} }
|
|
90
|
+
});
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (task.kind === "object-property") {
|
|
94
|
+
tasks.push({
|
|
95
|
+
kind: "visit",
|
|
96
|
+
value: task.source[task.key],
|
|
97
|
+
...task.target === void 0 ? {} : { destination: {
|
|
98
|
+
kind: "object",
|
|
99
|
+
target: task.target,
|
|
100
|
+
key: task.key
|
|
101
|
+
} }
|
|
102
|
+
});
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const current = task.value;
|
|
106
|
+
if (current === null) {
|
|
107
|
+
assign(task.destination, null);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (typeof current === "boolean" || typeof current === "string") {
|
|
111
|
+
assign(task.destination, current);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (typeof current === "number") {
|
|
115
|
+
if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
|
|
116
|
+
assign(task.destination, current);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (typeof current !== "object") return void 0;
|
|
120
|
+
if (ancestors.has(current)) return void 0;
|
|
121
|
+
if (Array.isArray(current)) {
|
|
122
|
+
if (!hasPlainArrayPrototype(current)) return void 0;
|
|
123
|
+
const length = current.length;
|
|
124
|
+
if (Reflect.ownKeys(current).length !== length + 1) return void 0;
|
|
125
|
+
const target = detach ? [] : void 0;
|
|
126
|
+
if (target !== void 0) assign(task.destination, target);
|
|
127
|
+
ancestors.add(current);
|
|
128
|
+
tasks.push({
|
|
129
|
+
kind: "leave",
|
|
130
|
+
source: current
|
|
131
|
+
});
|
|
132
|
+
for (let index = length - 1; index >= 0; index--) tasks.push({
|
|
133
|
+
kind: "array-item",
|
|
134
|
+
source: current,
|
|
135
|
+
index,
|
|
136
|
+
...target === void 0 ? {} : { target }
|
|
137
|
+
});
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (!hasPlainObjectPrototype(current)) return void 0;
|
|
141
|
+
const keys = enumerableStringKeys(current);
|
|
142
|
+
if (keys === void 0) return void 0;
|
|
143
|
+
const target = detach ? {} : void 0;
|
|
144
|
+
if (target !== void 0) assign(task.destination, target);
|
|
145
|
+
ancestors.add(current);
|
|
146
|
+
tasks.push({
|
|
147
|
+
kind: "leave",
|
|
148
|
+
source: current
|
|
149
|
+
});
|
|
150
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
151
|
+
const key = keys[index];
|
|
152
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
153
|
+
if (key === void 0) return void 0;
|
|
154
|
+
tasks.push({
|
|
155
|
+
kind: "object-property",
|
|
156
|
+
source: current,
|
|
157
|
+
key,
|
|
158
|
+
...target === void 0 ? {} : { target }
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return detach ? root : true;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Validate and detach lossless JSON in one read per property.
|
|
166
|
+
* @param value - candidate value to validate and detach.
|
|
167
|
+
* @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
|
|
168
|
+
*/
|
|
169
|
+
function snapshotJsonValue(value) {
|
|
170
|
+
return walkJsonValue(value, true);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Test the same lossless JSON rules as {@link snapshotJsonValue} without detaching the value.
|
|
174
|
+
* @param value - candidate value to test.
|
|
175
|
+
* @returns whether the value survives a JSON round trip without loss.
|
|
176
|
+
*/
|
|
177
|
+
function isJsonValue(value) {
|
|
178
|
+
return walkJsonValue(value, false) === true;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Compare JSON-compatible values structurally.
|
|
182
|
+
* @param a - one JSON-compatible value.
|
|
183
|
+
* @param b - the other JSON-compatible value.
|
|
184
|
+
* @returns whether both values contain the same JSON data.
|
|
185
|
+
*/
|
|
186
|
+
function deepEqualJson(a, b) {
|
|
187
|
+
if (a === b) return true;
|
|
188
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
189
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
190
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
191
|
+
return a.every((entry, index) => deepEqualJson(entry, b[index]));
|
|
192
|
+
}
|
|
193
|
+
const left = a;
|
|
194
|
+
const right = b;
|
|
195
|
+
const keys = Object.keys(left);
|
|
196
|
+
if (keys.length !== Object.keys(right).length) return false;
|
|
197
|
+
return keys.every((key) => key in right && deepEqualJson(left[key], right[key]));
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
|
|
201
|
+
* @param value - value to freeze.
|
|
202
|
+
* @returns the same value after every reachable enumerable child is frozen.
|
|
203
|
+
*/
|
|
204
|
+
function deepFreeze(value) {
|
|
205
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
206
|
+
const pending = [{
|
|
207
|
+
kind: "visit",
|
|
208
|
+
node: value
|
|
209
|
+
}];
|
|
210
|
+
while (pending.length > 0) {
|
|
211
|
+
const task = pending.pop();
|
|
212
|
+
/* v8 ignore next -- the loop condition guarantees one pending task. */
|
|
213
|
+
if (task === void 0) continue;
|
|
214
|
+
if (task.kind === "property") {
|
|
215
|
+
pending.push({
|
|
216
|
+
kind: "visit",
|
|
217
|
+
node: task.source[task.key]
|
|
218
|
+
});
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const node = task.node;
|
|
222
|
+
if (node === null || typeof node !== "object") continue;
|
|
223
|
+
if (node instanceof AbortSignal) continue;
|
|
224
|
+
if (seen.has(node)) continue;
|
|
225
|
+
seen.add(node);
|
|
226
|
+
Object.freeze(node);
|
|
227
|
+
const keys = Object.keys(node);
|
|
228
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
229
|
+
const key = keys[index];
|
|
230
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
231
|
+
if (key === void 0) continue;
|
|
232
|
+
pending.push({
|
|
233
|
+
kind: "property",
|
|
234
|
+
source: node,
|
|
235
|
+
key
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region ../../compaction/compaction-image-offload/src/project-message.ts
|
|
243
|
+
/**
|
|
244
|
+
* Project selected image occurrences to immutable offloaded blocks.
|
|
245
|
+
* @param message - message projected before this decision.
|
|
246
|
+
* @param indexes - nonempty, strictly increasing depth-first image indexes.
|
|
247
|
+
* @returns an immutable message with the same identity and selected images marked.
|
|
248
|
+
* @throws when a selected occurrence is missing or already offloaded.
|
|
249
|
+
*/
|
|
250
|
+
function offloadMessageImages(message, indexes) {
|
|
251
|
+
let imageIndex = 0;
|
|
252
|
+
let selected = 0;
|
|
253
|
+
const visit = (blocks) => {
|
|
254
|
+
let next;
|
|
255
|
+
for (const [index, block] of blocks.entries()) {
|
|
256
|
+
let projected = block;
|
|
257
|
+
if (block.type === "image") {
|
|
258
|
+
if (imageIndex === indexes[selected]) {
|
|
259
|
+
if (block.offloaded === true) throw new Error(`image/offload: image index ${imageIndex} is already offloaded`);
|
|
260
|
+
projected = {
|
|
261
|
+
...block,
|
|
262
|
+
offloaded: true
|
|
263
|
+
};
|
|
264
|
+
selected += 1;
|
|
265
|
+
}
|
|
266
|
+
imageIndex += 1;
|
|
267
|
+
} else if (block.type === "tool-result") {
|
|
268
|
+
const content = visit(block.content);
|
|
269
|
+
if (content !== block.content) projected = {
|
|
270
|
+
...block,
|
|
271
|
+
content
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
if (projected !== block) next ??= blocks.slice(0, index);
|
|
275
|
+
next?.push(projected);
|
|
276
|
+
}
|
|
277
|
+
return next ?? blocks;
|
|
278
|
+
};
|
|
279
|
+
const content = visit(message.content);
|
|
280
|
+
if (selected !== indexes.length) throw new Error(`image/offload: image index ${indexes[selected]} does not exist`);
|
|
281
|
+
return deepFreeze({
|
|
282
|
+
...message,
|
|
283
|
+
content
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region ../../compaction/compaction-image-offload/src/projection.ts
|
|
288
|
+
/** Whether a durable value is a JSON object. */
|
|
289
|
+
function isRecord$1(value) {
|
|
290
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
291
|
+
}
|
|
292
|
+
/** Whether a durable occurrence index or sequence is canonical. */
|
|
293
|
+
function isIndex(value) {
|
|
294
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 && !Object.is(value, -0);
|
|
295
|
+
}
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region ../session-format-catalog/src/message-projections.ts
|
|
298
|
+
/** Pure first-party message interpreters for detached current-format replay, including browser readers. */
|
|
299
|
+
/** Installed interpretation definitions; recovery listeners are mounted separately by their owning plugins. */
|
|
300
|
+
const currentSessionMessageProjections = [{
|
|
301
|
+
type: "image/offload",
|
|
302
|
+
project(event, context) {
|
|
303
|
+
const data = event.data;
|
|
304
|
+
if (!isRecord$1(data) || Object.keys(data).length !== 1 || !Array.isArray(data["targets"]) || data["targets"].length === 0) throw new Error("image/offload: data must contain a nonempty targets array");
|
|
305
|
+
const messages = /* @__PURE__ */ new Map();
|
|
306
|
+
const nodes = new Set(context.nodes);
|
|
307
|
+
for (const target of data["targets"]) {
|
|
308
|
+
if (!isRecord$1(target) || Object.keys(target).length !== 2 || !isIndex(target["seq"]) || !Array.isArray(target["imageIndexes"]) || target["imageIndexes"].length === 0) throw new Error("image/offload: each target must contain a seq and nonempty imageIndexes");
|
|
309
|
+
const seq = target["seq"];
|
|
310
|
+
if (messages.has(seq)) throw new Error(`image/offload: duplicate target seq ${seq}`);
|
|
311
|
+
if (!nodes.has(seq)) throw new Error(`image/offload: target seq ${seq} is not a current surface node`);
|
|
312
|
+
const source = context.events[seq - context.baseSeq];
|
|
313
|
+
if (source?.type !== "user/message" && source?.type !== "tool/result") throw new Error(`image/offload: target seq ${seq} must be user/message or tool/result`);
|
|
314
|
+
let previous = -1;
|
|
315
|
+
for (const index of target["imageIndexes"]) {
|
|
316
|
+
if (!isIndex(index) || index <= previous) throw new Error("image/offload: imageIndexes must be strictly increasing non-negative safe integers");
|
|
317
|
+
previous = index;
|
|
318
|
+
}
|
|
319
|
+
const message = context.messages.get(seq) ?? (source.type === "user/message" ? source.data : source.data.message);
|
|
320
|
+
messages.set(seq, offloadMessageImages(message, target["imageIndexes"]));
|
|
321
|
+
}
|
|
322
|
+
return messages;
|
|
323
|
+
}
|
|
324
|
+
}];
|
|
325
|
+
//#endregion
|
|
12
326
|
//#region ../../../vendor/cosmokit/src/misc.ts
|
|
13
327
|
/** Return true when a value is `null` or `undefined`. */
|
|
14
328
|
function isNullable(value) {
|
|
@@ -861,8 +1175,9 @@ var LoggerService = class LoggerService {
|
|
|
861
1175
|
*/
|
|
862
1176
|
exporter(exporter) {
|
|
863
1177
|
return this.ctx.effect(() => {
|
|
864
|
-
|
|
865
|
-
|
|
1178
|
+
const id = ++this._snExporter;
|
|
1179
|
+
this.exporters.set(id, exporter);
|
|
1180
|
+
return () => this.exporters.delete(id);
|
|
866
1181
|
}, "ctx.logger.exporter()");
|
|
867
1182
|
}
|
|
868
1183
|
_resolveConfig() {
|
|
@@ -1390,8 +1705,8 @@ var Fiber = class {
|
|
|
1390
1705
|
*
|
|
1391
1706
|
* @param config — the new raw config; validated before anything restarts.
|
|
1392
1707
|
* @param noSave — hint for persistence hooks not to write the change back.
|
|
1393
|
-
* @returns the
|
|
1394
|
-
* @throws when
|
|
1708
|
+
* @returns nothing; the restart runs behind the `internal/update` waterfall.
|
|
1709
|
+
* @throws {ValidationError} when the new config fails validation.
|
|
1395
1710
|
*/
|
|
1396
1711
|
update(config, noSave = false) {
|
|
1397
1712
|
this.assertActive();
|
|
@@ -1403,7 +1718,7 @@ var Fiber = class {
|
|
|
1403
1718
|
return;
|
|
1404
1719
|
}
|
|
1405
1720
|
config = this._resolveConfig(config);
|
|
1406
|
-
|
|
1721
|
+
this.context.waterfall(this, "internal/update", config, noSave, () => {
|
|
1407
1722
|
this.config = config;
|
|
1408
1723
|
this._error = void 0;
|
|
1409
1724
|
return this.restart();
|
|
@@ -2083,236 +2398,6 @@ function brandNumber(value) {
|
|
|
2083
2398
|
return value;
|
|
2084
2399
|
}
|
|
2085
2400
|
//#endregion
|
|
2086
|
-
//#region ../../util/values/src/index.ts
|
|
2087
|
-
/**
|
|
2088
|
-
* Mark an unreachable closed-union branch.
|
|
2089
|
-
* @param value - impossible value; an unhandled typed variant fails at the call site.
|
|
2090
|
-
* @param context - optional switch-site label included in the failure message.
|
|
2091
|
-
* @returns never; a runtime value that escaped its type always throws.
|
|
2092
|
-
*/
|
|
2093
|
-
function assertNever(value, context) {
|
|
2094
|
-
const rendered = JSON.stringify(value) ?? String(value);
|
|
2095
|
-
throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
|
|
2096
|
-
}
|
|
2097
|
-
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
2098
|
-
function hasIntrinsicConstructor(prototype, name) {
|
|
2099
|
-
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
2100
|
-
if (typeof constructor !== "function") return false;
|
|
2101
|
-
try {
|
|
2102
|
-
return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
|
|
2103
|
-
} catch {
|
|
2104
|
-
return false;
|
|
2105
|
-
}
|
|
2106
|
-
}
|
|
2107
|
-
/** Whether a candidate is one realm's intrinsic `Object.prototype`. */
|
|
2108
|
-
function isIntrinsicObjectPrototype(value) {
|
|
2109
|
-
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
|
|
2110
|
-
}
|
|
2111
|
-
/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
|
|
2112
|
-
function hasPlainArrayPrototype(value) {
|
|
2113
|
-
const prototype = Object.getPrototypeOf(value);
|
|
2114
|
-
if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
|
|
2115
|
-
const objectPrototype = Object.getPrototypeOf(prototype);
|
|
2116
|
-
return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);
|
|
2117
|
-
}
|
|
2118
|
-
/** Whether an object is a plain or null-prototype record from any JavaScript realm. */
|
|
2119
|
-
function hasPlainObjectPrototype(value) {
|
|
2120
|
-
const prototype = Object.getPrototypeOf(value);
|
|
2121
|
-
return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype(prototype);
|
|
2122
|
-
}
|
|
2123
|
-
/** Return every JSON-visible object key, or reject own data JSON would discard. */
|
|
2124
|
-
function enumerableStringKeys(value) {
|
|
2125
|
-
const keys = Reflect.ownKeys(value);
|
|
2126
|
-
if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
|
|
2127
|
-
return keys;
|
|
2128
|
-
}
|
|
2129
|
-
/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
|
|
2130
|
-
function walkJsonValue(value, detach) {
|
|
2131
|
-
const ancestors = /* @__PURE__ */ new Set();
|
|
2132
|
-
let root;
|
|
2133
|
-
const assign = (destination, item) => {
|
|
2134
|
-
if (destination === void 0) return;
|
|
2135
|
-
if (destination.kind === "root") root = item;
|
|
2136
|
-
else if (destination.kind === "array") destination.target[destination.index] = item;
|
|
2137
|
-
else Object.defineProperty(destination.target, destination.key, {
|
|
2138
|
-
value: item,
|
|
2139
|
-
enumerable: true,
|
|
2140
|
-
configurable: true,
|
|
2141
|
-
writable: true
|
|
2142
|
-
});
|
|
2143
|
-
};
|
|
2144
|
-
const tasks = [{
|
|
2145
|
-
kind: "visit",
|
|
2146
|
-
value,
|
|
2147
|
-
...detach ? { destination: { kind: "root" } } : {}
|
|
2148
|
-
}];
|
|
2149
|
-
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
2150
|
-
if (task.kind === "leave") {
|
|
2151
|
-
ancestors.delete(task.source);
|
|
2152
|
-
continue;
|
|
2153
|
-
}
|
|
2154
|
-
if (task.kind === "array-item") {
|
|
2155
|
-
if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
|
|
2156
|
-
tasks.push({
|
|
2157
|
-
kind: "visit",
|
|
2158
|
-
value: task.source[task.index],
|
|
2159
|
-
...task.target === void 0 ? {} : { destination: {
|
|
2160
|
-
kind: "array",
|
|
2161
|
-
target: task.target,
|
|
2162
|
-
index: task.index
|
|
2163
|
-
} }
|
|
2164
|
-
});
|
|
2165
|
-
continue;
|
|
2166
|
-
}
|
|
2167
|
-
if (task.kind === "object-property") {
|
|
2168
|
-
tasks.push({
|
|
2169
|
-
kind: "visit",
|
|
2170
|
-
value: task.source[task.key],
|
|
2171
|
-
...task.target === void 0 ? {} : { destination: {
|
|
2172
|
-
kind: "object",
|
|
2173
|
-
target: task.target,
|
|
2174
|
-
key: task.key
|
|
2175
|
-
} }
|
|
2176
|
-
});
|
|
2177
|
-
continue;
|
|
2178
|
-
}
|
|
2179
|
-
const current = task.value;
|
|
2180
|
-
if (current === null) {
|
|
2181
|
-
assign(task.destination, null);
|
|
2182
|
-
continue;
|
|
2183
|
-
}
|
|
2184
|
-
if (typeof current === "boolean" || typeof current === "string") {
|
|
2185
|
-
assign(task.destination, current);
|
|
2186
|
-
continue;
|
|
2187
|
-
}
|
|
2188
|
-
if (typeof current === "number") {
|
|
2189
|
-
if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
|
|
2190
|
-
assign(task.destination, current);
|
|
2191
|
-
continue;
|
|
2192
|
-
}
|
|
2193
|
-
if (typeof current !== "object") return void 0;
|
|
2194
|
-
if (ancestors.has(current)) return void 0;
|
|
2195
|
-
if (Array.isArray(current)) {
|
|
2196
|
-
if (!hasPlainArrayPrototype(current)) return void 0;
|
|
2197
|
-
const length = current.length;
|
|
2198
|
-
if (Reflect.ownKeys(current).length !== length + 1) return void 0;
|
|
2199
|
-
const target = detach ? [] : void 0;
|
|
2200
|
-
if (target !== void 0) assign(task.destination, target);
|
|
2201
|
-
ancestors.add(current);
|
|
2202
|
-
tasks.push({
|
|
2203
|
-
kind: "leave",
|
|
2204
|
-
source: current
|
|
2205
|
-
});
|
|
2206
|
-
for (let index = length - 1; index >= 0; index--) tasks.push({
|
|
2207
|
-
kind: "array-item",
|
|
2208
|
-
source: current,
|
|
2209
|
-
index,
|
|
2210
|
-
...target === void 0 ? {} : { target }
|
|
2211
|
-
});
|
|
2212
|
-
continue;
|
|
2213
|
-
}
|
|
2214
|
-
if (!hasPlainObjectPrototype(current)) return void 0;
|
|
2215
|
-
const keys = enumerableStringKeys(current);
|
|
2216
|
-
if (keys === void 0) return void 0;
|
|
2217
|
-
const target = detach ? {} : void 0;
|
|
2218
|
-
if (target !== void 0) assign(task.destination, target);
|
|
2219
|
-
ancestors.add(current);
|
|
2220
|
-
tasks.push({
|
|
2221
|
-
kind: "leave",
|
|
2222
|
-
source: current
|
|
2223
|
-
});
|
|
2224
|
-
for (let index = keys.length - 1; index >= 0; index--) {
|
|
2225
|
-
const key = keys[index];
|
|
2226
|
-
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
2227
|
-
if (key === void 0) return void 0;
|
|
2228
|
-
tasks.push({
|
|
2229
|
-
kind: "object-property",
|
|
2230
|
-
source: current,
|
|
2231
|
-
key,
|
|
2232
|
-
...target === void 0 ? {} : { target }
|
|
2233
|
-
});
|
|
2234
|
-
}
|
|
2235
|
-
}
|
|
2236
|
-
return detach ? root : true;
|
|
2237
|
-
}
|
|
2238
|
-
/**
|
|
2239
|
-
* Validate and detach lossless JSON in one read per property.
|
|
2240
|
-
* @param value - candidate value to validate and detach.
|
|
2241
|
-
* @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
|
|
2242
|
-
*/
|
|
2243
|
-
function snapshotJsonValue(value) {
|
|
2244
|
-
return walkJsonValue(value, true);
|
|
2245
|
-
}
|
|
2246
|
-
/**
|
|
2247
|
-
* Test the same lossless JSON rules as {@link snapshotJsonValue} without detaching the value.
|
|
2248
|
-
* @param value - candidate value to test.
|
|
2249
|
-
* @returns whether the value survives a JSON round trip without loss.
|
|
2250
|
-
*/
|
|
2251
|
-
function isJsonValue(value) {
|
|
2252
|
-
return walkJsonValue(value, false) === true;
|
|
2253
|
-
}
|
|
2254
|
-
/**
|
|
2255
|
-
* Compare JSON-compatible values structurally.
|
|
2256
|
-
* @param a - one JSON-compatible value.
|
|
2257
|
-
* @param b - the other JSON-compatible value.
|
|
2258
|
-
* @returns whether both values contain the same JSON data.
|
|
2259
|
-
*/
|
|
2260
|
-
function deepEqualJson(a, b) {
|
|
2261
|
-
if (a === b) return true;
|
|
2262
|
-
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
2263
|
-
if (Array.isArray(a) || Array.isArray(b)) {
|
|
2264
|
-
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
2265
|
-
return a.every((entry, index) => deepEqualJson(entry, b[index]));
|
|
2266
|
-
}
|
|
2267
|
-
const left = a;
|
|
2268
|
-
const right = b;
|
|
2269
|
-
const keys = Object.keys(left);
|
|
2270
|
-
if (keys.length !== Object.keys(right).length) return false;
|
|
2271
|
-
return keys.every((key) => key in right && deepEqualJson(left[key], right[key]));
|
|
2272
|
-
}
|
|
2273
|
-
/**
|
|
2274
|
-
* Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
|
|
2275
|
-
* @param value - value to freeze.
|
|
2276
|
-
* @returns the same value after every reachable enumerable child is frozen.
|
|
2277
|
-
*/
|
|
2278
|
-
function deepFreeze(value) {
|
|
2279
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
2280
|
-
const pending = [{
|
|
2281
|
-
kind: "visit",
|
|
2282
|
-
node: value
|
|
2283
|
-
}];
|
|
2284
|
-
while (pending.length > 0) {
|
|
2285
|
-
const task = pending.pop();
|
|
2286
|
-
/* v8 ignore next -- the loop condition guarantees one pending task. */
|
|
2287
|
-
if (task === void 0) continue;
|
|
2288
|
-
if (task.kind === "property") {
|
|
2289
|
-
pending.push({
|
|
2290
|
-
kind: "visit",
|
|
2291
|
-
node: task.source[task.key]
|
|
2292
|
-
});
|
|
2293
|
-
continue;
|
|
2294
|
-
}
|
|
2295
|
-
const node = task.node;
|
|
2296
|
-
if (node === null || typeof node !== "object") continue;
|
|
2297
|
-
if (node instanceof AbortSignal) continue;
|
|
2298
|
-
if (seen.has(node)) continue;
|
|
2299
|
-
seen.add(node);
|
|
2300
|
-
Object.freeze(node);
|
|
2301
|
-
const keys = Object.keys(node);
|
|
2302
|
-
for (let index = keys.length - 1; index >= 0; index--) {
|
|
2303
|
-
const key = keys[index];
|
|
2304
|
-
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
2305
|
-
if (key === void 0) continue;
|
|
2306
|
-
pending.push({
|
|
2307
|
-
kind: "property",
|
|
2308
|
-
source: node,
|
|
2309
|
-
key
|
|
2310
|
-
});
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
return value;
|
|
2314
|
-
}
|
|
2315
|
-
//#endregion
|
|
2316
2401
|
//#region ../../core/session/src/types.ts
|
|
2317
2402
|
/**
|
|
2318
2403
|
* Brand a string as a {@link SessionId}.
|
|
@@ -2383,6 +2468,7 @@ const KNOWN_SESSION_EVENT_TYPES = new Set([
|
|
|
2383
2468
|
"goal/change",
|
|
2384
2469
|
"hook/invoked",
|
|
2385
2470
|
"hook/result",
|
|
2471
|
+
"image/offload",
|
|
2386
2472
|
"llm/retry",
|
|
2387
2473
|
"llm/retry-started",
|
|
2388
2474
|
"model/selection",
|
|
@@ -2418,8 +2504,11 @@ const KNOWN_SESSION_EVENT_TYPES = new Set([
|
|
|
2418
2504
|
"turn/end",
|
|
2419
2505
|
"turn/start",
|
|
2420
2506
|
"user/message",
|
|
2421
|
-
"web/deepseek-search-llm-request"
|
|
2507
|
+
"web/deepseek-search-llm-request",
|
|
2508
|
+
"workspace/changes"
|
|
2422
2509
|
]);
|
|
2510
|
+
/** Event types whose model-visible effects require an explicit pure interpreter. */
|
|
2511
|
+
const MESSAGE_PROJECTION_EVENT_TYPES = new Set(["image/offload"]);
|
|
2423
2512
|
//#endregion
|
|
2424
2513
|
//#region ../../core/session/src/surface.ts
|
|
2425
2514
|
/** Runtime counterpart of the message-producing event union. */
|
|
@@ -2440,17 +2529,18 @@ function isSurfaceEligibleType(type) {
|
|
|
2440
2529
|
/**
|
|
2441
2530
|
* Project a single event into the LLM message it derives to, or null when it
|
|
2442
2531
|
* produces none — a non-surface event (attempt, boundary, log-only record) or an
|
|
2443
|
-
* empty-content assistant/message (which exists only to host usage).
|
|
2444
|
-
*
|
|
2445
|
-
*
|
|
2446
|
-
*
|
|
2447
|
-
*
|
|
2448
|
-
* nested in the event wrapper and shared by delivery, durable history, and
|
|
2449
|
-
* model requests.
|
|
2532
|
+
* empty-content assistant/message (which exists only to host usage). A caller
|
|
2533
|
+
* reconstructing model input supplies the same prefix's `projectedMessages`
|
|
2534
|
+
* from {@link foldSurface}; without that map this function reads original
|
|
2535
|
+
* event content. Session instance methods apply the live projection. Messages
|
|
2536
|
+
* are immutable and unchanged content retains its durable identity.
|
|
2450
2537
|
* @param event - the event to project.
|
|
2538
|
+
* @param projectedMessages - message projections from the same log prefix's surface fold.
|
|
2451
2539
|
* @returns the derived message, or null when the event produces none.
|
|
2452
2540
|
*/
|
|
2453
|
-
function deriveEventMessage(event) {
|
|
2541
|
+
function deriveEventMessage(event, projectedMessages) {
|
|
2542
|
+
const projected = projectedMessages?.get(event.seq);
|
|
2543
|
+
if (projected !== void 0) return projected;
|
|
2454
2544
|
switch (event.type) {
|
|
2455
2545
|
case "user/message": return event.data;
|
|
2456
2546
|
case "system/message":
|
|
@@ -2495,7 +2585,10 @@ function validateSessionEventData(event, subject) {
|
|
|
2495
2585
|
function createFoldState() {
|
|
2496
2586
|
return {
|
|
2497
2587
|
nodes: [],
|
|
2498
|
-
replaceGeneration: 0
|
|
2588
|
+
replaceGeneration: 0,
|
|
2589
|
+
contentGeneration: 0,
|
|
2590
|
+
projectedMessages: /* @__PURE__ */ new Map(),
|
|
2591
|
+
projections: /* @__PURE__ */ new Set()
|
|
2499
2592
|
};
|
|
2500
2593
|
}
|
|
2501
2594
|
/** Whether a runtime value is a non-negative safe event sequence. */
|
|
@@ -2524,7 +2617,7 @@ function surfaceOpOf(event) {
|
|
|
2524
2617
|
return op;
|
|
2525
2618
|
}
|
|
2526
2619
|
/** Validate cited source-event seqs against prior log entries and the replacement range. */
|
|
2527
|
-
function
|
|
2620
|
+
function assertSourceEventReferences(event, shadowedSeqs) {
|
|
2528
2621
|
const raw = event.sourceEventSeqs;
|
|
2529
2622
|
if (event.type === "assistant/message" && raw !== void 0) throw new Error("assistant/message embeds its source stream and cannot carry sourceEventSeqs");
|
|
2530
2623
|
const sources = /* @__PURE__ */ new Set();
|
|
@@ -2553,7 +2646,7 @@ function assertProvenance(event, shadowedSeqs) {
|
|
|
2553
2646
|
function validateSurfaceMetadata(event) {
|
|
2554
2647
|
const op = surfaceOpOf(event);
|
|
2555
2648
|
if (op !== void 0 && op !== "append" && (op.startSeq >= event.seq || op.endSeq >= event.seq)) throw new Error(`surface replace at seq ${event.seq}: startSeq and endSeq must reference earlier events`);
|
|
2556
|
-
if (op !== void 0)
|
|
2649
|
+
if (op !== void 0) assertSourceEventReferences(event, []);
|
|
2557
2650
|
return op;
|
|
2558
2651
|
}
|
|
2559
2652
|
/** Locate one replacement range without mutating the current fold state. */
|
|
@@ -2626,16 +2719,28 @@ function assertSystemHeadRewrite(event, state, startIdx, shadowedSeqs, events, b
|
|
|
2626
2719
|
if (event.type !== "system/message" || shadowedSeqs.length !== 1) throw new Error("surface replace: node 0 holds the system prompt and may be rewritten only by a system/message over exactly that node");
|
|
2627
2720
|
}
|
|
2628
2721
|
/** Validate one event at its replay boundary and prepare its atomic fold transition. */
|
|
2629
|
-
function planSurfaceEvent(state, event, expectedSeq, events, baseSeq) {
|
|
2722
|
+
function planSurfaceEvent(state, event, expectedSeq, events, baseSeq, projections) {
|
|
2630
2723
|
if (event.seq !== expectedSeq) throw new Error(`session event seq ${event.seq} is not contiguous; expected ${expectedSeq}`);
|
|
2631
2724
|
const surfaceOp = validateSurfaceMetadata(event);
|
|
2725
|
+
const projection = projections.find((item) => item.type === event.type);
|
|
2726
|
+
if (projection !== void 0) return {
|
|
2727
|
+
kind: "project",
|
|
2728
|
+
projection,
|
|
2729
|
+
messages: projection.project(event, {
|
|
2730
|
+
nodes: state.nodes,
|
|
2731
|
+
events,
|
|
2732
|
+
baseSeq,
|
|
2733
|
+
messages: state.projectedMessages
|
|
2734
|
+
})
|
|
2735
|
+
};
|
|
2736
|
+
if (MESSAGE_PROJECTION_EVENT_TYPES.has(event.type)) throw new Error(`session event "${event.type}" requires a message projection; load its owning plugin or supply its projection definition`);
|
|
2632
2737
|
if (surfaceOp === void 0) return;
|
|
2633
2738
|
if (surfaceOp === "append") return {
|
|
2634
2739
|
kind: "append",
|
|
2635
2740
|
seq: event.seq
|
|
2636
2741
|
};
|
|
2637
2742
|
const range = replacementRange(state, surfaceOp);
|
|
2638
|
-
|
|
2743
|
+
assertSourceEventReferences(event, range.shadowedSeqs);
|
|
2639
2744
|
assertToolResultRewrite(event, range.shadowedSeqs, events, baseSeq);
|
|
2640
2745
|
assertSystemHeadRewrite(event, state, range.startIdx, range.shadowedSeqs, events, baseSeq);
|
|
2641
2746
|
return {
|
|
@@ -2647,8 +2752,8 @@ function planSurfaceEvent(state, event, expectedSeq, events, baseSeq) {
|
|
|
2647
2752
|
};
|
|
2648
2753
|
}
|
|
2649
2754
|
/** Apply one event and return replacement metadata only when one occurred. */
|
|
2650
|
-
function applySurfaceEvent(state, event, expectedSeq, events, baseSeq) {
|
|
2651
|
-
return applySurfacePlan(state, planSurfaceEvent(state, event, expectedSeq, events, baseSeq));
|
|
2755
|
+
function applySurfaceEvent(state, event, expectedSeq, events, baseSeq, projections) {
|
|
2756
|
+
return applySurfacePlan(state, planSurfaceEvent(state, event, expectedSeq, events, baseSeq, projections));
|
|
2652
2757
|
}
|
|
2653
2758
|
/** Commit one previously validated surface transition. */
|
|
2654
2759
|
function applySurfacePlan(state, plan) {
|
|
@@ -2656,6 +2761,11 @@ function applySurfacePlan(state, plan) {
|
|
|
2656
2761
|
else if (plan?.kind === "replace") {
|
|
2657
2762
|
state.nodes.splice(plan.startIdx, plan.endIdx - plan.startIdx + 1, plan.seq);
|
|
2658
2763
|
state.replaceGeneration += 1;
|
|
2764
|
+
state.contentGeneration += 1;
|
|
2765
|
+
} else if (plan?.kind === "project") {
|
|
2766
|
+
for (const [seq, message] of plan.messages) state.projectedMessages.set(seq, message);
|
|
2767
|
+
state.projections.add(plan.projection);
|
|
2768
|
+
state.contentGeneration += 1;
|
|
2659
2769
|
}
|
|
2660
2770
|
if (plan?.kind !== "replace") return;
|
|
2661
2771
|
return {
|
|
@@ -2669,6 +2779,7 @@ function applySurfacePlan(state, plan) {
|
|
|
2669
2779
|
var SurfaceManager = class {
|
|
2670
2780
|
log;
|
|
2671
2781
|
baseSeq;
|
|
2782
|
+
projections;
|
|
2672
2783
|
/** Shared transition state; replacement history is not retained. */
|
|
2673
2784
|
_state = createFoldState();
|
|
2674
2785
|
/** Last processed absolute seq. */
|
|
@@ -2678,10 +2789,12 @@ var SurfaceManager = class {
|
|
|
2678
2789
|
/**
|
|
2679
2790
|
* @param log - Contiguous complete log or loaded event window.
|
|
2680
2791
|
* @param baseSeq - Absolute sequence of the window's first event.
|
|
2792
|
+
* @param projections - live borrowed definitions; removing a used definition invalidates further reads.
|
|
2681
2793
|
*/
|
|
2682
|
-
constructor(log, baseSeq = SessionLogOffset(0)) {
|
|
2794
|
+
constructor(log, baseSeq = SessionLogOffset(0), projections = []) {
|
|
2683
2795
|
this.log = log;
|
|
2684
2796
|
this.baseSeq = baseSeq;
|
|
2797
|
+
this.projections = projections;
|
|
2685
2798
|
this._lastProcessedSeq = baseSeq === 0 ? -1 : SessionSeq(baseSeq - 1);
|
|
2686
2799
|
}
|
|
2687
2800
|
/**
|
|
@@ -2689,21 +2802,40 @@ var SurfaceManager = class {
|
|
|
2689
2802
|
* @param event - candidate event that has not entered the log yet.
|
|
2690
2803
|
*/
|
|
2691
2804
|
validateNext(event) {
|
|
2805
|
+
this._assertProjections();
|
|
2692
2806
|
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta();
|
|
2693
2807
|
const expectedSeq = SessionSeq(this.baseSeq + this.log.length);
|
|
2694
2808
|
this._pendingPlan = {
|
|
2695
2809
|
event,
|
|
2696
2810
|
expectedSeq,
|
|
2697
|
-
plan: planSurfaceEvent(this._state, event, expectedSeq, this.log, this.baseSeq)
|
|
2811
|
+
plan: planSurfaceEvent(this._state, event, expectedSeq, this.log, this.baseSeq, this.projections)
|
|
2698
2812
|
};
|
|
2699
2813
|
}
|
|
2700
2814
|
/** Monotonic count of folded positional replacements. */
|
|
2701
2815
|
get replaceGeneration() {
|
|
2816
|
+
this._assertProjections();
|
|
2702
2817
|
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta();
|
|
2703
2818
|
return this._state.replaceGeneration;
|
|
2704
2819
|
}
|
|
2820
|
+
/** Monotonic count of committed changes to existing model-visible content. */
|
|
2821
|
+
get contentGeneration() {
|
|
2822
|
+
this._assertProjections();
|
|
2823
|
+
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta();
|
|
2824
|
+
return this._state.contentGeneration;
|
|
2825
|
+
}
|
|
2826
|
+
/**
|
|
2827
|
+
* Project one message with every committed message projection applied.
|
|
2828
|
+
* @param event - message-producing or log-only event.
|
|
2829
|
+
* @returns its immutable projected message, or null when it produces none.
|
|
2830
|
+
*/
|
|
2831
|
+
deriveEventMessage(event) {
|
|
2832
|
+
this._assertProjections();
|
|
2833
|
+
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta();
|
|
2834
|
+
return deriveEventMessage(event, this._state.projectedMessages);
|
|
2835
|
+
}
|
|
2705
2836
|
/** Surface event sequences in model-visible order. */
|
|
2706
2837
|
get nodes() {
|
|
2838
|
+
this._assertProjections();
|
|
2707
2839
|
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta();
|
|
2708
2840
|
return this._state.nodes;
|
|
2709
2841
|
}
|
|
@@ -2715,11 +2847,18 @@ var SurfaceManager = class {
|
|
|
2715
2847
|
const event = this.log[index];
|
|
2716
2848
|
const pending = this._pendingPlan;
|
|
2717
2849
|
if (pending?.event === event && pending.expectedSeq === seq) applySurfacePlan(this._state, pending.plan);
|
|
2718
|
-
else applySurfaceEvent(this._state, event, SessionSeq(seq), this.log, this.baseSeq);
|
|
2850
|
+
else applySurfaceEvent(this._state, event, SessionSeq(seq), this.log, this.baseSeq, this.projections);
|
|
2719
2851
|
if (pending !== void 0 && pending.expectedSeq <= seq) this._pendingPlan = void 0;
|
|
2720
2852
|
this._lastProcessedSeq = SessionSeq(seq);
|
|
2721
2853
|
}
|
|
2722
2854
|
}
|
|
2855
|
+
/** Cached messages cannot outlive the definitions that interpreted their log. */
|
|
2856
|
+
_assertProjections() {
|
|
2857
|
+
const candidate = this._pendingPlan;
|
|
2858
|
+
const pending = candidate !== void 0 && this.log[candidate.expectedSeq - this.baseSeq] === candidate.event ? candidate.plan : void 0;
|
|
2859
|
+
const required = pending?.kind === "project" ? [...this._state.projections, pending.projection] : this._state.projections;
|
|
2860
|
+
for (const projection of required) if (!this.projections.includes(projection)) throw new Error(`session message projection "${projection.type}" was removed or replaced; restore the session with its owning plugin`);
|
|
2861
|
+
}
|
|
2723
2862
|
};
|
|
2724
2863
|
//#endregion
|
|
2725
2864
|
//#region ../../typert/protocol/src/remote-error.ts
|
|
@@ -3719,13 +3858,15 @@ function failureSnapshot(value) {
|
|
|
3719
3858
|
const status = candidate.status;
|
|
3720
3859
|
const providerRetryAfterMs = candidate.providerRetryAfterMs;
|
|
3721
3860
|
const requestId = candidate.requestId;
|
|
3722
|
-
|
|
3861
|
+
const offloadImages = candidate.offloadImages;
|
|
3862
|
+
if (typeof message !== "string" || message.length === 0 || typeof code !== "string" || code.length === 0 || status !== void 0 && (!Number.isInteger(status) || status < 100 || status > 599) || providerRetryAfterMs !== void 0 && (!Number.isFinite(providerRetryAfterMs) || providerRetryAfterMs <= 0) || requestId !== void 0 && (typeof requestId !== "string" || requestId.length === 0) || offloadImages !== void 0 && (!Number.isSafeInteger(offloadImages) || offloadImages <= 0)) return void 0;
|
|
3723
3863
|
return Object.freeze({
|
|
3724
3864
|
message,
|
|
3725
3865
|
code,
|
|
3726
3866
|
...status === void 0 ? {} : { status },
|
|
3727
3867
|
...providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs },
|
|
3728
|
-
...requestId === void 0 ? {} : { requestId }
|
|
3868
|
+
...requestId === void 0 ? {} : { requestId },
|
|
3869
|
+
...offloadImages === void 0 ? {} : { offloadImages }
|
|
3729
3870
|
});
|
|
3730
3871
|
} catch (_sdkFailureGetter) {
|
|
3731
3872
|
return;
|
|
@@ -4414,7 +4555,8 @@ var LlmError = class extends HarnessError {
|
|
|
4414
4555
|
code,
|
|
4415
4556
|
...options?.status === void 0 ? {} : { status: options.status },
|
|
4416
4557
|
...options?.providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs: options.providerRetryAfterMs },
|
|
4417
|
-
...options?.requestId === void 0 ? {} : { requestId: options.requestId }
|
|
4558
|
+
...options?.requestId === void 0 ? {} : { requestId: options.requestId },
|
|
4559
|
+
...options?.offloadImages === void 0 ? {} : { offloadImages: options.offloadImages }
|
|
4418
4560
|
});
|
|
4419
4561
|
}
|
|
4420
4562
|
};
|
|
@@ -4682,7 +4824,8 @@ var LlmError = class extends HarnessError {
|
|
|
4682
4824
|
id: model.id,
|
|
4683
4825
|
...model.name === void 0 ? {} : { name: model.name },
|
|
4684
4826
|
...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
|
|
4685
|
-
...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens }
|
|
4827
|
+
...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens },
|
|
4828
|
+
...model.inputModalities === void 0 ? {} : { inputModalities: [...model.inputModalities] }
|
|
4686
4829
|
});
|
|
4687
4830
|
}
|
|
4688
4831
|
return models;
|
|
@@ -5291,7 +5434,7 @@ const attachments = /* @__PURE__ */ new WeakMap();
|
|
|
5291
5434
|
var Session = class Session {
|
|
5292
5435
|
log = [];
|
|
5293
5436
|
/** Single incremental owner of surface acceptance and projection state. */
|
|
5294
|
-
surfaceManager
|
|
5437
|
+
surfaceManager;
|
|
5295
5438
|
/** The ordered surface over this session's event log. */
|
|
5296
5439
|
get surface() {
|
|
5297
5440
|
return this.surfaceManager;
|
|
@@ -5342,10 +5485,12 @@ var Session = class Session {
|
|
|
5342
5485
|
* @param seed - optional borrowed replay or fork events.
|
|
5343
5486
|
* @param header - optional borrowed storage metadata.
|
|
5344
5487
|
* @param inheritedEventCount - exact fork-inherited prefix length for a seeded header.
|
|
5488
|
+
* @param projections - pure interpreters for plugin-owned message changes.
|
|
5345
5489
|
* @returns a detached session.
|
|
5490
|
+
* @throws when a seed event requires a missing message interpreter or fails validation.
|
|
5346
5491
|
*/
|
|
5347
|
-
static create(id, seed, header, inheritedEventCount) {
|
|
5348
|
-
return new Session(id, seed, header, "snapshot", inheritedEventCount);
|
|
5492
|
+
static create(id, seed, header, inheritedEventCount, projections) {
|
|
5493
|
+
return new Session(id, seed, header, "snapshot", inheritedEventCount, projections);
|
|
5349
5494
|
}
|
|
5350
5495
|
/**
|
|
5351
5496
|
* Restore a detached session by adopting an independently owned or deeply frozen seed.
|
|
@@ -5358,12 +5503,15 @@ var Session = class Session {
|
|
|
5358
5503
|
* @param header - independently owned storage metadata.
|
|
5359
5504
|
* @param inheritedEventCount - exact fork-inherited prefix length decoded from storage.
|
|
5360
5505
|
* @param eventState - aliasing state carried from the operation that produced the seed.
|
|
5506
|
+
* @param projections - pure interpreters for plugin-owned message changes.
|
|
5361
5507
|
* @returns a restored detached session.
|
|
5508
|
+
* @throws when a seed event requires a missing message interpreter or fails validation.
|
|
5362
5509
|
*/
|
|
5363
|
-
static fromRestore(id, seed, header, inheritedEventCount, eventState) {
|
|
5364
|
-
return new Session(id, seed, header, eventState, inheritedEventCount);
|
|
5510
|
+
static fromRestore(id, seed, header, inheritedEventCount, eventState, projections) {
|
|
5511
|
+
return new Session(id, seed, header, eventState, inheritedEventCount, projections);
|
|
5365
5512
|
}
|
|
5366
|
-
constructor(id, seed, header, mode = "snapshot", suppliedInheritedEventCount) {
|
|
5513
|
+
constructor(id, seed, header, mode = "snapshot", suppliedInheritedEventCount, projections = []) {
|
|
5514
|
+
this.surfaceManager = new SurfaceManager(this.log, SessionLogOffset(0), projections);
|
|
5367
5515
|
const restoredHeader = mode === "snapshot" ? void 0 : validateRestoredSessionHeader(id, header);
|
|
5368
5516
|
if (seed !== void 0) for (const [index, source] of seed.entries()) {
|
|
5369
5517
|
const snapshot = mode === "snapshot" ? snapshotJsonValue(source) : source;
|
|
@@ -5393,6 +5541,8 @@ var Session = class Session {
|
|
|
5393
5541
|
eventsSnapshot;
|
|
5394
5542
|
/**
|
|
5395
5543
|
* Return the immutable event stored at one exact sequence number.
|
|
5544
|
+
* @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
|
|
5545
|
+
* See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
|
|
5396
5546
|
* @param seq - event sequence number.
|
|
5397
5547
|
* @returns the accepted event, or undefined when the log does not contain it.
|
|
5398
5548
|
*/
|
|
@@ -5403,6 +5553,8 @@ var Session = class Session {
|
|
|
5403
5553
|
* Materialize an immutable snapshot of a half-open event sequence range.
|
|
5404
5554
|
* A full current snapshot is reused until the next append; every previously
|
|
5405
5555
|
* returned snapshot remains stable after later appends.
|
|
5556
|
+
* @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
|
|
5557
|
+
* See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
|
|
5406
5558
|
* @param fromSeq - non-negative inclusive sequence number; defaults to the log start.
|
|
5407
5559
|
* @param toSeqExclusive - non-negative exclusive sequence number; defaults to the current end.
|
|
5408
5560
|
* @returns a frozen array of the selected deeply frozen events.
|
|
@@ -5416,6 +5568,8 @@ var Session = class Session {
|
|
|
5416
5568
|
}
|
|
5417
5569
|
/**
|
|
5418
5570
|
* Return this Session's events after its fork-inherited prefix.
|
|
5571
|
+
* @deprecated Existing logic may remain unmigrated for now, but new calls are prohibited.
|
|
5572
|
+
* See the [Agent Note](../../../../.agents/notes/implemented/architecture/2026-09-09-deprecate-synchronous-session-event-reads.md).
|
|
5419
5573
|
* @returns a fresh array containing child-owned events in log order.
|
|
5420
5574
|
*/
|
|
5421
5575
|
ownEvents() {
|
|
@@ -5549,7 +5703,7 @@ var Session = class Session {
|
|
|
5549
5703
|
derived = [];
|
|
5550
5704
|
/** Surface position (nodes projected) the cache has reached. */
|
|
5551
5705
|
derivedNodes = 0;
|
|
5552
|
-
/** {@link SurfaceManager.
|
|
5706
|
+
/** {@link SurfaceManager.contentGeneration} the cache was built under. */
|
|
5553
5707
|
derivedGeneration = 0;
|
|
5554
5708
|
/**
|
|
5555
5709
|
* Derive the LLM message history by walking the ordered sequences of
|
|
@@ -5558,21 +5712,21 @@ var Session = class Session {
|
|
|
5558
5712
|
* append records its `surfaceOp`, so a raw event with no marker (a chunk, a
|
|
5559
5713
|
* turn boundary) is correctly absent, and a compaction `replace` deletes the
|
|
5560
5714
|
* shadowed nodes from the derivation. The projection rules are
|
|
5561
|
-
* {@link deriveEventMessage},
|
|
5715
|
+
* {@link deriveEventMessage}, with logged message projections applied
|
|
5716
|
+
* without changing node membership or message identity.
|
|
5562
5717
|
*
|
|
5563
|
-
* CACHED:
|
|
5564
|
-
*
|
|
5565
|
-
* {@link SessionSurface.replaceGeneration}) rebuilds. The returned array is
|
|
5718
|
+
* CACHED: pure tail growth costs O(new nodes); a replacement or message projection
|
|
5719
|
+
* ({@link SessionSurface.contentGeneration}) rebuilds. The returned array is
|
|
5566
5720
|
* a fresh snapshot per call (later appends never grow an array a caller
|
|
5567
5721
|
* already holds); the `Message` objects in it are SHARED and **deep-frozen**.
|
|
5568
|
-
*
|
|
5569
|
-
*
|
|
5722
|
+
* Unchanged content reuses frozen event data; projected blocks are frozen
|
|
5723
|
+
* derived copies. Consumers cannot mutate the log through either form.
|
|
5570
5724
|
* @returns a fresh array of the shared, frozen derived history.
|
|
5571
5725
|
*/
|
|
5572
5726
|
deriveMessages() {
|
|
5573
5727
|
const surface = this.surface;
|
|
5574
5728
|
const nodes = surface.nodes;
|
|
5575
|
-
const generation = surface.
|
|
5729
|
+
const generation = surface.contentGeneration;
|
|
5576
5730
|
if (generation !== this.derivedGeneration) {
|
|
5577
5731
|
this.derived = [];
|
|
5578
5732
|
this.derivedNodes = 0;
|
|
@@ -5586,13 +5740,13 @@ var Session = class Session {
|
|
|
5586
5740
|
return [...this.derived];
|
|
5587
5741
|
}
|
|
5588
5742
|
/**
|
|
5589
|
-
*
|
|
5590
|
-
*
|
|
5743
|
+
* Project one event with all committed message projections applied.
|
|
5744
|
+
* The original durable event remains unchanged.
|
|
5591
5745
|
* @param event - the event to project.
|
|
5592
5746
|
* @returns the derived message, or null when the event produces none.
|
|
5593
5747
|
*/
|
|
5594
5748
|
deriveEventMessage(event) {
|
|
5595
|
-
return deriveEventMessage(event);
|
|
5749
|
+
return this.surfaceManager.deriveEventMessage(event);
|
|
5596
5750
|
}
|
|
5597
5751
|
};
|
|
5598
5752
|
//#endregion
|
|
@@ -6183,7 +6337,7 @@ function validateInstalledCurrentSessionHeader(header) {
|
|
|
6183
6337
|
*/
|
|
6184
6338
|
function validateInstalledCurrentSessionArtifact(artifact) {
|
|
6185
6339
|
if (artifact.header.version !== 3) throw new Error(`installed Session format is v3, got v${artifact.header.version}`);
|
|
6186
|
-
Session.fromRestore(SessionId(artifact.header.id), artifact.events, artifact.header, SessionLogOffset(artifact.inheritedEventCount), "detached");
|
|
6340
|
+
Session.fromRestore(SessionId(artifact.header.id), artifact.events, artifact.header, SessionLogOffset(artifact.inheritedEventCount), "detached", currentSessionMessageProjections);
|
|
6187
6341
|
}
|
|
6188
6342
|
//#endregion
|
|
6189
6343
|
//#region ../session-format-v0-to-v1/src/dispositions.ts
|
|
@@ -7657,11 +7811,11 @@ function assertReleasedV1Header(header) {
|
|
|
7657
7811
|
* @param record - exact event envelope.
|
|
7658
7812
|
* @param seq - event position used for earlier-reference checks.
|
|
7659
7813
|
* @param type - surface event type used in diagnostics.
|
|
7660
|
-
* @param assistantSources - whether this generation admits empty Assistant chunk
|
|
7814
|
+
* @param assistantSources - whether this generation admits empty Assistant chunk references.
|
|
7661
7815
|
*/
|
|
7662
7816
|
function assertReleasedSurfaceMetadata(record, seq, type, assistantSources) {
|
|
7663
7817
|
const sources = record["sourceEventSeqs"];
|
|
7664
|
-
if (type === "assistant/message" && sources !== void 0 && assistantSources === "forbid-assistant") throw new SessionFormatError(`assistant/message ${seq} retains obsolete chunk
|
|
7818
|
+
if (type === "assistant/message" && sources !== void 0 && assistantSources === "forbid-assistant") throw new SessionFormatError(`assistant/message ${seq} retains obsolete chunk references`);
|
|
7665
7819
|
if (sources !== void 0) {
|
|
7666
7820
|
if (!Array.isArray(sources)) throw new SessionFormatError(`${type} ${seq} sourceEventSeqs must be an array`);
|
|
7667
7821
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7977,6 +8131,7 @@ function decodeSeqRanges$1(value, maxEntries) {
|
|
|
7977
8131
|
}
|
|
7978
8132
|
//#endregion
|
|
7979
8133
|
//#region ../session-format-v0-to-v1/src/migration.ts
|
|
8134
|
+
const LEGACY_ASSISTANT_SOURCE_KEY = ["pro", "venance"].join("");
|
|
7980
8135
|
/** Identity format edge that promotes released v0 into released v1. */
|
|
7981
8136
|
const sessionFormatV0ToV1 = defineSessionFormatMigration({
|
|
7982
8137
|
name: "@deepseek-ai/dsh-session-format-v0-to-v1",
|
|
@@ -8300,9 +8455,12 @@ function normalizeLegacyMessage(event, sessionId, messageIds) {
|
|
|
8300
8455
|
}
|
|
8301
8456
|
};
|
|
8302
8457
|
case "assistant/message": {
|
|
8303
|
-
if (Object.hasOwn(data, "message") || !Object.hasOwn(data, "content") || !Object.hasOwn(data,
|
|
8304
|
-
const
|
|
8305
|
-
const
|
|
8458
|
+
if (Object.hasOwn(data, "message") || !Object.hasOwn(data, "content") || !Object.hasOwn(data, LEGACY_ASSISTANT_SOURCE_KEY)) return event;
|
|
8459
|
+
const content = data["content"];
|
|
8460
|
+
const eventData = { ...data };
|
|
8461
|
+
delete eventData["content"];
|
|
8462
|
+
const source = releasedV0Record(eventData[LEGACY_ASSISTANT_SOURCE_KEY], `assistant/message ${event.seq} legacy source`);
|
|
8463
|
+
Reflect.deleteProperty(eventData, LEGACY_ASSISTANT_SOURCE_KEY);
|
|
8306
8464
|
return {
|
|
8307
8465
|
...event,
|
|
8308
8466
|
data: {
|
|
@@ -8878,7 +9036,7 @@ const releasedV2SessionFormatCodec = Object.freeze({
|
|
|
8878
9036
|
return encodeHeader(header, inheritedEventCount);
|
|
8879
9037
|
},
|
|
8880
9038
|
encodeEvent(event) {
|
|
8881
|
-
return
|
|
9039
|
+
return encodeSourceEventRanges(event);
|
|
8882
9040
|
}
|
|
8883
9041
|
});
|
|
8884
9042
|
function decodePhysicalHeader(value) {
|
|
@@ -8986,7 +9144,7 @@ function encodeHeader(header, inheritedEventCount) {
|
|
|
8986
9144
|
...header.agentPreset === void 0 ? {} : { agentPreset: header.agentPreset }
|
|
8987
9145
|
};
|
|
8988
9146
|
}
|
|
8989
|
-
function
|
|
9147
|
+
function encodeSourceEventRanges(event) {
|
|
8990
9148
|
if (event.sourceEventSeqs === void 0) return event;
|
|
8991
9149
|
return {
|
|
8992
9150
|
...event,
|
|
@@ -9229,7 +9387,7 @@ function transformMessage(state, event, context) {
|
|
|
9229
9387
|
emitSource(state, messageEvent(event, attemptGroup(turn, step)), context);
|
|
9230
9388
|
return;
|
|
9231
9389
|
}
|
|
9232
|
-
if (pending === void 0 || !matchesChunkSources(pending.group, sources)) throw refusal(`assistant/message ${event.seq} chunk
|
|
9390
|
+
if (pending === void 0 || !matchesChunkSources(pending.group, sources)) throw refusal(`assistant/message ${event.seq} chunk references are not one complete ordered attempt`);
|
|
9233
9391
|
assertAttemptCut(state, pending.group, event.seq);
|
|
9234
9392
|
pending.group.terminal = true;
|
|
9235
9393
|
flushBuffered(state, pending, context);
|
|
@@ -10053,7 +10211,7 @@ function relationshipEvent(event) {
|
|
|
10053
10211
|
//#endregion
|
|
10054
10212
|
//#region ../session-format-v2-to-v3/src/codec.ts
|
|
10055
10213
|
/** V3 framing with hard structural admission and recoverable canonical event validation. */
|
|
10056
|
-
/** V3 codec validates structural rows before recovery and logical envelopes after
|
|
10214
|
+
/** V3 codec validates structural rows before recovery and logical envelopes after source-event range decoding. */
|
|
10057
10215
|
const releasedV3SessionFormatCodec = Object.freeze({
|
|
10058
10216
|
version: 3,
|
|
10059
10217
|
decodeHeader(value) {
|
|
@@ -10119,7 +10277,7 @@ const releasedV3SessionFormatCodec = Object.freeze({
|
|
|
10119
10277
|
});
|
|
10120
10278
|
/**
|
|
10121
10279
|
* Validate owned V3 admission rules before a scanner or codec can discard a recoverable tail.
|
|
10122
|
-
* This checks only identified structural payloads; physical
|
|
10280
|
+
* This checks only identified structural payloads; physical source-event ranges still belong to decoding.
|
|
10123
10281
|
* @param row - parsed physical row, before envelope or compressed-range decoding.
|
|
10124
10282
|
*/
|
|
10125
10283
|
function assertV3RowAdmission(row) {
|
|
@@ -11289,7 +11447,7 @@ async function verifyCurrentGeneration(path, compression, expectedId, expectedEv
|
|
|
11289
11447
|
});
|
|
11290
11448
|
if (generation.meta.id !== expectedId) throw new Error(`current session generation contains id "${generation.meta.id}", expected "${expectedId}"`);
|
|
11291
11449
|
if (generation.events.length !== expectedEventCount) throw new Error(`current session generation contains ${generation.events.length} events, expected ${expectedEventCount}`);
|
|
11292
|
-
Session.fromRestore(generation.meta.id, generation.events, generation.meta, generation.inheritedEventCount, "detached");
|
|
11450
|
+
Session.fromRestore(generation.meta.id, generation.events, generation.meta, generation.inheritedEventCount, "detached", currentSessionMessageProjections);
|
|
11293
11451
|
assertCurrentAssistantStreams(generation.events);
|
|
11294
11452
|
return {
|
|
11295
11453
|
identity: snapshot.identity,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-session-persistence-jsonl",
|
|
3
3
|
"description": "JSONL durable session persistence backend for the DeepSeek Harness",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6-alpha.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -28,23 +28,23 @@
|
|
|
28
28
|
],
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@deepseek-ai/dsh-session
|
|
32
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
31
|
+
"@deepseek-ai/dsh-session": "^0.1.6-alpha.2",
|
|
32
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.6-alpha.2",
|
|
33
33
|
"@deepseek-ai/cordis": "^4.0.2"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"koffi": "^3.1.0",
|
|
37
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
38
|
-
"@deepseek-ai/dsh-session-format
|
|
39
|
-
"@deepseek-ai/dsh-session-format": "^0.1.
|
|
40
|
-
"@deepseek-ai/dsh-session-format-v2-to-v3": "^0.1.
|
|
41
|
-
"@deepseek-ai/
|
|
42
|
-
"@deepseek-ai/
|
|
37
|
+
"@deepseek-ai/dsh-llm": "^0.1.6-alpha.2",
|
|
38
|
+
"@deepseek-ai/dsh-session-format": "^0.1.6-alpha.2",
|
|
39
|
+
"@deepseek-ai/dsh-session-format-catalog": "^0.1.6-alpha.2",
|
|
40
|
+
"@deepseek-ai/dsh-session-format-v2-to-v3": "^0.1.6-alpha.2",
|
|
41
|
+
"@deepseek-ai/node-addon-system": "^0.1.2",
|
|
42
|
+
"@deepseek-ai/schemastery": "^3.18.2"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
46
|
-
"@deepseek-ai/dsh-session-format-v0-to-v1": "^0.1.
|
|
47
|
-
"@deepseek-ai/dsh-session-persistence": "^0.1.
|
|
45
|
+
"@deepseek-ai/dsh-session": "^0.1.6-alpha.2",
|
|
46
|
+
"@deepseek-ai/dsh-session-format-v0-to-v1": "^0.1.6-alpha.2",
|
|
47
|
+
"@deepseek-ai/dsh-session-persistence": "^0.1.6-alpha.2",
|
|
48
48
|
"@deepseek-ai/cordis": "^4.0.2"
|
|
49
49
|
}
|
|
50
50
|
}
|