@zhushanwen/zcode-session-source 0.2.0
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.md +39 -0
- package/dist/index.d.ts +323 -0
- package/dist/index.js +1129 -0
- package/package.json +35 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1129 @@
|
|
|
1
|
+
// src/recovery.ts
|
|
2
|
+
import { copyFileSync, existsSync, mkdtempSync, readdirSync, rmSync, statSync, writeFileSync } from "fs";
|
|
3
|
+
import { basename, join } from "path";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
|
|
6
|
+
// src/sqlite-driver.ts
|
|
7
|
+
async function importSqliteModule() {
|
|
8
|
+
const bunGlobal = globalThis.Bun;
|
|
9
|
+
if (typeof bunGlobal !== "undefined") {
|
|
10
|
+
const bunSqliteModuleId = "bun:sqlite";
|
|
11
|
+
const mod2 = await import(bunSqliteModuleId);
|
|
12
|
+
if (typeof mod2.Database !== "function") {
|
|
13
|
+
throw new Error("bun:sqlite \u53EF\u7528\u4F46\u7F3A Database \u5BFC\u51FA\uFF08bun \u7248\u672C\u5F02\u5E38\uFF09");
|
|
14
|
+
}
|
|
15
|
+
return mod2;
|
|
16
|
+
}
|
|
17
|
+
const nodeSqliteModuleId = "node:sqlite";
|
|
18
|
+
const mod = await import(nodeSqliteModuleId).catch(() => void 0);
|
|
19
|
+
if (typeof mod?.DatabaseSync !== "function") {
|
|
20
|
+
throw new Error("\u5F53\u524D node \u8FD0\u884C\u65F6\u4E0D\u652F\u6301 node:sqlite\uFF08\u9700 >=22.13\uFF09\uFF0C\u4E14\u5BBF\u4E3B\u975E bun \u8FD0\u884C\u65F6");
|
|
21
|
+
}
|
|
22
|
+
return mod;
|
|
23
|
+
}
|
|
24
|
+
var cachedDriver;
|
|
25
|
+
async function loadSqliteDriver() {
|
|
26
|
+
if (cachedDriver) return cachedDriver;
|
|
27
|
+
const mod = await importSqliteModule();
|
|
28
|
+
const bunGlobal = globalThis.Bun;
|
|
29
|
+
if (typeof bunGlobal !== "undefined") {
|
|
30
|
+
const Database = mod.Database;
|
|
31
|
+
cachedDriver = {
|
|
32
|
+
id: "bun:sqlite",
|
|
33
|
+
open(path, opts) {
|
|
34
|
+
const db = new Database(path, { readonly: opts.readOnly });
|
|
35
|
+
const rawPrepare = db.prepare.bind(db);
|
|
36
|
+
return {
|
|
37
|
+
prepare(sql) {
|
|
38
|
+
const stmt = rawPrepare(sql);
|
|
39
|
+
return {
|
|
40
|
+
all: (...args) => stmt.all(...args),
|
|
41
|
+
get: (...args) => {
|
|
42
|
+
const row = stmt.get(...args);
|
|
43
|
+
return row === null ? void 0 : row;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
close: () => db.close()
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
} else {
|
|
52
|
+
const DatabaseSync = mod.DatabaseSync;
|
|
53
|
+
cachedDriver = {
|
|
54
|
+
id: "node:sqlite",
|
|
55
|
+
open(path, opts) {
|
|
56
|
+
return new DatabaseSync(path, { readOnly: opts.readOnly });
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return cachedDriver;
|
|
61
|
+
}
|
|
62
|
+
function toSqliteFileUri(dbPath, immutable) {
|
|
63
|
+
const encoded = dbPath.replaceAll("%", "%25").replaceAll("?", "%3F").replaceAll("#", "%23");
|
|
64
|
+
return immutable ? `file:${encoded}?immutable=1` : `file:${encoded}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/recovery.ts
|
|
68
|
+
var SNAPSHOT_TMP_PREFIX = "taiji-zcode-snap-";
|
|
69
|
+
var KIB = 1024;
|
|
70
|
+
var SNAPSHOT_MAX_DB_KIB = 256;
|
|
71
|
+
var SNAPSHOT_MAX_DB_BYTES = SNAPSHOT_MAX_DB_KIB * KIB * KIB;
|
|
72
|
+
var REQUIRED_TABLES = ["session", "message", "part"];
|
|
73
|
+
function walPathOf(dbPath) {
|
|
74
|
+
return `${dbPath}-wal`;
|
|
75
|
+
}
|
|
76
|
+
var SqliteUnreadableError = class extends Error {
|
|
77
|
+
attempted;
|
|
78
|
+
constructor(attempted, message) {
|
|
79
|
+
super(message);
|
|
80
|
+
this.name = "SqliteUnreadableError";
|
|
81
|
+
this.attempted = attempted;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
async function tryOpen(dbPath, opts = {}) {
|
|
85
|
+
const driver = await loadSqliteDriver();
|
|
86
|
+
const target = opts.immutable ? toSqliteFileUri(dbPath, true) : dbPath;
|
|
87
|
+
const db = driver.open(target, { readOnly: true });
|
|
88
|
+
try {
|
|
89
|
+
const rows = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all();
|
|
90
|
+
const tableNames = [];
|
|
91
|
+
for (const row of rows) {
|
|
92
|
+
if (typeof row === "object" && row !== null) {
|
|
93
|
+
const name = row.name;
|
|
94
|
+
if (typeof name === "string") tableNames.push(name);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return { db, tableNames };
|
|
98
|
+
} catch (err) {
|
|
99
|
+
try {
|
|
100
|
+
db.close();
|
|
101
|
+
} catch (err2) {
|
|
102
|
+
console.debug("recovery: close after failed probe not attributed (best-effort)", err2);
|
|
103
|
+
}
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function tablesPresent(tableNames) {
|
|
108
|
+
const set = new Set(tableNames);
|
|
109
|
+
return REQUIRED_TABLES.every((t) => set.has(t));
|
|
110
|
+
}
|
|
111
|
+
function closeQuietly(db) {
|
|
112
|
+
try {
|
|
113
|
+
db.close();
|
|
114
|
+
} catch (err) {
|
|
115
|
+
console.debug("recovery: readonly connection close failed (best-effort)", err);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function openWithRecovery(dbPath) {
|
|
119
|
+
const attempted = [];
|
|
120
|
+
const skipNotes = [];
|
|
121
|
+
let lastError;
|
|
122
|
+
try {
|
|
123
|
+
const opened = await tryOpen(dbPath);
|
|
124
|
+
return toResult(opened, "L1-direct");
|
|
125
|
+
} catch (err) {
|
|
126
|
+
attempted.push("L1-direct");
|
|
127
|
+
lastError = err;
|
|
128
|
+
}
|
|
129
|
+
const walPresent = existsSync(walPathOf(dbPath));
|
|
130
|
+
if (walPresent) {
|
|
131
|
+
skipNotes.push("-wal present: L2 immutable and L3 snapshot gated off (silent data loss / rollback guard)");
|
|
132
|
+
} else {
|
|
133
|
+
try {
|
|
134
|
+
const opened = await tryOpen(dbPath, { immutable: true });
|
|
135
|
+
return toResult(opened, "L2-immutable");
|
|
136
|
+
} catch (err) {
|
|
137
|
+
attempted.push("L2-immutable");
|
|
138
|
+
lastError = err;
|
|
139
|
+
}
|
|
140
|
+
let dbBytes = -1;
|
|
141
|
+
try {
|
|
142
|
+
dbBytes = statSync(dbPath).size;
|
|
143
|
+
} catch (err) {
|
|
144
|
+
console.debug("recovery: statSync failed, db size treated as unknown (best-effort)", err);
|
|
145
|
+
}
|
|
146
|
+
if (dbBytes > SNAPSHOT_MAX_DB_BYTES) {
|
|
147
|
+
skipNotes.push(
|
|
148
|
+
`L3 rejected by size gate: db is ${dbBytes} bytes (> ${SNAPSHOT_MAX_DB_BYTES}), copy refused`
|
|
149
|
+
);
|
|
150
|
+
} else {
|
|
151
|
+
try {
|
|
152
|
+
const snap = await openViaSnapshot(dbPath);
|
|
153
|
+
return {
|
|
154
|
+
db: snap.db,
|
|
155
|
+
via: "L3-snapshot",
|
|
156
|
+
dispose() {
|
|
157
|
+
closeQuietly(snap.db);
|
|
158
|
+
rmSync(snap.snapshotDir, { recursive: true, force: true });
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
} catch (err) {
|
|
162
|
+
attempted.push("L3-snapshot");
|
|
163
|
+
lastError = err;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const parts = [`zcode session db unreadable (recovery ladder exhausted): ${dbPath}`];
|
|
168
|
+
if (skipNotes.length > 0) parts.push(skipNotes.join("; "));
|
|
169
|
+
const detail = lastError instanceof Error ? lastError.message : String(lastError);
|
|
170
|
+
parts.push(`last failure: ${detail}`);
|
|
171
|
+
throw new SqliteUnreadableError(attempted, parts.join(" \u2014 "));
|
|
172
|
+
}
|
|
173
|
+
function toResult(opened, via) {
|
|
174
|
+
return {
|
|
175
|
+
db: opened.db,
|
|
176
|
+
via,
|
|
177
|
+
dispose() {
|
|
178
|
+
closeQuietly(opened.db);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async function openViaSnapshot(dbPath) {
|
|
183
|
+
const dbBytes = statSync(dbPath).size;
|
|
184
|
+
if (dbBytes > SNAPSHOT_MAX_DB_BYTES) {
|
|
185
|
+
throw new SqliteUnreadableError(
|
|
186
|
+
[],
|
|
187
|
+
`snapshot refused by size gate: db is ${dbBytes} bytes (> ${SNAPSHOT_MAX_DB_BYTES}), copy refused`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
const snapshotDir = mkdtempSync(join(tmpdir(), SNAPSHOT_TMP_PREFIX));
|
|
191
|
+
let opened;
|
|
192
|
+
try {
|
|
193
|
+
const snapDb = join(snapshotDir, basename(dbPath));
|
|
194
|
+
copyFileSync(dbPath, snapDb);
|
|
195
|
+
writeFileSync(`${snapDb}-wal`, "");
|
|
196
|
+
opened = await tryOpen(snapDb);
|
|
197
|
+
if (!tablesPresent(opened.tableNames)) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`snapshot db failed table-set validation: expected ${REQUIRED_TABLES.join("/")}, found ${opened.tableNames.length > 0 ? [...opened.tableNames].sort().join("/") : "(none)"}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
return { db: opened.db, tableNames: opened.tableNames, snapshotDir };
|
|
203
|
+
} catch (err) {
|
|
204
|
+
if (opened) closeQuietly(opened.db);
|
|
205
|
+
rmSync(snapshotDir, { recursive: true, force: true });
|
|
206
|
+
throw err;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/sqlite-access.ts
|
|
211
|
+
import { existsSync as existsSync2 } from "fs";
|
|
212
|
+
import { homedir } from "os";
|
|
213
|
+
import { join as join2 } from "path";
|
|
214
|
+
import { ZCODE_HOST_DB_SUFFIX, ZCODE_ISOLATED_DB_SEGMENTS } from "@zhushanwen/subagent-engine-sdk";
|
|
215
|
+
function hostZcodeDbPath() {
|
|
216
|
+
return join2(homedir(), ...ZCODE_HOST_DB_SUFFIX);
|
|
217
|
+
}
|
|
218
|
+
function zcodeIsolatedDbPath(dataDir) {
|
|
219
|
+
return join2(dataDir, ...ZCODE_ISOLATED_DB_SEGMENTS);
|
|
220
|
+
}
|
|
221
|
+
function zcodeImportDbAllowlist(dataDir) {
|
|
222
|
+
return [zcodeIsolatedDbPath(dataDir), hostZcodeDbPath()];
|
|
223
|
+
}
|
|
224
|
+
var KNOWN_ZCODE_SCHEMA_VERSIONS = ["0.16.5"];
|
|
225
|
+
var ZcodeSchemaDriftError = class extends Error {
|
|
226
|
+
/** 观测到的版本(表不可读时为 undefined——两种归因经此字段区分,不拆两个错误类)。 */
|
|
227
|
+
observedVersion;
|
|
228
|
+
constructor(observedVersion, message) {
|
|
229
|
+
super(message);
|
|
230
|
+
this.name = "ZcodeSchemaDriftError";
|
|
231
|
+
this.observedVersion = observedVersion;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
function asString(v, col) {
|
|
235
|
+
if (typeof v !== "string") throw new Error(`session \u8868 ${col} \u5217\u7C7B\u578B\u5F02\u5E38\uFF08\u671F\u671B string\uFF0C\u5B9E\u9645 ${typeof v}\uFF09`);
|
|
236
|
+
return v;
|
|
237
|
+
}
|
|
238
|
+
function asNumber(v, col) {
|
|
239
|
+
if (typeof v !== "number" || !Number.isFinite(v)) throw new Error(`session \u8868 ${col} \u5217\u7C7B\u578B\u5F02\u5E38\uFF08\u671F\u671B number\uFF0C\u5B9E\u9645 ${typeof v}\uFF09`);
|
|
240
|
+
return v;
|
|
241
|
+
}
|
|
242
|
+
function rowToSessionRow(row) {
|
|
243
|
+
if (typeof row !== "object" || row === null) throw new Error("session \u8868\u8FD4\u56DE\u975E\u5BF9\u8C61\u884C");
|
|
244
|
+
const r = row;
|
|
245
|
+
return {
|
|
246
|
+
id: asString(r.id, "id"),
|
|
247
|
+
title: asString(r.title, "title"),
|
|
248
|
+
directory: asString(r.directory, "directory"),
|
|
249
|
+
taskType: asString(r.task_type, "task_type"),
|
|
250
|
+
timeCreated: asNumber(r.time_created, "time_created"),
|
|
251
|
+
timeUpdated: asNumber(r.time_updated, "time_updated")
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function parseRowData(raw, ctx) {
|
|
255
|
+
if (typeof raw !== "string") {
|
|
256
|
+
throw new Error(`${ctx} \u5217\u7C7B\u578B\u5F02\u5E38\uFF08\u671F\u671B JSON \u5B57\u7B26\u4E32\uFF0C\u5B9E\u9645 ${typeof raw}\uFF09`);
|
|
257
|
+
}
|
|
258
|
+
let parsed;
|
|
259
|
+
try {
|
|
260
|
+
parsed = JSON.parse(raw);
|
|
261
|
+
} catch (err) {
|
|
262
|
+
throw new Error(`${ctx} \u4E0D\u662F\u5408\u6CD5 JSON\uFF08${err instanceof Error ? err.message : String(err)}\uFF09`);
|
|
263
|
+
}
|
|
264
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
265
|
+
throw new Error(`${ctx} \u89E3\u6790\u540E\u975E\u5BF9\u8C61\uFF08\u5B9E\u9645 ${parsed === null ? "null" : Array.isArray(parsed) ? "array" : typeof parsed}\uFF09`);
|
|
266
|
+
}
|
|
267
|
+
return parsed;
|
|
268
|
+
}
|
|
269
|
+
function wrapDb(db) {
|
|
270
|
+
return {
|
|
271
|
+
listCandidateSessions(opts) {
|
|
272
|
+
const sql = "SELECT id, title, directory, task_type, time_created, time_updated FROM session WHERE task_type != 'subagent_child' ORDER BY time_updated DESC" + (opts?.limit !== void 0 ? " LIMIT ?" : "");
|
|
273
|
+
const rows = opts?.limit !== void 0 ? db.prepare(sql).all(opts.limit) : db.prepare(sql).all();
|
|
274
|
+
return rows.map(rowToSessionRow);
|
|
275
|
+
},
|
|
276
|
+
getSessionRow(id) {
|
|
277
|
+
const row = db.prepare("SELECT id, title, directory, task_type, time_created, time_updated FROM session WHERE id = ?").get(id);
|
|
278
|
+
return row === void 0 ? void 0 : rowToSessionRow(row);
|
|
279
|
+
},
|
|
280
|
+
getSessionTranscript(sessionId) {
|
|
281
|
+
const rows = db.prepare(
|
|
282
|
+
"SELECT m.id AS mid, m.sequence AS mseq, m.data AS mdata, p.sequence AS pseq, p.data AS pdata FROM message m LEFT JOIN part p ON p.message_id = m.id WHERE m.session_id = ? ORDER BY m.sequence, p.sequence"
|
|
283
|
+
).all(sessionId);
|
|
284
|
+
const messages = [];
|
|
285
|
+
let current;
|
|
286
|
+
for (const row of rows) {
|
|
287
|
+
if (typeof row !== "object" || row === null) throw new Error("message/part \u8054\u5408\u67E5\u8BE2\u8FD4\u56DE\u975E\u5BF9\u8C61\u884C");
|
|
288
|
+
const r = row;
|
|
289
|
+
const mid = asString(r.mid, "message.id");
|
|
290
|
+
if (current?.id !== mid) {
|
|
291
|
+
current = {
|
|
292
|
+
id: mid,
|
|
293
|
+
sequence: asNumber(r.mseq, "message.sequence"),
|
|
294
|
+
data: parseRowData(r.mdata, `message(${mid}).data`),
|
|
295
|
+
parts: []
|
|
296
|
+
};
|
|
297
|
+
messages.push(current);
|
|
298
|
+
}
|
|
299
|
+
if (r.pseq !== null && r.pseq !== void 0 && r.pdata !== null && r.pdata !== void 0) {
|
|
300
|
+
current.parts.push(parseRowData(r.pdata, `part(message=${mid}).data`));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return messages;
|
|
304
|
+
},
|
|
305
|
+
candidatesByteSize(sessionIds) {
|
|
306
|
+
if (sessionIds.length === 0) return /* @__PURE__ */ new Map();
|
|
307
|
+
const placeholders = sessionIds.map(() => "?").join(", ");
|
|
308
|
+
const rows = db.prepare(
|
|
309
|
+
`SELECT session_id AS sid, SUM(length(CAST(data AS BLOB))) AS bytes FROM part WHERE session_id IN (${placeholders}) GROUP BY session_id`
|
|
310
|
+
).all(...sessionIds);
|
|
311
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
312
|
+
for (const row of rows) {
|
|
313
|
+
if (typeof row !== "object" || row === null) continue;
|
|
314
|
+
const r = row;
|
|
315
|
+
if (typeof r.sid !== "string") continue;
|
|
316
|
+
if (typeof r.bytes !== "number") {
|
|
317
|
+
console.warn(
|
|
318
|
+
`[zcode-session-source] candidatesByteSize \u884C bytes \u975E number\uFF08\u5B9E\u9645 ${typeof r.bytes}\uFF09\uFF0C\u8BE5\u4F1A\u8BDD\u5927\u5C0F\u672A\u77E5\uFF1AsessionId=${r.sid}`
|
|
319
|
+
);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
sizes.set(r.sid, r.bytes);
|
|
323
|
+
}
|
|
324
|
+
return sizes;
|
|
325
|
+
},
|
|
326
|
+
readSchemaVersion() {
|
|
327
|
+
try {
|
|
328
|
+
const row = db.prepare("SELECT app_version FROM schema_migration ORDER BY time_applied DESC LIMIT 1").get();
|
|
329
|
+
if (typeof row === "object" && row !== null) {
|
|
330
|
+
const v = row.app_version;
|
|
331
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
332
|
+
}
|
|
333
|
+
return void 0;
|
|
334
|
+
} catch {
|
|
335
|
+
return void 0;
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
close() {
|
|
339
|
+
try {
|
|
340
|
+
db.close();
|
|
341
|
+
} catch (err) {
|
|
342
|
+
void err;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function assertKnownSchema(db) {
|
|
348
|
+
let observedVersion;
|
|
349
|
+
try {
|
|
350
|
+
const row = db.prepare("SELECT app_version FROM schema_migration ORDER BY time_applied DESC LIMIT 1").get();
|
|
351
|
+
if (typeof row === "object" && row !== null) {
|
|
352
|
+
const v = row.app_version;
|
|
353
|
+
if (typeof v === "string" && v.length > 0) observedVersion = v;
|
|
354
|
+
}
|
|
355
|
+
} catch {
|
|
356
|
+
throw new ZcodeSchemaDriftError(
|
|
357
|
+
void 0,
|
|
358
|
+
`zcode session db schema_migration unreadable (expected one of: ${KNOWN_ZCODE_SCHEMA_VERSIONS.join("/")})`
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
if (observedVersion === void 0 || !KNOWN_ZCODE_SCHEMA_VERSIONS.includes(observedVersion)) {
|
|
362
|
+
throw new ZcodeSchemaDriftError(
|
|
363
|
+
observedVersion,
|
|
364
|
+
`zcode session db schema version unknown: ${observedVersion ?? "(absent)"} (known: ${KNOWN_ZCODE_SCHEMA_VERSIONS.join("/")})`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async function openZcodeSessionDb(dbPath) {
|
|
369
|
+
if (!existsSync2(dbPath)) {
|
|
370
|
+
throw new Error(`db \u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${dbPath}`);
|
|
371
|
+
}
|
|
372
|
+
const opened = await openWithRecovery(dbPath);
|
|
373
|
+
try {
|
|
374
|
+
assertKnownSchema(opened.db);
|
|
375
|
+
} catch (err) {
|
|
376
|
+
opened.dispose();
|
|
377
|
+
throw err;
|
|
378
|
+
}
|
|
379
|
+
return { db: wrapDb(opened.db), via: opened.via, dispose: opened.dispose };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/converter.ts
|
|
383
|
+
import { normalizeZcodeRowId } from "@zhushanwen/session-core";
|
|
384
|
+
|
|
385
|
+
// src/normalize.ts
|
|
386
|
+
var NORMALIZED_ID_RE = /^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?$/;
|
|
387
|
+
var SESS_PREFIX = "sess_";
|
|
388
|
+
function normalizeZcodeSessionId(raw) {
|
|
389
|
+
const stripped = raw.startsWith(SESS_PREFIX) ? raw.slice(SESS_PREFIX.length) : raw;
|
|
390
|
+
const replaced = stripped.replaceAll("_", "-");
|
|
391
|
+
if (replaced.length === 0) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
`zcode \u4F1A\u8BDD id \u5F52\u4E00\u5316\u540E\u4E3A\u7A7A\uFF08\u539F\u59CB id=\u300C${raw}\u300D\uFF09\uFF1A\u8BE5 id \u5F62\u6001\u8D85\u51FA\u5DF2\u9A8C\u8BC1\u57DF\uFF08sess_<uuid>\uFF09`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
if (replaced.includes("_")) {
|
|
397
|
+
throw new Error(
|
|
398
|
+
`zcode \u4F1A\u8BDD id \u5F52\u4E00\u5316\u540E\u4ECD\u542B\u4E0B\u5212\u7EBF\uFF08\u539F\u59CB id=\u300C${raw}\u300D\u2192\u300C${replaced}\u300D\uFF09\uFF1A\u7834\u574F\u300C\u6587\u4EF6\u540D\u5C3E\u6BB5 == header.id\u300D\u4E0D\u53D8\u91CF`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
if (!NORMALIZED_ID_RE.test(replaced)) {
|
|
402
|
+
throw new Error(
|
|
403
|
+
`zcode \u4F1A\u8BDD id \u5F52\u4E00\u5316\u540E\u542B\u975E\u6CD5\u5B57\u7B26\u6216\u9996\u5C3E\u975E\u5B57\u6BCD\u6570\u5B57\uFF08\u539F\u59CB id=\u300C${raw}\u300D\u2192\u300C${replaced}\u300D\uFF0C\u5408\u6CD5\u96C6 = \u5B57\u6BCD\u6570\u5B57\u4E0E\u300C-\u300D\u4E14\u9996\u5C3E\u5B57\u6BCD\u6570\u5B57\uFF09\uFF1A\u8BE5 id \u5F62\u6001\u8D85\u51FA\u5DF2\u9A8C\u8BC1\u57DF\uFF08sess_<uuid>\uFF09`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
return replaced;
|
|
407
|
+
}
|
|
408
|
+
var zcodeCandidateKey = normalizeZcodeSessionId;
|
|
409
|
+
|
|
410
|
+
// src/semantics.ts
|
|
411
|
+
var SEMANTICS_KINDS = Object.freeze([
|
|
412
|
+
"user_prompt",
|
|
413
|
+
"slash_command",
|
|
414
|
+
"system_reminder",
|
|
415
|
+
"background_notification",
|
|
416
|
+
"subagent_notification",
|
|
417
|
+
"todo_reminder",
|
|
418
|
+
"rewind_notice",
|
|
419
|
+
"fork_notice",
|
|
420
|
+
"timeline_event",
|
|
421
|
+
"compact_summary",
|
|
422
|
+
"shared_context",
|
|
423
|
+
"assistant_response"
|
|
424
|
+
]);
|
|
425
|
+
var SEMANTICS_ORIGINS = Object.freeze([
|
|
426
|
+
"real_user",
|
|
427
|
+
"agent_runtime",
|
|
428
|
+
"system",
|
|
429
|
+
"migration",
|
|
430
|
+
"import"
|
|
431
|
+
]);
|
|
432
|
+
var MESSAGE_SOURCES = Object.freeze([
|
|
433
|
+
"background_task",
|
|
434
|
+
"fork",
|
|
435
|
+
"goal_state_change",
|
|
436
|
+
"goal-continuation",
|
|
437
|
+
"plugin_reference",
|
|
438
|
+
"rewind",
|
|
439
|
+
"selection_side_chat",
|
|
440
|
+
"subagent",
|
|
441
|
+
"subagent_message",
|
|
442
|
+
"todo_reminder",
|
|
443
|
+
"workflow_launch",
|
|
444
|
+
"shared_context"
|
|
445
|
+
]);
|
|
446
|
+
var LEGACY_METADATA_SOURCES = Object.freeze([
|
|
447
|
+
"agent_control_message",
|
|
448
|
+
"background_task",
|
|
449
|
+
"goal-continuation",
|
|
450
|
+
"goal_completion_verification",
|
|
451
|
+
"goal_state_change",
|
|
452
|
+
"plugin_reference",
|
|
453
|
+
"queued_system_notification",
|
|
454
|
+
"resume_goal_state",
|
|
455
|
+
"resume_referenced_session_context",
|
|
456
|
+
"rewind",
|
|
457
|
+
"selection_side_chat",
|
|
458
|
+
"subagent",
|
|
459
|
+
"subagent_message",
|
|
460
|
+
"target_continuation",
|
|
461
|
+
"task_notification",
|
|
462
|
+
"task_status",
|
|
463
|
+
"todo_reminder"
|
|
464
|
+
]);
|
|
465
|
+
var PROJECTION_POLICIES = Object.freeze([
|
|
466
|
+
"realUserInput",
|
|
467
|
+
"visibleAssistant",
|
|
468
|
+
"providerContextOnly",
|
|
469
|
+
"hiddenSynthetic",
|
|
470
|
+
"timelineOnly",
|
|
471
|
+
"compactSummary"
|
|
472
|
+
]);
|
|
473
|
+
var SEMANTICS_KIND_SET = new Set(SEMANTICS_KINDS);
|
|
474
|
+
var SEMANTICS_ORIGIN_SET = new Set(SEMANTICS_ORIGINS);
|
|
475
|
+
var MESSAGE_SOURCE_SET = new Set(MESSAGE_SOURCES);
|
|
476
|
+
var LEGACY_METADATA_SOURCE_SET = new Set(LEGACY_METADATA_SOURCES);
|
|
477
|
+
var PROJECTION_POLICY_SET = new Set(PROJECTION_POLICIES);
|
|
478
|
+
|
|
479
|
+
// src/projection.ts
|
|
480
|
+
var MODEL_ONLY = "model-only";
|
|
481
|
+
var FORK = "fork";
|
|
482
|
+
var GOAL_CONTINUATION_PREFIX = '<system-reminder source="goal-continuation">';
|
|
483
|
+
var SYSTEM_REMINDER_TAG = "<system-reminder>";
|
|
484
|
+
var GOAL_CONTINUATION_SENTINEL = "Continue working toward the active session goal.";
|
|
485
|
+
var GOAL_STATE_SENTINEL = "Current session goal state";
|
|
486
|
+
var REWIND_APPLIED_TEXTS = Object.freeze([
|
|
487
|
+
"Conversation rewind applied.",
|
|
488
|
+
"Workspace rewind applied."
|
|
489
|
+
]);
|
|
490
|
+
var TASK_NOTIFICATION_TAG = "<task-notification>";
|
|
491
|
+
var SUBAGENT_NOTIFICATION_TAG = "<subagent-notification>";
|
|
492
|
+
var GOAL_CONTINUATION_SOURCE = "goal-continuation";
|
|
493
|
+
function asRecord(v) {
|
|
494
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
|
|
495
|
+
}
|
|
496
|
+
function asNonEmptyString(v) {
|
|
497
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
498
|
+
}
|
|
499
|
+
function inClosedSet(v, set) {
|
|
500
|
+
return typeof v === "string" && set.has(v);
|
|
501
|
+
}
|
|
502
|
+
function messageSource(data, parts) {
|
|
503
|
+
const metadata = asRecord(data.metadata);
|
|
504
|
+
const semantics = asRecord(data.semantics);
|
|
505
|
+
return data.source ?? asNonEmptyString(metadata?.source) ?? semantics?.source ?? parts.map((p) => asNonEmptyString(asRecord(p.metadata)?.source)).find((s) => s !== void 0);
|
|
506
|
+
}
|
|
507
|
+
function isSyntheticMessage(data, parts) {
|
|
508
|
+
return data.synthetic === true || parts.some((p) => p.synthetic === true);
|
|
509
|
+
}
|
|
510
|
+
function hasModelOnlyPart(parts) {
|
|
511
|
+
return parts.some((p) => {
|
|
512
|
+
const metadata = asRecord(p.metadata);
|
|
513
|
+
return metadata?.visibility === MODEL_ONLY || asNonEmptyString(metadata?.source) === GOAL_CONTINUATION_SOURCE;
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
function hasSessionForkContext(metadata) {
|
|
517
|
+
return asRecord(metadata?.forkContext)?.kind === "session_fork";
|
|
518
|
+
}
|
|
519
|
+
function isTimelineOnlyMessage(data, parts) {
|
|
520
|
+
if (asRecord(data.semantics)?.kind === "timeline_event") return true;
|
|
521
|
+
const metadata = asRecord(data.metadata);
|
|
522
|
+
if (data.source === FORK || asNonEmptyString(metadata?.source) === FORK) return true;
|
|
523
|
+
return parts.some((p) => {
|
|
524
|
+
const partMetadata = asRecord(p.metadata);
|
|
525
|
+
return p.type === "timeline" || hasSessionForkContext(partMetadata) || p.type === "compaction" && (typeof partMetadata?.timelineStatus === "string" || typeof p.summaryMessageId === "string");
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
function hasLegacyReminderText(parts) {
|
|
529
|
+
const text = textFromParts(parts).trimStart();
|
|
530
|
+
return text.startsWith(GOAL_CONTINUATION_PREFIX) || text.startsWith(SYSTEM_REMINDER_TAG) && (text.includes(GOAL_CONTINUATION_SENTINEL) || text.includes(GOAL_STATE_SENTINEL)) || REWIND_APPLIED_TEXTS.some((t) => text.includes(t));
|
|
531
|
+
}
|
|
532
|
+
function hasLegacyNotifyText(parts) {
|
|
533
|
+
const text = textFromParts(parts).trimStart();
|
|
534
|
+
return text.startsWith(TASK_NOTIFICATION_TAG) || text.startsWith(SUBAGENT_NOTIFICATION_TAG);
|
|
535
|
+
}
|
|
536
|
+
function textFromParts(parts) {
|
|
537
|
+
return parts.filter((p) => p.type === "text" && p.ignored !== true).map((p) => p.text ?? "").join("");
|
|
538
|
+
}
|
|
539
|
+
function isUnclassifiedByClosedSets(semantics, src) {
|
|
540
|
+
if (semantics && !inClosedSet(semantics.kind, SEMANTICS_KIND_SET)) return true;
|
|
541
|
+
if (semantics && !inClosedSet(semantics.origin, SEMANTICS_ORIGIN_SET)) return true;
|
|
542
|
+
return src !== void 0 && !(typeof src === "string" && (MESSAGE_SOURCE_SET.has(src) || LEGACY_METADATA_SOURCE_SET.has(src)));
|
|
543
|
+
}
|
|
544
|
+
function classifyBySemanticsBranches(semantics, data) {
|
|
545
|
+
if (semantics.kind === "timeline_event") return "timelineOnly";
|
|
546
|
+
if (semantics.origin === "real_user" && data.synthetic !== true && data.visibility !== MODEL_ONLY) {
|
|
547
|
+
return "realUserInput";
|
|
548
|
+
}
|
|
549
|
+
if (data.role === "assistant" && semantics.kind === "assistant_response" && semantics.uiVisibility === "visible" && semantics.transcriptVisibility === "visible") {
|
|
550
|
+
return "visibleAssistant";
|
|
551
|
+
}
|
|
552
|
+
if (semantics.providerVisibility === "visible") return "providerContextOnly";
|
|
553
|
+
if (semantics.kind === "fork_notice") return "timelineOnly";
|
|
554
|
+
if (semantics.origin === "agent_runtime" || semantics.uiVisibility === "hidden" || semantics.transcriptVisibility === "hidden") {
|
|
555
|
+
return "hiddenSynthetic";
|
|
556
|
+
}
|
|
557
|
+
return void 0;
|
|
558
|
+
}
|
|
559
|
+
function classifyLegacyFallback(data, parts, src) {
|
|
560
|
+
if (data.visibility === MODEL_ONLY || hasModelOnlyPart(parts)) return "providerContextOnly";
|
|
561
|
+
if (isTimelineOnlyMessage(data, parts)) return "timelineOnly";
|
|
562
|
+
if (src === FORK) return "timelineOnly";
|
|
563
|
+
if (typeof src === "string" && LEGACY_METADATA_SOURCE_SET.has(src)) return "providerContextOnly";
|
|
564
|
+
if (hasLegacyReminderText(parts)) return "providerContextOnly";
|
|
565
|
+
if (isSyntheticMessage(data, parts) && hasLegacyNotifyText(parts)) return "providerContextOnly";
|
|
566
|
+
if (isSyntheticMessage(data, parts)) return "hiddenSynthetic";
|
|
567
|
+
return data.role === "assistant" ? "visibleAssistant" : "realUserInput";
|
|
568
|
+
}
|
|
569
|
+
function classifyMessage(data, parts) {
|
|
570
|
+
const semantics = asRecord(data.semantics);
|
|
571
|
+
const src = messageSource(data, parts);
|
|
572
|
+
if (isUnclassifiedByClosedSets(semantics, src)) return "unclassified";
|
|
573
|
+
if (semantics?.kind === "compact_summary" || data.summary !== void 0) return "compactSummary";
|
|
574
|
+
if (semantics) {
|
|
575
|
+
const bySemantics = classifyBySemanticsBranches(semantics, data);
|
|
576
|
+
if (bySemantics !== void 0) return bySemantics;
|
|
577
|
+
}
|
|
578
|
+
return classifyLegacyFallback(data, parts, src);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/assistant-mapping.ts
|
|
582
|
+
var STOP_REASON_MAP = Object.freeze({
|
|
583
|
+
"tool-calls": "toolUse",
|
|
584
|
+
stop: "stop",
|
|
585
|
+
completed: "stop",
|
|
586
|
+
length: "length",
|
|
587
|
+
interrupted: "aborted",
|
|
588
|
+
failed: "error",
|
|
589
|
+
stream_recovery_discarded: "error",
|
|
590
|
+
start_plan_admission_retry_discarded: "error"
|
|
591
|
+
});
|
|
592
|
+
function mapStopReason(zcodeFinish) {
|
|
593
|
+
return typeof zcodeFinish === "string" ? STOP_REASON_MAP[zcodeFinish] ?? "stop" : "stop";
|
|
594
|
+
}
|
|
595
|
+
function unsealedStopReason(data) {
|
|
596
|
+
const error = isRecord(data.error) ? data.error : void 0;
|
|
597
|
+
if (error === void 0) return "stop";
|
|
598
|
+
const errData = isRecord(error.data) ? error.data : void 0;
|
|
599
|
+
const turnResult = typeof errData?.turnResult === "string" ? errData.turnResult : void 0;
|
|
600
|
+
if (turnResult === "cancelled") return "aborted";
|
|
601
|
+
return "error";
|
|
602
|
+
}
|
|
603
|
+
function zeroUsage() {
|
|
604
|
+
return {
|
|
605
|
+
input: 0,
|
|
606
|
+
output: 0,
|
|
607
|
+
cacheRead: 0,
|
|
608
|
+
cacheWrite: 0,
|
|
609
|
+
reasoning: 0,
|
|
610
|
+
totalTokens: 0,
|
|
611
|
+
cost: { total: 0 }
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
function usageFromStepFinish(partData) {
|
|
615
|
+
const tokens = partData.tokens;
|
|
616
|
+
if (!isRecord(tokens)) return { usage: void 0, degradation: void 0 };
|
|
617
|
+
const cache = isRecord(tokens.cache) ? tokens.cache : {};
|
|
618
|
+
const input = asFinite(tokens.input);
|
|
619
|
+
const output = asFinite(tokens.output);
|
|
620
|
+
const cacheRead = asFinite(cache.read);
|
|
621
|
+
const cacheWrite = asFinite(cache.write);
|
|
622
|
+
const reasoning = asFinite(tokens.reasoning);
|
|
623
|
+
const totalTokens = asFinite(tokens.total);
|
|
624
|
+
const costTotal = asFinite(partData.cost);
|
|
625
|
+
const usage = {
|
|
626
|
+
...input !== void 0 && { input },
|
|
627
|
+
...output !== void 0 && { output },
|
|
628
|
+
...cacheRead !== void 0 && { cacheRead },
|
|
629
|
+
...cacheWrite !== void 0 && { cacheWrite },
|
|
630
|
+
...reasoning !== void 0 && { reasoning },
|
|
631
|
+
...totalTokens !== void 0 && { totalTokens },
|
|
632
|
+
...costTotal !== void 0 && { cost: { total: costTotal } }
|
|
633
|
+
};
|
|
634
|
+
if (Object.keys(usage).length === 0) {
|
|
635
|
+
return {
|
|
636
|
+
usage: void 0,
|
|
637
|
+
degradation: "step-finish tokens/cost \u5206\u91CF\u5168\u90E8\u4E0D\u53EF\u89E3\uFF0C\u6574\u6761 usage \u4E0D\u5199\uFF08\u96F6\u503C\u515C\u5E95\uFF09"
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
return { usage, degradation: void 0 };
|
|
641
|
+
}
|
|
642
|
+
function isRecord(v) {
|
|
643
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
644
|
+
}
|
|
645
|
+
function asFinite(v) {
|
|
646
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
647
|
+
}
|
|
648
|
+
function asNonEmptyString2(v) {
|
|
649
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/compaction.ts
|
|
653
|
+
function summaryBodyOf(data) {
|
|
654
|
+
const summary = isRecord(data.summary) ? data.summary : void 0;
|
|
655
|
+
return asNonEmptyString2(summary?.body);
|
|
656
|
+
}
|
|
657
|
+
function partHasTimelineStatus(part) {
|
|
658
|
+
if (typeof part.timelineStatus === "string") return true;
|
|
659
|
+
const metadata = isRecord(part.metadata) ? part.metadata : void 0;
|
|
660
|
+
return typeof metadata?.timelineStatus === "string";
|
|
661
|
+
}
|
|
662
|
+
function resolveFirstKeptEntryId(tier1, lastEmittedId, ownId) {
|
|
663
|
+
if (tier1 !== void 0) return tier1;
|
|
664
|
+
if (lastEmittedId !== null) return lastEmittedId;
|
|
665
|
+
return ownId;
|
|
666
|
+
}
|
|
667
|
+
function compactionPartDisposition(msg, part, messageIds, mergeable) {
|
|
668
|
+
const summaryId = asNonEmptyString2(part.summaryMessageId);
|
|
669
|
+
if (summaryId !== void 0) {
|
|
670
|
+
if (!messageIds.has(summaryId)) return { disposition: "dangling" };
|
|
671
|
+
if (mergeable.has(summaryId)) return { disposition: "merged", summaryId };
|
|
672
|
+
return void 0;
|
|
673
|
+
}
|
|
674
|
+
const semantics = isRecord(msg.data.semantics) ? msg.data.semantics : void 0;
|
|
675
|
+
if (semantics?.kind === "compact_summary" && mergeable.has(msg.id)) {
|
|
676
|
+
return { disposition: "merged", summaryId: msg.id };
|
|
677
|
+
}
|
|
678
|
+
if (msg.data.role === "user" && !partHasTimelineStatus(part) && mergeable.has(msg.id)) {
|
|
679
|
+
return { disposition: "merged", summaryId: msg.id };
|
|
680
|
+
}
|
|
681
|
+
return void 0;
|
|
682
|
+
}
|
|
683
|
+
function precomputeCompactionAssociation(messages, policies) {
|
|
684
|
+
const messageIds = new Set(messages.map((msg) => msg.id));
|
|
685
|
+
const mergeable = /* @__PURE__ */ new Set();
|
|
686
|
+
for (const [i, msg] of messages.entries()) {
|
|
687
|
+
if (policies[i] === "compactSummary" && summaryBodyOf(msg.data) !== void 0) mergeable.add(msg.id);
|
|
688
|
+
}
|
|
689
|
+
const linkedParts = /* @__PURE__ */ new Map();
|
|
690
|
+
const compactionDisposition = /* @__PURE__ */ new WeakMap();
|
|
691
|
+
for (const msg of messages) {
|
|
692
|
+
for (const part of msg.parts) {
|
|
693
|
+
if (part.type !== "compaction") continue;
|
|
694
|
+
const result = compactionPartDisposition(msg, part, messageIds, mergeable);
|
|
695
|
+
if (result === void 0) continue;
|
|
696
|
+
if (result.disposition === "dangling") {
|
|
697
|
+
compactionDisposition.set(part, "dangling");
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
compactionDisposition.set(part, "merged");
|
|
701
|
+
const list = linkedParts.get(result.summaryId);
|
|
702
|
+
if (list !== void 0) list.push(part);
|
|
703
|
+
else linkedParts.set(result.summaryId, [part]);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return { linkedParts, compactionDisposition };
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// src/converter.ts
|
|
710
|
+
var DEGRADATION_PREVIEW_MAX = 80;
|
|
711
|
+
function pushDegradation(degradations, text, messageId) {
|
|
712
|
+
degradations.push({
|
|
713
|
+
code: "dropped_transient",
|
|
714
|
+
count: 1,
|
|
715
|
+
...messageId !== void 0 && {
|
|
716
|
+
sample: { messageId, preview: text.slice(0, DEGRADATION_PREVIEW_MAX) }
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
function degradationMeta(data) {
|
|
721
|
+
const semantics = isRecord(data.semantics) ? data.semantics : void 0;
|
|
722
|
+
const kind = typeof semantics?.kind === "string" ? semantics.kind : void 0;
|
|
723
|
+
const metadata = isRecord(data.metadata) ? data.metadata : void 0;
|
|
724
|
+
let source;
|
|
725
|
+
if (typeof data.source === "string") source = data.source;
|
|
726
|
+
else if (typeof metadata?.source === "string") source = metadata.source;
|
|
727
|
+
return { ...kind !== void 0 && { kind }, ...source !== void 0 && { source } };
|
|
728
|
+
}
|
|
729
|
+
function isToolPreservedSource(source) {
|
|
730
|
+
return source.startsWith("background_") || source.startsWith("subagent_") || source === "subagent";
|
|
731
|
+
}
|
|
732
|
+
function droppedMessageCode(data) {
|
|
733
|
+
const { source } = degradationMeta(data);
|
|
734
|
+
return source !== void 0 && isToolPreservedSource(source) ? "dropped_redundant" : "dropped_transient";
|
|
735
|
+
}
|
|
736
|
+
function messageTextPreview(parts) {
|
|
737
|
+
return parts.filter((p) => p.type === "text" && p.ignored !== true).map((p) => typeof p.text === "string" ? p.text : "").join("");
|
|
738
|
+
}
|
|
739
|
+
var TOOL_NAME_MAP = Object.freeze({
|
|
740
|
+
Edit: "edit",
|
|
741
|
+
Bash: "bash",
|
|
742
|
+
Read: "read",
|
|
743
|
+
Write: "write",
|
|
744
|
+
Grep: "grep",
|
|
745
|
+
Glob: "glob",
|
|
746
|
+
Skill: "skill"
|
|
747
|
+
});
|
|
748
|
+
function mapToolName(zcodeTool) {
|
|
749
|
+
return TOOL_NAME_MAP[zcodeTool] ?? zcodeTool;
|
|
750
|
+
}
|
|
751
|
+
var EntryChain = class {
|
|
752
|
+
counter = 0;
|
|
753
|
+
lastId = null;
|
|
754
|
+
next() {
|
|
755
|
+
this.counter += 1;
|
|
756
|
+
const id = normalizeZcodeRowId(this.counter);
|
|
757
|
+
const parentId = this.lastId;
|
|
758
|
+
this.lastId = id;
|
|
759
|
+
return { id, parentId };
|
|
760
|
+
}
|
|
761
|
+
/** 最后一条已发射 entry 的 id(无则 null)——D2 firstKeptEntryId ② 级锚(紧邻前驱)。 */
|
|
762
|
+
last() {
|
|
763
|
+
return this.lastId;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* 下一个 id 预览(不推进计数器)。compaction entry 的 ③ 级锚 = 其自身 id,而 id 由
|
|
767
|
+
* 发射时才生成、firstKeptEntryId 又必须在发射前进入 payload——peek 让两者解耦,
|
|
768
|
+
* next() 产出的 id 与 peek 结果严格一致(同一确定性公式)。
|
|
769
|
+
*/
|
|
770
|
+
peek() {
|
|
771
|
+
return normalizeZcodeRowId(this.counter + 1);
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
var MESSAGE_ROLES = /* @__PURE__ */ new Set(["user", "assistant", "toolResult"]);
|
|
775
|
+
var RAW_JSON_SNIPPET_CHARS = 120;
|
|
776
|
+
function toCanonicalEntry(raw) {
|
|
777
|
+
const type = raw.type;
|
|
778
|
+
const id = raw.id;
|
|
779
|
+
const parentId = raw.parentId;
|
|
780
|
+
if (typeof type !== "string" || type.length === 0) {
|
|
781
|
+
throw new Error(`converter \u5185\u90E8\u9519\u8BEF\uFF1Aentry \u7F3A type\uFF08${JSON.stringify(raw).slice(0, RAW_JSON_SNIPPET_CHARS)}\uFF09`);
|
|
782
|
+
}
|
|
783
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
784
|
+
throw new Error(`converter \u5185\u90E8\u9519\u8BEF\uFF1Aentry \u7F3A id\uFF08type=${type}\uFF09`);
|
|
785
|
+
}
|
|
786
|
+
if (parentId !== null && typeof parentId !== "string") {
|
|
787
|
+
throw new Error(`converter \u5185\u90E8\u9519\u8BEF\uFF1Aentry parentId \u975E string|null\uFF08type=${type}\uFF0Cid=${id}\uFF09`);
|
|
788
|
+
}
|
|
789
|
+
const message = raw.message;
|
|
790
|
+
if (message !== void 0) {
|
|
791
|
+
if (!isRecord(message) || typeof message.role !== "string" || !MESSAGE_ROLES.has(message.role)) {
|
|
792
|
+
throw new Error(`converter \u5185\u90E8\u9519\u8BEF\uFF1Aentry message.role \u975E\u6CD5\uFF08type=${type}\uFF0Cid=${id}\uFF09`);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return raw;
|
|
796
|
+
}
|
|
797
|
+
function newSegment() {
|
|
798
|
+
return { content: [], toolResults: [], startMs: void 0 };
|
|
799
|
+
}
|
|
800
|
+
function toolResultShape(state, partData, msgId, degradations) {
|
|
801
|
+
const status = state.status;
|
|
802
|
+
const output = state.output;
|
|
803
|
+
if (status === "error") {
|
|
804
|
+
const errText = typeof state.error === "string" ? state.error : typeof output === "string" ? output : "";
|
|
805
|
+
return { content: [{ type: "text", text: errText }], isError: true };
|
|
806
|
+
}
|
|
807
|
+
if (typeof output === "string") {
|
|
808
|
+
return { content: [{ type: "text", text: output }], isError: false };
|
|
809
|
+
}
|
|
810
|
+
if (output === null || output === void 0) {
|
|
811
|
+
return { content: [], isError: false };
|
|
812
|
+
}
|
|
813
|
+
if (isRecord(output)) {
|
|
814
|
+
return { content: [], details: output, isError: false };
|
|
815
|
+
}
|
|
816
|
+
pushDegradation(
|
|
817
|
+
degradations,
|
|
818
|
+
`tool\u300C${String(partData.tool)}\u300Doutput \u5F62\u6001\u8D85\u51FA\u5DF2\u9A8C\u8BC1\u57DF\uFF08typeof ${typeof output}\uFF09\uFF0C\u8DF3\u8FC7\u8F93\u51FA\uFF1Amessage=${msgId}`,
|
|
819
|
+
msgId
|
|
820
|
+
);
|
|
821
|
+
return { content: [], isError: false };
|
|
822
|
+
}
|
|
823
|
+
function convertZcodeTranscript(messages, session) {
|
|
824
|
+
const degradations = [];
|
|
825
|
+
const entries = [];
|
|
826
|
+
const sessionCreatedMs = asFinite(session.timeCreated);
|
|
827
|
+
const headerTimestamp = sessionCreatedMs !== void 0 ? new Date(sessionCreatedMs).toISOString() : void 0;
|
|
828
|
+
if (sessionCreatedMs === void 0) {
|
|
829
|
+
pushDegradation(
|
|
830
|
+
degradations,
|
|
831
|
+
`session.timeCreated \u4E0D\u53EF\u89E3\uFF08${String(session.timeCreated)}\uFF09\uFF0Cheader/message \u65F6\u95F4\u6233\u7F3A\u7701\uFF1A\u4E0D\u4F2A\u9020 1970`
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
const header = {
|
|
835
|
+
id: normalizeZcodeSessionId(session.id),
|
|
836
|
+
...headerTimestamp !== void 0 && { timestamp: headerTimestamp }
|
|
837
|
+
};
|
|
838
|
+
const chain = new EntryChain();
|
|
839
|
+
const emitEntry = (type, timestamp, rest) => {
|
|
840
|
+
const { id, parentId } = chain.next();
|
|
841
|
+
const entry = toCanonicalEntry({ type, id, parentId, ...timestamp !== void 0 && { timestamp }, ...rest });
|
|
842
|
+
entries.push(entry);
|
|
843
|
+
return entry.id;
|
|
844
|
+
};
|
|
845
|
+
const messageIdToEntryId = /* @__PURE__ */ new Map();
|
|
846
|
+
const policies = messages.map((msg) => classifyMessage(msg.data, msg.parts));
|
|
847
|
+
const { linkedParts, compactionDisposition } = precomputeCompactionAssociation(messages, policies);
|
|
848
|
+
const dropGroups = /* @__PURE__ */ new Map();
|
|
849
|
+
const recordDroppedMessage = (degradation) => {
|
|
850
|
+
const key = `${degradation.code}|${degradation.kind ?? ""}|${degradation.source ?? ""}`;
|
|
851
|
+
const existing = dropGroups.get(key);
|
|
852
|
+
if (existing === void 0) {
|
|
853
|
+
dropGroups.set(key, { ...degradation });
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
existing.count += degradation.count;
|
|
857
|
+
};
|
|
858
|
+
const deps = {
|
|
859
|
+
chain,
|
|
860
|
+
linkedParts,
|
|
861
|
+
compactionDisposition,
|
|
862
|
+
degradations,
|
|
863
|
+
messageIdToEntryId,
|
|
864
|
+
recordDroppedMessage
|
|
865
|
+
};
|
|
866
|
+
emitEntry("session_info", headerTimestamp, typeof session.title === "string" && session.title.length > 0 ? { name: session.title } : {});
|
|
867
|
+
for (const [msgIndex, msg] of messages.entries()) {
|
|
868
|
+
const policy = policies[msgIndex];
|
|
869
|
+
const time = isRecord(msg.data.time) ? msg.data.time : {};
|
|
870
|
+
const createdMs = asFinite(time.created) ?? sessionCreatedMs;
|
|
871
|
+
const createdIso = createdMs !== void 0 ? new Date(createdMs).toISOString() : headerTimestamp;
|
|
872
|
+
const emitForMessage = (type, timestamp, rest) => {
|
|
873
|
+
const entryId = emitEntry(type, timestamp, rest);
|
|
874
|
+
if (!messageIdToEntryId.has(msg.id)) messageIdToEntryId.set(msg.id, entryId);
|
|
875
|
+
};
|
|
876
|
+
convertMessageByPolicy(msg, policy, createdMs, createdIso, emitForMessage, deps);
|
|
877
|
+
}
|
|
878
|
+
degradations.push(...dropGroups.values());
|
|
879
|
+
return { header, entries, degradations };
|
|
880
|
+
}
|
|
881
|
+
function convertMessageByPolicy(msg, policy, createdMs, createdIso, emitForMessage, deps) {
|
|
882
|
+
switch (policy) {
|
|
883
|
+
case "realUserInput":
|
|
884
|
+
convertUserMessage(msg, createdMs, createdIso, emitForMessage, deps.degradations, deps.compactionDisposition);
|
|
885
|
+
break;
|
|
886
|
+
case "visibleAssistant":
|
|
887
|
+
convertAssistantMessage(msg, msg.data, createdMs, createdIso, emitForMessage, deps.degradations, deps.compactionDisposition);
|
|
888
|
+
break;
|
|
889
|
+
case "compactSummary":
|
|
890
|
+
emitCompactSummary(msg, createdMs, createdIso, emitForMessage, deps);
|
|
891
|
+
break;
|
|
892
|
+
case "providerContextOnly":
|
|
893
|
+
case "hiddenSynthetic":
|
|
894
|
+
case "timelineOnly":
|
|
895
|
+
deps.recordDroppedMessage({ code: droppedMessageCode(msg.data), count: 1, ...degradationMeta(msg.data) });
|
|
896
|
+
break;
|
|
897
|
+
case "unclassified":
|
|
898
|
+
deps.recordDroppedMessage({
|
|
899
|
+
code: "unclassified",
|
|
900
|
+
count: 1,
|
|
901
|
+
...degradationMeta(msg.data),
|
|
902
|
+
sample: { messageId: msg.id, preview: messageTextPreview(msg.parts).slice(0, DEGRADATION_PREVIEW_MAX) }
|
|
903
|
+
});
|
|
904
|
+
break;
|
|
905
|
+
default: {
|
|
906
|
+
const exhaustive = policy;
|
|
907
|
+
throw new Error(`\u672A\u5904\u7406\u7684\u6295\u5F71\u7B56\u7565\uFF1A${String(exhaustive)}`);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
function emitCompactSummary(msg, createdMs, createdIso, emitForMessage, deps) {
|
|
912
|
+
const body = summaryBodyOf(msg.data);
|
|
913
|
+
if (body === void 0) {
|
|
914
|
+
convertUserMessage(msg, createdMs, createdIso, emitForMessage, deps.degradations, deps.compactionDisposition);
|
|
915
|
+
deps.degradations.push({ code: "compaction_unlinked", count: 1, ...degradationMeta(msg.data) });
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
const anchor = deps.linkedParts.get(msg.id)?.[0];
|
|
919
|
+
let tokensBefore;
|
|
920
|
+
let anchorMs;
|
|
921
|
+
let tailId;
|
|
922
|
+
if (anchor !== void 0) {
|
|
923
|
+
tokensBefore = asFinite(anchor.preCompactTokenCount);
|
|
924
|
+
anchorMs = partStartTime(anchor);
|
|
925
|
+
tailId = asNonEmptyString2(anchor.tail_start_id);
|
|
926
|
+
}
|
|
927
|
+
const tsMs = anchorMs ?? createdMs;
|
|
928
|
+
const tier1 = tailId !== void 0 ? deps.messageIdToEntryId.get(tailId) : void 0;
|
|
929
|
+
const ownId = deps.chain.peek();
|
|
930
|
+
emitForMessage("compaction", tsMs !== void 0 ? new Date(tsMs).toISOString() : createdIso, {
|
|
931
|
+
summary: body,
|
|
932
|
+
firstKeptEntryId: resolveFirstKeptEntryId(tier1, deps.chain.last(), ownId),
|
|
933
|
+
...tokensBefore !== void 0 && { tokensBefore },
|
|
934
|
+
...anchor !== void 0 && { details: anchor }
|
|
935
|
+
});
|
|
936
|
+
for (const part of msg.parts) {
|
|
937
|
+
if (part.type === "compaction" && deps.compactionDisposition.get(part) === "dangling") {
|
|
938
|
+
handleNonTextPart(part, msg, createdMs, createdIso, emitForMessage, deps.degradations, deps.compactionDisposition);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
function convertUserMessage(msg, createdMs, createdIso, emitEntry, degradations, compactionDisposition) {
|
|
943
|
+
const content = [];
|
|
944
|
+
for (const part of msg.parts) {
|
|
945
|
+
const type = part.type;
|
|
946
|
+
if (type === "text") {
|
|
947
|
+
pushTextPart(part, content, msg.id, degradations);
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
if (type === "file") {
|
|
951
|
+
pushDegradation(degradations, `user \u6D88\u606F file part \u4E22\u5F03\uFF08zcode-artifact \u5F15\u7528\u65E0\u6CD5\u642C\u8FD0\uFF0CD6\uFF09\uFF1Amessage=${msg.id}`, msg.id);
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
handleNonTextPart(part, msg, createdMs, createdIso, emitEntry, degradations, compactionDisposition);
|
|
955
|
+
}
|
|
956
|
+
emitEntry("message", createdIso, {
|
|
957
|
+
message: { role: "user", content, ...createdMs !== void 0 && { timestamp: createdMs } }
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
function convertAssistantMessage(msg, data, createdMs, createdIso, emitEntry, degradations, compactionDisposition) {
|
|
961
|
+
const providerId = typeof data.providerId === "string" ? data.providerId : void 0;
|
|
962
|
+
const modelId = typeof data.modelId === "string" ? data.modelId : void 0;
|
|
963
|
+
let segment = newSegment();
|
|
964
|
+
const closeSegment = (finishReason, usage) => {
|
|
965
|
+
if (segment.content.length === 0) return;
|
|
966
|
+
const tsMs = segment.startMs ?? createdMs;
|
|
967
|
+
const tsIso = tsMs !== void 0 ? new Date(tsMs).toISOString() : createdIso;
|
|
968
|
+
emitEntry("message", tsIso, {
|
|
969
|
+
message: {
|
|
970
|
+
role: "assistant",
|
|
971
|
+
content: segment.content,
|
|
972
|
+
...tsMs !== void 0 && { timestamp: tsMs },
|
|
973
|
+
...providerId !== void 0 && { provider: providerId },
|
|
974
|
+
...modelId !== void 0 && { model: modelId },
|
|
975
|
+
// 产物不变量门:usage 恒在场(缺 → 零值兜底,zeroUsage 注释)
|
|
976
|
+
usage: usage ?? zeroUsage(),
|
|
977
|
+
// 段有 step-finish 收口 → 采信其 finish;未收口段(取消/失败尾段)→ 消息级
|
|
978
|
+
// data.error 优先,无 error 证据才 'stop' 保底(T3c 事故补强)
|
|
979
|
+
stopReason: finishReason !== void 0 ? mapStopReason(finishReason) : unsealedStopReason(data)
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
for (const toolResult of segment.toolResults) {
|
|
983
|
+
emitEntry("message", tsIso, { message: { ...toolResult, ...tsMs !== void 0 && { timestamp: tsMs } } });
|
|
984
|
+
}
|
|
985
|
+
segment = newSegment();
|
|
986
|
+
};
|
|
987
|
+
for (const part of msg.parts) {
|
|
988
|
+
const type = part.type;
|
|
989
|
+
if (type === "step-start") {
|
|
990
|
+
closeSegment(void 0, void 0);
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
if (type === "step-finish") {
|
|
994
|
+
const { usage, degradation } = usageFromStepFinish(part);
|
|
995
|
+
if (degradation !== void 0) pushDegradation(degradations, `${degradation}\uFF1Amessage=${msg.id}`, msg.id);
|
|
996
|
+
closeSegment(part.reason, usage);
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
if (type === "text") {
|
|
1000
|
+
if (segment.startMs === void 0) segment.startMs = partStartTime(part);
|
|
1001
|
+
pushTextPart(part, segment.content, msg.id, degradations);
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
if (type === "reasoning") {
|
|
1005
|
+
if (segment.startMs === void 0) segment.startMs = partStartTime(part);
|
|
1006
|
+
const thinking = part.text;
|
|
1007
|
+
if (typeof thinking !== "string") {
|
|
1008
|
+
pushDegradation(degradations, `reasoning part \u6587\u672C\u5F62\u6001\u5F02\u5E38\uFF08${typeof thinking}\uFF09\u8DF3\u8FC7\uFF1Amessage=${msg.id}`, msg.id);
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
segment.content.push({ type: "thinking", thinking });
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
if (type === "tool") {
|
|
1015
|
+
convertToolPart(part, msg, segment, degradations);
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
handleNonTextPart(part, msg, createdMs, createdIso, emitEntry, degradations, compactionDisposition);
|
|
1019
|
+
}
|
|
1020
|
+
closeSegment(void 0, void 0);
|
|
1021
|
+
}
|
|
1022
|
+
function toolCallArguments(input, toolName, callId, msgId, degradations) {
|
|
1023
|
+
if (input === void 0 || input === null) return {};
|
|
1024
|
+
if (isRecord(input)) return input;
|
|
1025
|
+
pushDegradation(
|
|
1026
|
+
degradations,
|
|
1027
|
+
`tool\u300C${toolName}\u300Dinput \u5F62\u6001\u8D85\u51FA\u5DF2\u9A8C\u8BC1\u57DF\uFF08typeof ${typeof input}\uFF09\uFF0C\u6309\u7A7A\u5165\u53C2\u8F6C\u6362\uFF1AcallID=${callId}`,
|
|
1028
|
+
msgId
|
|
1029
|
+
);
|
|
1030
|
+
return {};
|
|
1031
|
+
}
|
|
1032
|
+
function registerTruncatedOutput(state, msgData, degradations) {
|
|
1033
|
+
const partMetadata = isRecord(state.metadata) ? state.metadata : void 0;
|
|
1034
|
+
const serialization = partMetadata !== void 0 && isRecord(partMetadata.serialization) ? partMetadata.serialization : void 0;
|
|
1035
|
+
if (serialization?.truncated === true) {
|
|
1036
|
+
degradations.push({ code: "truncated_output", count: 1, ...degradationMeta(msgData) });
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
function convertToolPart(part, msg, segment, degradations) {
|
|
1040
|
+
const callId = part.callID;
|
|
1041
|
+
const toolName = part.tool;
|
|
1042
|
+
const state = part.state;
|
|
1043
|
+
if (typeof callId !== "string" || typeof toolName !== "string" || !isRecord(state)) {
|
|
1044
|
+
pushDegradation(degradations, `tool part \u7ED3\u6784\u5F02\u5E38\uFF08callID/tool/state \u5F62\u6001\uFF09\u6574\u5BF9\u4E22\u5F03\uFF1Amessage=${msg.id}`, msg.id);
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
const status = state.status;
|
|
1048
|
+
if (status !== "completed" && status !== "error") {
|
|
1049
|
+
pushDegradation(
|
|
1050
|
+
degradations,
|
|
1051
|
+
`\u672A\u5B8C\u6210 tool\uFF08status=${String(status)}\uFF09\u6574\u5BF9\u4E22\u5F03\uFF08D7\uFF0C\u9632 dangling toolCall\uFF09\uFF1AcallID=${callId} tool=${toolName}`,
|
|
1052
|
+
msg.id
|
|
1053
|
+
);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
const args = toolCallArguments(state.input, toolName, callId, msg.id, degradations);
|
|
1057
|
+
const mappedName = mapToolName(toolName);
|
|
1058
|
+
segment.content.push({ type: "toolCall", id: callId, name: mappedName, arguments: args });
|
|
1059
|
+
const shape = toolResultShape(state, part, msg.id, degradations);
|
|
1060
|
+
registerTruncatedOutput(state, msg.data, degradations);
|
|
1061
|
+
segment.toolResults.push({
|
|
1062
|
+
role: "toolResult",
|
|
1063
|
+
toolCallId: callId,
|
|
1064
|
+
toolName: mappedName,
|
|
1065
|
+
content: shape.content,
|
|
1066
|
+
...shape.details !== void 0 && { details: shape.details },
|
|
1067
|
+
...shape.isError && { isError: true }
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
function handleNonTextPart(part, msg, createdMs, createdIso, emitEntry, degradations, compactionDisposition) {
|
|
1071
|
+
const type = part.type;
|
|
1072
|
+
if (type === "compaction") {
|
|
1073
|
+
const disposition = compactionDisposition.get(part);
|
|
1074
|
+
if (disposition === "merged") return;
|
|
1075
|
+
if (disposition === "dangling") {
|
|
1076
|
+
degradations.push({ code: "compaction_unlinked", count: 1, ...degradationMeta(msg.data) });
|
|
1077
|
+
}
|
|
1078
|
+
const tsMs = partStartTime(part) ?? createdMs;
|
|
1079
|
+
emitEntry("custom", tsMs !== void 0 ? new Date(tsMs).toISOString() : createdIso, {
|
|
1080
|
+
customType: "zcode-import:compaction",
|
|
1081
|
+
data: part
|
|
1082
|
+
});
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
if (type === "timeline") {
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
pushDegradation(degradations, `\u672A\u77E5 part type\u300C${String(type)}\u300D\u8DF3\u8FC7\uFF08\u524D\u5411\u517C\u5BB9\uFF09\uFF1Amessage=${msg.id}`, msg.id);
|
|
1089
|
+
}
|
|
1090
|
+
function pushTextPart(part, content, msgId, degradations) {
|
|
1091
|
+
const text = part.text;
|
|
1092
|
+
if (typeof text !== "string") {
|
|
1093
|
+
pushDegradation(degradations, `text part \u6587\u672C\u5F62\u6001\u5F02\u5E38\uFF08${typeof text}\uFF09\u8DF3\u8FC7\uFF1Amessage=${msgId}`, msgId);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
content.push({ type: "text", text });
|
|
1097
|
+
}
|
|
1098
|
+
function partStartTime(part) {
|
|
1099
|
+
return isRecord(part.time) ? asFinite(part.time.start) : void 0;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// src/read.ts
|
|
1103
|
+
async function readZcodeSession(dbPath, sessionId) {
|
|
1104
|
+
const handle = await openZcodeSessionDb(dbPath);
|
|
1105
|
+
try {
|
|
1106
|
+
const row = handle.db.getSessionRow(sessionId);
|
|
1107
|
+
if (!row) {
|
|
1108
|
+
throw new Error(
|
|
1109
|
+
`\u8BE5\u4F1A\u8BDD\u5DF2\u4E0D\u5728 zcode \u5E93\u4E2D\uFF08sessionId=${sessionId}\uFF09\uFF1A\u8BE5 id \u53EF\u80FD\u5DF2\u88AB zcode \u4FA7\u56DE\u6536\u6216\u4ECE\u672A\u843D\u5E93`
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
const transcript = handle.db.getSessionTranscript(sessionId);
|
|
1113
|
+
return convertZcodeTranscript(transcript, { id: sessionId, title: row.title, timeCreated: row.timeCreated });
|
|
1114
|
+
} finally {
|
|
1115
|
+
handle.dispose();
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
export {
|
|
1119
|
+
SqliteUnreadableError,
|
|
1120
|
+
ZcodeSchemaDriftError,
|
|
1121
|
+
convertZcodeTranscript,
|
|
1122
|
+
hostZcodeDbPath,
|
|
1123
|
+
normalizeZcodeSessionId,
|
|
1124
|
+
openZcodeSessionDb,
|
|
1125
|
+
readZcodeSession,
|
|
1126
|
+
zcodeCandidateKey,
|
|
1127
|
+
zcodeImportDbAllowlist,
|
|
1128
|
+
zcodeIsolatedDbPath
|
|
1129
|
+
};
|