mu-agent 0.1.8 → 0.1.13
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/CHANGELOG.md +121 -0
- package/README.md +34 -30
- package/dist/bundle/chunks/anthropic-messages-4PAJY4R6.js +6 -0
- package/dist/bundle/chunks/anthropic.js +1 -1
- package/dist/bundle/chunks/azure-openai-responses-Y4BBWQ3N.js +2 -0
- package/dist/bundle/chunks/bedrock-converse-stream.js +12 -9
- package/dist/bundle/chunks/{chunk-NG4PO5FP.js → chunk-3J3HSHLX.js} +128 -110
- package/dist/bundle/chunks/{chunk-TRDNDS6A.js → chunk-3V45WARH.js} +6 -3
- package/dist/bundle/chunks/{chunk-S3GGRYIO.js → chunk-5AGIVGSV.js} +1 -1
- package/dist/bundle/chunks/chunk-6QGV3IWD.js +6 -0
- package/dist/bundle/chunks/{chunk-4OBXYPH4.js → chunk-6Z2347ML.js} +1 -1
- package/dist/bundle/chunks/{chunk-F5ZABL6Z.js → chunk-7CBJINYJ.js} +2 -2
- package/dist/bundle/chunks/{chunk-DRQDKNXA.js → chunk-7DDKXS2Q.js} +1 -1
- package/dist/bundle/chunks/{chunk-H3QXYIF7.js → chunk-CF5GWZHW.js} +1 -1
- package/dist/bundle/chunks/{chunk-RLZVFGW4.js → chunk-DYTENXHX.js} +29 -92
- package/dist/bundle/chunks/{chunk-NTSWBG4X.js → chunk-FLHKNMLQ.js} +1 -1
- package/dist/bundle/chunks/chunk-FTJT2X6E.js +2 -0
- package/dist/bundle/chunks/{chunk-4L3WN2XY.js → chunk-JXWJOIH4.js} +1 -1
- package/dist/bundle/chunks/{chunk-TAOL3JFA.js → chunk-KIDDRTZP.js} +4 -4
- package/dist/bundle/chunks/chunk-LHRPMXVI.js +2 -0
- package/dist/bundle/chunks/chunk-LMNWTX2L.js +11 -0
- package/dist/bundle/chunks/chunk-LPXNMA2W.js +2 -0
- package/dist/bundle/chunks/chunk-LT2C56HV.js +130 -0
- package/dist/bundle/chunks/{chunk-6CYCI3EJ.js → chunk-LVVZS2NO.js} +1 -1
- package/dist/bundle/chunks/chunk-MF3VB62R.js +91 -0
- package/dist/bundle/chunks/chunk-NN4BEPYX.js +2 -0
- package/dist/bundle/chunks/{chunk-LD2P6SFZ.js → chunk-PMRZO46Y.js} +1 -1
- package/dist/bundle/chunks/chunk-R4VPBBDS.js +2 -0
- package/dist/bundle/chunks/chunk-RDZVAGPR.js +2 -0
- package/dist/bundle/chunks/chunk-TMTOQ7H7.js +2 -0
- package/dist/bundle/chunks/chunk-UKVNB6C4.js +2 -0
- package/dist/bundle/chunks/chunk-URBBMV3M.js +9 -0
- package/dist/bundle/chunks/chunk-WCGU75WO.js +11 -0
- package/dist/bundle/chunks/chunk-WCJNJQO5.js +9 -0
- package/dist/bundle/chunks/chunk-WDURT4ZM.js +2 -0
- package/dist/bundle/chunks/{cli-AOVD3SSZ.js → cli-M4YBZZ6Q.js} +4 -4
- package/dist/bundle/chunks/cloudflare-workers-ai-system-one-MEZHQVNP.js +2 -0
- package/dist/bundle/chunks/codemode-worker.js +117 -13
- package/dist/bundle/chunks/easter-egg-3d-VFY3YRQR.js +2 -0
- package/dist/bundle/chunks/execute-FKI7Y4XR.js +499 -0
- package/dist/bundle/chunks/github-copilot.js +1 -1
- package/dist/bundle/chunks/{google-generative-ai-XMFHGQC5.js → google-generative-ai-D2VJQBTK.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-4XRRNP74.js → google-vertex-SQG2BO6C.js} +1 -1
- package/dist/bundle/chunks/https-proxy-agent-LVOO5TPL.js +13 -0
- package/dist/bundle/chunks/image-resize-worker.js +1 -1
- package/dist/bundle/chunks/{jiti-loader-C3NU2SDH.js → jiti-loader-GL4HJFAD.js} +1 -1
- package/dist/bundle/chunks/{jiti-static-loader-E6ANQD5T.js → jiti-static-loader-2RCDI6B4.js} +1 -1
- package/dist/bundle/chunks/{lib-TEDK3E5I.js → lib-4AVRLBHM.js} +1 -1
- package/dist/bundle/chunks/{llama-cpp-classify-XT44PI3C.js → llama-cpp-classify-5YHLY3RC.js} +2 -2
- package/dist/bundle/chunks/mistral-conversations-BDARVJTT.js +5 -0
- package/dist/bundle/chunks/{multipart-parser-SQ6GDJL7.js → multipart-parser-FMGQ7342.js} +1 -1
- package/dist/bundle/chunks/{node-TWVVAGPO.js → node-AUOGOBCN.js} +1 -1
- package/dist/bundle/chunks/{node-N5BSDOA5.js → node-KCMANYC2.js} +1 -1
- package/dist/bundle/chunks/openai-chatgpt.js +1 -1
- package/dist/bundle/chunks/openai-codex-responses-CECGTDYD.js +10 -0
- package/dist/bundle/chunks/openai-codex.js +1 -1
- package/dist/bundle/chunks/openai-completions-UOA7MELD.js +7 -0
- package/dist/bundle/chunks/openai-decisions-HLM372N6.js +5 -0
- package/dist/bundle/chunks/openai-responses-TII7ZY5X.js +3 -0
- package/dist/bundle/chunks/{openrouter-images-ZVXWCSIM.js → openrouter-images-7T452AS5.js} +1 -1
- package/dist/bundle/chunks/{pi-messages-TM5SOOIW.js → pi-messages-2UDKLKOO.js} +1 -1
- package/dist/bundle/chunks/runtime-IKE5OQIW.js +2 -0
- package/dist/bundle/chunks/{src-UK77SYZE.js → src-QKGKDFSU.js} +1 -1
- package/dist/bundle/chunks/typesafe-system-one-KNCICMO5.js +2 -0
- package/dist/bundle/chunks/virtual-modules-TJPHLTRT.js +2 -0
- package/dist/bundle/cli-runtime.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/core/export-html/template.js +21 -16
- package/dist/modes/interactive/theme/dark.json +1 -1
- package/dist/modes/interactive/theme/light.json +1 -1
- package/dist/modes/interactive/theme/theme-schema.d.ts +28 -0
- package/dist/modes/interactive/theme/theme-schema.d.ts.map +1 -0
- package/dist/modes/interactive/theme/theme-schema.js +105 -0
- package/dist/modes/interactive/theme/theme-schema.js.map +1 -0
- package/dist/modes/interactive/theme/theme-tokens.d.ts +257 -0
- package/dist/modes/interactive/theme/theme-tokens.d.ts.map +1 -0
- package/dist/modes/interactive/theme/theme-tokens.js +94 -0
- package/dist/modes/interactive/theme/theme-tokens.js.map +1 -0
- package/dist/modes/interactive/theme/theme.d.ts +13 -16
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +22 -29
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/docs/cli-integration.md +1 -1
- package/docs/cli.md +22 -10
- package/docs/codemode.md +29 -6
- package/docs/compaction.md +5 -5
- package/docs/custom-provider.md +53 -1
- package/docs/docs.json +8 -0
- package/docs/environment-variables.md +3 -1
- package/docs/extensions.md +122 -13
- package/docs/json.md +3 -3
- package/docs/keybindings.md +5 -4
- package/docs/llama-cpp.md +9 -2
- package/docs/mcp.md +4 -2
- package/docs/message-types.md +22 -4
- package/docs/models-configuration.md +187 -0
- package/docs/models.md +18 -2
- package/docs/nvidia-rtx-llama-cpp.md +96 -0
- package/docs/providers.md +47 -0
- package/docs/sessions.md +1 -1
- package/docs/settings.md +9 -6
- package/docs/terminal-setup.md +14 -0
- package/docs/themes.md +4 -2
- package/docs/tui.md +71 -1
- package/docs/usage.md +1 -1
- package/examples/README.md +0 -3
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/dynamic-resources/dynamic.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/minimal-mode.ts +4 -5
- package/examples/extensions/overlay-test.ts +2 -2
- package/examples/extensions/plan-mode/index.ts +6 -2
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/index.ts +4 -6
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/judge/dist/auth.js +91 -35
- package/judge/dist/chrome.js +2 -2
- package/judge/dist/import.js +852 -175
- package/judge/dist/setup.js +240 -153
- package/judge/manifest.json +2512 -546
- package/judge/mu/chunk-3NQ5BIBI.js +4773 -0
- package/judge/mu/chunk-SKGYWHW6.js +51 -0
- package/judge/mu/chunk-WQM4MV6L.js +58 -0
- package/judge/mu/cloud-code-V7FOGB4J.js +22802 -0
- package/judge/mu/dist-75PS4OC4.js +1283 -0
- package/judge/{dist/kyrn-judge.js → mu/index.js} +5365 -32809
- package/judge/mu/multipart-parser-ZDXVIOOP.js +372 -0
- package/judge/mu/src-TVVA5ZK2.js +1409 -0
- package/judge/themes/mu-dark.json +80 -0
- package/judge/themes/mu-light.json +80 -0
- package/kyrn/bin/kyrn-doctor +44 -19
- package/kyrn/bin/kyrn-judge-local +43 -14
- package/kyrn/bin/kyrn-ledger +16 -1
- package/kyrn/bin/mu.mjs +142 -61
- package/kyrn/local-judge/server.py +26 -14
- package/package.json +8 -6
- package/dist/bundle/chunks/anthropic-messages-MI4QHRXW.js +0 -6
- package/dist/bundle/chunks/azure-openai-responses-E3QGN4VN.js +0 -2
- package/dist/bundle/chunks/chunk-33KL6IIQ.js +0 -20
- package/dist/bundle/chunks/chunk-A3JYRWB6.js +0 -2
- package/dist/bundle/chunks/chunk-C47CLQZK.js +0 -2
- package/dist/bundle/chunks/chunk-HQHD6PZI.js +0 -2
- package/dist/bundle/chunks/chunk-KJUDFE6Y.js +0 -11
- package/dist/bundle/chunks/chunk-TE3GUV6H.js +0 -9
- package/dist/bundle/chunks/chunk-VIV3CWXG.js +0 -2
- package/dist/bundle/chunks/chunk-WE4MXUQS.js +0 -11
- package/dist/bundle/chunks/chunk-Y2PP32XV.js +0 -2
- package/dist/bundle/chunks/chunk-YLY6CKPK.js +0 -132
- package/dist/bundle/chunks/chunk-ZE5FMF3V.js +0 -99
- package/dist/bundle/chunks/cloudflare-workers-ai-system-one-NM3JRRWQ.js +0 -2
- package/dist/bundle/chunks/easter-egg-3d-G2LUBEMZ.js +0 -2
- package/dist/bundle/chunks/execute-MZVUC6BB.js +0 -388
- package/dist/bundle/chunks/https-proxy-agent-2VXB7436.js +0 -13
- package/dist/bundle/chunks/mistral-conversations-Q22M3IBG.js +0 -5
- package/dist/bundle/chunks/openai-codex-responses-4MYQSO5E.js +0 -10
- package/dist/bundle/chunks/openai-completions-OSRCWAAA.js +0 -7
- package/dist/bundle/chunks/openai-responses-UHF3DHNG.js +0 -3
- package/dist/bundle/chunks/runtime-VJMMDLVH.js +0 -2
- package/dist/bundle/chunks/typesafe-system-one-PRR5K6CI.js +0 -2
- package/dist/bundle/chunks/virtual-modules-RJXJGCPI.js +0 -2
- package/dist/modes/interactive/theme/theme-json.d.ts +0 -85
- package/dist/modes/interactive/theme/theme-json.d.ts.map +0 -1
- package/dist/modes/interactive/theme/theme-json.js +0 -132
- package/dist/modes/interactive/theme/theme-json.js.map +0 -1
- package/dist/modes/interactive/theme/theme-schema.json +0 -361
- package/examples/plugins/pi-example-plugin/README.md +0 -38
- package/examples/plugins/pi-example-plugin/package.json +0 -10
- package/examples/plugins/pi-example-plugin/src/contract.ts +0 -13
- package/examples/plugins/pi-example-plugin/src/session.ts +0 -24
- package/examples/plugins/pi-example-plugin/src/tui.ts +0 -31
package/judge/dist/import.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import { createRequire as __muCreateRequire } from "node:module"; const require = __muCreateRequire(import.meta.url);
|
|
2
2
|
|
|
3
3
|
// packages/kyrn-judge/src/import/command.ts
|
|
4
|
-
import { readFileSync } from "node:fs";
|
|
5
|
-
import { homedir as
|
|
6
|
-
import { join as
|
|
4
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
5
|
+
import { homedir as homedir4 } from "node:os";
|
|
6
|
+
import { join as join4, sep } from "node:path";
|
|
7
|
+
|
|
8
|
+
// packages/kyrn-judge/src/language.ts
|
|
9
|
+
function count(n, one, many = `${one}s`) {
|
|
10
|
+
return `${n} ${n === 1 ? one : many}`;
|
|
11
|
+
}
|
|
7
12
|
|
|
8
13
|
// packages/kyrn-judge/src/import/index.ts
|
|
9
|
-
import { mkdirSync, readdirSync as readdirSync2, realpathSync, statSync as
|
|
10
|
-
import { homedir as
|
|
11
|
-
import { join as
|
|
14
|
+
import { mkdirSync, readdirSync as readdirSync2, readFileSync, realpathSync, renameSync as renameSync2, statSync as statSync5, writeFileSync } from "node:fs";
|
|
15
|
+
import { homedir as homedir3 } from "node:os";
|
|
16
|
+
import { join as join3 } from "node:path";
|
|
12
17
|
|
|
13
18
|
// packages/kyrn-judge/src/import/claude-code.ts
|
|
14
19
|
import { statSync as statSync2 } from "node:fs";
|
|
15
20
|
import { basename } from "node:path";
|
|
16
21
|
|
|
17
22
|
// packages/kyrn-judge/src/import/jsonl.ts
|
|
18
|
-
import { closeSync, createReadStream, openSync, readSync } from "node:fs";
|
|
23
|
+
import { closeSync, createReadStream, fstatSync, openSync, readSync } from "node:fs";
|
|
19
24
|
var MAX_LINE_BYTES = 64 * 1024 * 1024;
|
|
20
25
|
function parse(bytes) {
|
|
21
26
|
const text = bytes.toString("utf8").trim();
|
|
@@ -69,18 +74,18 @@ function readHead(path, visit, limit = 1024 * 1024) {
|
|
|
69
74
|
try {
|
|
70
75
|
const chunk = Buffer.allocUnsafe(64 * 1024);
|
|
71
76
|
let pending = Buffer.alloc(0);
|
|
72
|
-
let
|
|
73
|
-
while (
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
77
|
+
let read2 = 0;
|
|
78
|
+
while (read2 < limit) {
|
|
79
|
+
const count2 = readSync(fd, chunk, 0, Math.min(chunk.length, limit - read2), null);
|
|
80
|
+
if (count2 === 0) {
|
|
76
81
|
if (!blank(pending)) {
|
|
77
82
|
const last = parse(pending);
|
|
78
83
|
if ("value" in last) visit(last.value);
|
|
79
84
|
}
|
|
80
85
|
return;
|
|
81
86
|
}
|
|
82
|
-
|
|
83
|
-
pending = Buffer.concat([pending, chunk.subarray(0,
|
|
87
|
+
read2 += count2;
|
|
88
|
+
pending = Buffer.concat([pending, chunk.subarray(0, count2)]);
|
|
84
89
|
let start = 0;
|
|
85
90
|
for (let newline = pending.indexOf(10, start); newline !== -1; newline = pending.indexOf(10, start)) {
|
|
86
91
|
const line = pending.subarray(start, newline);
|
|
@@ -95,6 +100,29 @@ function readHead(path, visit, limit = 1024 * 1024) {
|
|
|
95
100
|
closeSync(fd);
|
|
96
101
|
}
|
|
97
102
|
}
|
|
103
|
+
function readTail(path, limit = 256 * 1024) {
|
|
104
|
+
const fd = openSync(path, "r");
|
|
105
|
+
try {
|
|
106
|
+
const size = fstatSync(fd).size;
|
|
107
|
+
const start = Math.max(0, size - limit);
|
|
108
|
+
const buffer = Buffer.allocUnsafe(size - start);
|
|
109
|
+
const count2 = readSync(fd, buffer, 0, buffer.length, start);
|
|
110
|
+
let text = buffer.subarray(0, count2);
|
|
111
|
+
if (start > 0) {
|
|
112
|
+
const newline = text.indexOf(10);
|
|
113
|
+
text = newline === -1 ? Buffer.alloc(0) : text.subarray(newline + 1);
|
|
114
|
+
}
|
|
115
|
+
const values = [];
|
|
116
|
+
for (const line of text.toString("utf8").split("\n")) {
|
|
117
|
+
if (!line.trim()) continue;
|
|
118
|
+
const parsed = parse(Buffer.from(line));
|
|
119
|
+
if ("value" in parsed) values.push(parsed.value);
|
|
120
|
+
}
|
|
121
|
+
return values;
|
|
122
|
+
} finally {
|
|
123
|
+
closeSync(fd);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
98
126
|
function record(value) {
|
|
99
127
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
100
128
|
}
|
|
@@ -106,16 +134,24 @@ function list(value) {
|
|
|
106
134
|
}
|
|
107
135
|
|
|
108
136
|
// packages/kyrn-judge/src/import/session.ts
|
|
109
|
-
import { randomBytes, randomUUID } from "node:crypto";
|
|
110
|
-
import { closeSync as closeSync2, openSync as openSync2, readdirSync, statSync, writeSync } from "node:fs";
|
|
137
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
138
|
+
import { closeSync as closeSync2, openSync as openSync2, readdirSync, renameSync, rmSync, statSync, writeSync } from "node:fs";
|
|
111
139
|
import { homedir } from "node:os";
|
|
112
140
|
import { isAbsolute, join, resolve } from "node:path";
|
|
113
|
-
var TOOL_NAMES = {
|
|
141
|
+
var TOOL_NAMES = {
|
|
142
|
+
"claude-code": "Claude Code",
|
|
143
|
+
codex: "Codex",
|
|
144
|
+
zcode: "ZCode"
|
|
145
|
+
};
|
|
146
|
+
var isImportTool = (value) => typeof value === "string" && Object.hasOwn(TOOL_NAMES, value);
|
|
114
147
|
var IMPORT_MARKER = "mu.import";
|
|
115
148
|
var IMPORT_CONTEXT = "mu.import.context";
|
|
116
149
|
var SESSION_VERSION = 3;
|
|
117
150
|
var MAX_RESULT_CHARS = 1e5;
|
|
118
151
|
var IMAGE_MARKER = "(image not imported)";
|
|
152
|
+
var MAX_IMAGE_CHARS = 7 * 1024 * 1024;
|
|
153
|
+
var MAX_IMAGES_CHARS = 48 * 1024 * 1024;
|
|
154
|
+
var IMAGE_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
|
|
119
155
|
var NO_RESULT = "No result was recorded for this call.";
|
|
120
156
|
function emptyCounts() {
|
|
121
157
|
return {
|
|
@@ -125,6 +161,8 @@ function emptyCounts() {
|
|
|
125
161
|
assistant: 0,
|
|
126
162
|
toolCalls: 0,
|
|
127
163
|
toolResults: 0,
|
|
164
|
+
thinking: 0,
|
|
165
|
+
images: 0,
|
|
128
166
|
context: 0,
|
|
129
167
|
compactions: 0,
|
|
130
168
|
missingResults: 0,
|
|
@@ -188,8 +226,7 @@ function readOrigin(file) {
|
|
|
188
226
|
}
|
|
189
227
|
const details = record(marker?.details);
|
|
190
228
|
if (marker?.type !== "custom_message" || marker.customType !== IMPORT_MARKER || !details) return void 0;
|
|
191
|
-
|
|
192
|
-
if (tool !== "claude-code" && tool !== "codex") return void 0;
|
|
229
|
+
if (!isImportTool(details.tool)) return void 0;
|
|
193
230
|
if (typeof details.source !== "string") return void 0;
|
|
194
231
|
return { sessionId: str(header?.id) ?? "", origin: details };
|
|
195
232
|
}
|
|
@@ -216,15 +253,19 @@ function findImports(store) {
|
|
|
216
253
|
scan(sessionsRoot(store), 1);
|
|
217
254
|
const found = [];
|
|
218
255
|
for (const file of files) {
|
|
219
|
-
const
|
|
220
|
-
if (
|
|
256
|
+
const read2 = readOrigin(file);
|
|
257
|
+
if (read2) found.push({ sessionFile: file, sessionId: read2.sessionId, origin: read2.origin });
|
|
221
258
|
}
|
|
222
259
|
return found;
|
|
223
260
|
}
|
|
224
261
|
function findImport(imports, tool, sourceId, source) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
262
|
+
let latest;
|
|
263
|
+
for (const found of imports) {
|
|
264
|
+
if (found.origin.tool !== tool) continue;
|
|
265
|
+
if (!(sourceId !== "" && found.origin.sourceId === sourceId || found.origin.source === source)) continue;
|
|
266
|
+
if (!latest || String(found.origin.importedAt) > String(latest.origin.importedAt)) latest = found;
|
|
267
|
+
}
|
|
268
|
+
return latest;
|
|
228
269
|
}
|
|
229
270
|
function firstLine(text, max = 80) {
|
|
230
271
|
const line = text.split(/\r?\n/).map((part) => part.replace(/\s+/g, " ").trim()).find((part) => part.length > 0) ?? "";
|
|
@@ -312,13 +353,13 @@ function toolArguments(raw) {
|
|
|
312
353
|
}
|
|
313
354
|
function unknownBlockText(block) {
|
|
314
355
|
const { type, ...rest } = block;
|
|
315
|
-
let
|
|
356
|
+
let json2;
|
|
316
357
|
try {
|
|
317
|
-
|
|
358
|
+
json2 = JSON.stringify(rest);
|
|
318
359
|
} catch {
|
|
319
|
-
|
|
360
|
+
json2 = "";
|
|
320
361
|
}
|
|
321
|
-
return `[${typeof type === "string" ? type : "block"}] ${clip(
|
|
362
|
+
return `[${typeof type === "string" ? type : "block"}] ${clip(json2, 2e3).text}`.trim();
|
|
322
363
|
}
|
|
323
364
|
var ZERO_USAGE = {
|
|
324
365
|
input: 0,
|
|
@@ -329,6 +370,29 @@ var ZERO_USAGE = {
|
|
|
329
370
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
|
|
330
371
|
};
|
|
331
372
|
var IMAGE = { type: "image", data: "", mimeType: "image/png" };
|
|
373
|
+
function imageOf(mimeType, data) {
|
|
374
|
+
if (typeof mimeType !== "string" || typeof data !== "string" || data.length === 0) return IMAGE;
|
|
375
|
+
const type = mimeType.toLowerCase() === "image/jpg" ? "image/jpeg" : mimeType.toLowerCase();
|
|
376
|
+
return IMAGE_TYPES.has(type) ? { type: "image", data, mimeType: type } : IMAGE;
|
|
377
|
+
}
|
|
378
|
+
function imageFromUrl(url) {
|
|
379
|
+
if (typeof url !== "string") return IMAGE;
|
|
380
|
+
const head = /^data:([^;,]+);base64,/i.exec(url.slice(0, 200));
|
|
381
|
+
return head ? imageOf(head[1], url.slice(head[0].length)) : IMAGE;
|
|
382
|
+
}
|
|
383
|
+
function stableId(seed, taken) {
|
|
384
|
+
const hash = createHash("sha256").update(seed).digest("hex");
|
|
385
|
+
for (let start = 0; start + 8 <= hash.length; start += 8) {
|
|
386
|
+
const id2 = hash.slice(start, start + 8);
|
|
387
|
+
if (!taken.has(id2)) {
|
|
388
|
+
taken.add(id2);
|
|
389
|
+
return id2;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const id = randomUUID();
|
|
393
|
+
taken.add(id);
|
|
394
|
+
return id;
|
|
395
|
+
}
|
|
332
396
|
var SessionBuilder = class {
|
|
333
397
|
tool;
|
|
334
398
|
counts = emptyCounts();
|
|
@@ -343,23 +407,25 @@ var SessionBuilder = class {
|
|
|
343
407
|
constructor(tool) {
|
|
344
408
|
this.tool = tool;
|
|
345
409
|
}
|
|
346
|
-
drop(reason,
|
|
347
|
-
if (
|
|
348
|
-
this.counts.dropped[reason] = (this.counts.dropped[reason] ?? 0) +
|
|
410
|
+
drop(reason, count2 = 1) {
|
|
411
|
+
if (count2 <= 0) return;
|
|
412
|
+
this.counts.dropped[reason] = (this.counts.dropped[reason] ?? 0) + count2;
|
|
349
413
|
}
|
|
350
414
|
/** Whether anything a person said or a model answered is in it: a transcript without either is not imported. */
|
|
351
415
|
get hasConversation() {
|
|
352
416
|
return this.counts.user + this.counts.assistant > 0;
|
|
353
417
|
}
|
|
354
|
-
/**
|
|
355
|
-
|
|
418
|
+
/** Text and images as they were; an image without its data, or larger than any model takes, says so instead. */
|
|
419
|
+
blocksOf(content) {
|
|
356
420
|
if (typeof content === "string") return [{ type: "text", text: content }];
|
|
357
421
|
const blocks = [];
|
|
358
422
|
for (const block of content) {
|
|
359
|
-
if (block.type === "
|
|
423
|
+
if (block.type === "text") blocks.push({ type: "text", text: block.text });
|
|
424
|
+
else if (block.data.length > 0 && block.data.length <= MAX_IMAGE_CHARS) blocks.push(block);
|
|
425
|
+
else {
|
|
360
426
|
this.drop("images");
|
|
361
427
|
blocks.push({ type: "text", text: IMAGE_MARKER });
|
|
362
|
-
}
|
|
428
|
+
}
|
|
363
429
|
}
|
|
364
430
|
return blocks;
|
|
365
431
|
}
|
|
@@ -400,7 +466,7 @@ var SessionBuilder = class {
|
|
|
400
466
|
this.held = [];
|
|
401
467
|
}
|
|
402
468
|
user(content, at, key) {
|
|
403
|
-
const blocks = this.
|
|
469
|
+
const blocks = this.blocksOf(content).filter((block) => block.type === "image" || block.text.length > 0);
|
|
404
470
|
if (blocks.length === 0) return;
|
|
405
471
|
this.counts.user++;
|
|
406
472
|
this.push({
|
|
@@ -418,7 +484,15 @@ var SessionBuilder = class {
|
|
|
418
484
|
}
|
|
419
485
|
assistant(input) {
|
|
420
486
|
this.settle();
|
|
421
|
-
const
|
|
487
|
+
const unread = input.blocks.filter((block) => block.type === "thinking" && !block.thinking.trim()).length;
|
|
488
|
+
this.drop("unreadable thinking", unread);
|
|
489
|
+
let blocks = input.blocks.filter(
|
|
490
|
+
(block) => block.type === "toolCall" ? true : block.type === "text" ? block.text.length > 0 : block.thinking.trim().length > 0
|
|
491
|
+
);
|
|
492
|
+
if (blocks.every((block) => block.type === "thinking")) {
|
|
493
|
+
this.drop("thinking without an answer", blocks.length);
|
|
494
|
+
blocks = [];
|
|
495
|
+
}
|
|
422
496
|
if (blocks.length === 0 && !input.error) return;
|
|
423
497
|
const calls = blocks.filter((block) => block.type === "toolCall");
|
|
424
498
|
const message = {
|
|
@@ -435,6 +509,7 @@ var SessionBuilder = class {
|
|
|
435
509
|
this.counts.assistant++;
|
|
436
510
|
this.items.push({ kind: "message", message, at: input.at, key: input.key });
|
|
437
511
|
if (input.error) return;
|
|
512
|
+
this.counts.thinking += blocks.filter((block) => block.type === "thinking").length;
|
|
438
513
|
for (const call of calls) {
|
|
439
514
|
this.counts.toolCalls++;
|
|
440
515
|
this.calls.set(call.id, call.name);
|
|
@@ -443,18 +518,25 @@ var SessionBuilder = class {
|
|
|
443
518
|
}
|
|
444
519
|
toolResult(input) {
|
|
445
520
|
const name = input.name || this.calls.get(input.id) || "unknown";
|
|
446
|
-
const blocks = this.
|
|
521
|
+
const blocks = this.blocksOf(input.content);
|
|
447
522
|
if (!this.pending.has(input.id)) {
|
|
448
523
|
if (this.answered.has(input.id)) {
|
|
449
524
|
this.drop("repeated tool results");
|
|
450
525
|
return;
|
|
451
526
|
}
|
|
452
527
|
this.drop("tool results without their call, kept as context");
|
|
528
|
+
const text = blocks.map((block) => {
|
|
529
|
+
if (block.type === "text") return block.text;
|
|
530
|
+
this.drop("images");
|
|
531
|
+
return IMAGE_MARKER;
|
|
532
|
+
});
|
|
453
533
|
this.context(`[Result of an earlier ${name} call]
|
|
454
|
-
${
|
|
534
|
+
${text.join("\n")}`, input.at);
|
|
455
535
|
return;
|
|
456
536
|
}
|
|
457
|
-
const content = blocks.map(
|
|
537
|
+
const content = blocks.map(
|
|
538
|
+
(block) => block.type === "text" ? { type: "text", text: this.clipped(block.text) } : block
|
|
539
|
+
);
|
|
458
540
|
this.pending.delete(input.id);
|
|
459
541
|
this.answered.add(input.id);
|
|
460
542
|
this.counts.toolResults++;
|
|
@@ -487,25 +569,54 @@ ${blocks.map((block) => block.text).join("\n")}`, input.at);
|
|
|
487
569
|
this.counts.compactions++;
|
|
488
570
|
this.items.push({ kind: "compaction", summary, tokensBefore, at, keepFrom });
|
|
489
571
|
}
|
|
572
|
+
/**
|
|
573
|
+
* Images take MAX_IMAGES_CHARS at most, the latest first: they are what the conversation goes on from. An earlier
|
|
574
|
+
* one past that becomes IMAGE_MARKER.
|
|
575
|
+
*/
|
|
576
|
+
budgetImages() {
|
|
577
|
+
let left = MAX_IMAGES_CHARS;
|
|
578
|
+
for (let index = this.items.length - 1; index >= 0; index--) {
|
|
579
|
+
const item = this.items[index];
|
|
580
|
+
if (item.kind !== "message") continue;
|
|
581
|
+
const message = item.message;
|
|
582
|
+
if (message.role !== "user" && message.role !== "toolResult") continue;
|
|
583
|
+
if (typeof message.content === "string") continue;
|
|
584
|
+
const content = message.content;
|
|
585
|
+
if (!content.some((block) => block.type === "image")) continue;
|
|
586
|
+
const kept = [];
|
|
587
|
+
for (let at = content.length - 1; at >= 0; at--) {
|
|
588
|
+
const block = content[at];
|
|
589
|
+
if (block.type !== "image") kept.unshift(block);
|
|
590
|
+
else if (block.data.length <= left) {
|
|
591
|
+
left -= block.data.length;
|
|
592
|
+
this.counts.images++;
|
|
593
|
+
kept.unshift(block);
|
|
594
|
+
} else {
|
|
595
|
+
this.drop("images");
|
|
596
|
+
kept.unshift({ type: "text", text: IMAGE_MARKER });
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
message.content = kept;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
490
602
|
/** The session file's lines: header, marker, name, then the conversation as one chain. */
|
|
491
603
|
finish(input) {
|
|
492
604
|
this.settle();
|
|
605
|
+
this.budgetImages();
|
|
493
606
|
const taken = /* @__PURE__ */ new Set();
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
}
|
|
502
|
-
const id = randomUUID();
|
|
503
|
-
taken.add(id);
|
|
504
|
-
return id;
|
|
607
|
+
const seen = /* @__PURE__ */ new Map();
|
|
608
|
+
const newId = (key) => {
|
|
609
|
+
const times = seen.get(key) ?? 0;
|
|
610
|
+
seen.set(key, times + 1);
|
|
611
|
+
return stableId(`${input.sessionId}
|
|
612
|
+
${key}
|
|
613
|
+
${times}`, taken);
|
|
505
614
|
};
|
|
506
615
|
const startedAt = Date.parse(input.started);
|
|
507
|
-
const
|
|
508
|
-
const ids = this.items.map(
|
|
616
|
+
const iso2 = (at) => new Date(at > 0 ? at : startedAt).toISOString();
|
|
617
|
+
const ids = this.items.map(
|
|
618
|
+
(item, index) => newId(item.kind === "compaction" ? `compaction ${index}` : item.key ?? `item ${index}`)
|
|
619
|
+
);
|
|
509
620
|
const byKey = /* @__PURE__ */ new Map();
|
|
510
621
|
this.items.forEach((item, index) => {
|
|
511
622
|
if (item.kind !== "compaction" && item.key && !byKey.has(item.key)) byKey.set(item.key, index);
|
|
@@ -514,7 +625,8 @@ ${blocks.map((block) => block.text).join("\n")}`, input.at);
|
|
|
514
625
|
this.items.forEach((item, index) => {
|
|
515
626
|
if (item.kind === "compaction") lastCompaction = index;
|
|
516
627
|
});
|
|
517
|
-
this.counts.entries = this.items.length + 2 + (lastCompaction >= 0 ? 1 : 0);
|
|
628
|
+
this.counts.entries = this.items.length + 2 + (lastCompaction >= 0 ? 1 : 0) + (input.model ? 1 : 0);
|
|
629
|
+
const digest = createHash("sha256").update(JSON.stringify(this.items)).digest("hex").slice(0, 32);
|
|
518
630
|
const header = {
|
|
519
631
|
type: "session",
|
|
520
632
|
version: SESSION_VERSION,
|
|
@@ -524,17 +636,17 @@ ${blocks.map((block) => block.text).join("\n")}`, input.at);
|
|
|
524
636
|
};
|
|
525
637
|
const marker = {
|
|
526
638
|
type: "custom_message",
|
|
527
|
-
id: newId(),
|
|
639
|
+
id: newId("marker"),
|
|
528
640
|
parentId: null,
|
|
529
641
|
timestamp: input.started,
|
|
530
642
|
customType: IMPORT_MARKER,
|
|
531
643
|
content: input.note,
|
|
532
644
|
display: true,
|
|
533
|
-
details: { ...input.origin, counts: this.counts }
|
|
645
|
+
details: { ...input.origin, digest, counts: this.counts }
|
|
534
646
|
};
|
|
535
647
|
const info = {
|
|
536
648
|
type: "session_info",
|
|
537
|
-
id: newId(),
|
|
649
|
+
id: newId("name"),
|
|
538
650
|
parentId: marker.id,
|
|
539
651
|
timestamp: input.started,
|
|
540
652
|
name: input.name.replace(/[\r\n]+/g, " ").trim()
|
|
@@ -543,7 +655,7 @@ ${blocks.map((block) => block.text).join("\n")}`, input.at);
|
|
|
543
655
|
let parentId = info.id;
|
|
544
656
|
this.items.forEach((item, index) => {
|
|
545
657
|
const id = ids[index];
|
|
546
|
-
const timestamp =
|
|
658
|
+
const timestamp = iso2(item.at);
|
|
547
659
|
if (item.kind === "message") {
|
|
548
660
|
entries.push({ type: "message", id, parentId, timestamp, message: item.message });
|
|
549
661
|
} else if (item.kind === "context") {
|
|
@@ -559,7 +671,7 @@ ${blocks.map((block) => block.text).join("\n")}`, input.at);
|
|
|
559
671
|
entries.push(context);
|
|
560
672
|
} else {
|
|
561
673
|
const kept = item.keepFrom.map((key) => byKey.get(key)).find((at) => at !== void 0);
|
|
562
|
-
const firstKeptEntryId = kept !== void 0 && kept < index ? ids[kept] : index + 1 < ids.length ? ids[index + 1] : newId();
|
|
674
|
+
const firstKeptEntryId = kept !== void 0 && kept < index ? ids[kept] : index + 1 < ids.length ? ids[index + 1] : newId("after the last compaction");
|
|
563
675
|
const compaction = {
|
|
564
676
|
type: "compaction",
|
|
565
677
|
id,
|
|
@@ -573,25 +685,48 @@ ${blocks.map((block) => block.text).join("\n")}`, input.at);
|
|
|
573
685
|
}
|
|
574
686
|
parentId = id;
|
|
575
687
|
if (index === lastCompaction) {
|
|
576
|
-
const again = {
|
|
688
|
+
const again = {
|
|
689
|
+
...marker,
|
|
690
|
+
id: newId("marker again"),
|
|
691
|
+
parentId,
|
|
692
|
+
timestamp
|
|
693
|
+
};
|
|
577
694
|
entries.push(again);
|
|
578
695
|
parentId = again.id;
|
|
579
696
|
}
|
|
580
697
|
});
|
|
698
|
+
if (input.model) {
|
|
699
|
+
const change = {
|
|
700
|
+
type: "model_change",
|
|
701
|
+
id: newId("model"),
|
|
702
|
+
parentId,
|
|
703
|
+
timestamp: input.origin.importedAt,
|
|
704
|
+
provider: input.model.provider,
|
|
705
|
+
modelId: input.model.modelId
|
|
706
|
+
};
|
|
707
|
+
entries.push(change);
|
|
708
|
+
}
|
|
581
709
|
return [header, ...entries];
|
|
582
710
|
}
|
|
583
711
|
};
|
|
712
|
+
function fileStamp(path) {
|
|
713
|
+
try {
|
|
714
|
+
const stats = statSync(path);
|
|
715
|
+
return `${stats.size}:${Math.floor(stats.mtimeMs)}`;
|
|
716
|
+
} catch {
|
|
717
|
+
return void 0;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
584
720
|
function importNote(tool, source) {
|
|
585
721
|
const name = TOOL_NAMES[tool];
|
|
586
722
|
return [
|
|
587
723
|
`This conversation was imported from ${name} (${source}) and continues here.`,
|
|
588
|
-
`Its tool calls use ${name}'s tool names, which may differ from the tools available now
|
|
589
|
-
"Thinking and images were not imported."
|
|
724
|
+
`Its tool calls use ${name}'s tool names, which may differ from the tools available now.`
|
|
590
725
|
].join(" ");
|
|
591
726
|
}
|
|
592
727
|
function complete(builder, input) {
|
|
593
728
|
const started = Number.isNaN(Date.parse(input.started)) ? (/* @__PURE__ */ new Date()).toISOString() : input.started;
|
|
594
|
-
const sessionId = uuidv7(Date.parse(started));
|
|
729
|
+
const sessionId = input.options.sessionId ?? uuidv7(Date.parse(started));
|
|
595
730
|
const name = input.name?.trim() || `${TOOL_NAMES[builder.tool]} conversation`;
|
|
596
731
|
const entries = builder.finish({
|
|
597
732
|
sessionId,
|
|
@@ -599,13 +734,15 @@ function complete(builder, input) {
|
|
|
599
734
|
started,
|
|
600
735
|
name,
|
|
601
736
|
note: importNote(builder.tool, input.source),
|
|
737
|
+
...input.options.model ? { model: input.options.model } : {},
|
|
602
738
|
origin: {
|
|
603
739
|
version: 1,
|
|
604
740
|
tool: builder.tool,
|
|
605
741
|
source: input.source,
|
|
606
742
|
sourceId: input.sourceId,
|
|
607
|
-
importedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
608
|
-
...input.model ? { model: input.model } : {}
|
|
743
|
+
importedAt: (input.options.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
744
|
+
...input.model ? { model: input.model } : {},
|
|
745
|
+
...input.stamp ? { stamp: input.stamp } : {}
|
|
609
746
|
}
|
|
610
747
|
});
|
|
611
748
|
return {
|
|
@@ -631,24 +768,33 @@ function writeSessionFile(file, entries) {
|
|
|
631
768
|
closeSync2(fd);
|
|
632
769
|
}
|
|
633
770
|
}
|
|
771
|
+
function replaceSessionFile(file, entries) {
|
|
772
|
+
const temp = `${file}.${randomBytes(4).toString("hex")}.tmp`;
|
|
773
|
+
try {
|
|
774
|
+
writeSessionFile(temp, entries);
|
|
775
|
+
renameSync(temp, file);
|
|
776
|
+
} catch (error) {
|
|
777
|
+
rmSync(temp, { force: true });
|
|
778
|
+
throw error;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
634
781
|
|
|
635
782
|
// packages/kyrn-judge/src/import/claude-code.ts
|
|
636
783
|
var TREE_TYPES = /* @__PURE__ */ new Set(["user", "assistant", "system", "attachment"]);
|
|
637
784
|
function slim(data) {
|
|
638
785
|
delete data.toolUseResult;
|
|
639
|
-
for (const block of list(record(data.message)?.content)) {
|
|
640
|
-
const outer = record(block);
|
|
641
|
-
if (outer?.type === "image") delete outer.source;
|
|
642
|
-
if (outer?.type === "tool_result") {
|
|
643
|
-
for (const inner of list(outer.content)) {
|
|
644
|
-
const image = record(inner);
|
|
645
|
-
if (image?.type === "image") delete image.source;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
786
|
const file = record(record(record(data.attachment)?.content)?.file);
|
|
650
787
|
if (file) delete file.base64;
|
|
651
788
|
}
|
|
789
|
+
function imageBlock(block) {
|
|
790
|
+
const source = record(block.source);
|
|
791
|
+
return source?.type === "base64" ? imageOf(source.media_type, source.data) : imageOf(void 0, void 0);
|
|
792
|
+
}
|
|
793
|
+
var TITLES = {
|
|
794
|
+
"custom-title": "customTitle",
|
|
795
|
+
"ai-title": "aiTitle",
|
|
796
|
+
summary: "summary"
|
|
797
|
+
};
|
|
652
798
|
function blocksOf(message) {
|
|
653
799
|
const content = message?.content;
|
|
654
800
|
if (typeof content === "string") return [{ type: "text", text: content }];
|
|
@@ -663,7 +809,7 @@ function resultContent(content, counts) {
|
|
|
663
809
|
for (const block of list(content).map(record)) {
|
|
664
810
|
if (!block) continue;
|
|
665
811
|
if (block.type === "text") blocks.push({ type: "text", text: str(block.text) ?? "" });
|
|
666
|
-
else if (block.type === "image") blocks.push(
|
|
812
|
+
else if (block.type === "image") blocks.push(imageBlock(block));
|
|
667
813
|
else if (block.type === "tool_reference")
|
|
668
814
|
blocks.push({ type: "text", text: `[tool reference: ${str(block.tool_name) ?? "unknown"}]` });
|
|
669
815
|
else {
|
|
@@ -710,6 +856,8 @@ async function readClaudeCode(path, options = {}) {
|
|
|
710
856
|
const records = [];
|
|
711
857
|
const byUuid = /* @__PURE__ */ new Map();
|
|
712
858
|
let sessionId;
|
|
859
|
+
const titles = /* @__PURE__ */ new Map();
|
|
860
|
+
const stamp = fileStamp(source);
|
|
713
861
|
for await (const line of readJsonLines(source)) {
|
|
714
862
|
counts.records++;
|
|
715
863
|
if (!("value" in line)) {
|
|
@@ -719,13 +867,15 @@ async function readClaudeCode(path, options = {}) {
|
|
|
719
867
|
const data = record(line.value);
|
|
720
868
|
const type = str(data?.type);
|
|
721
869
|
if (!data || !type) {
|
|
722
|
-
builder.drop("records
|
|
870
|
+
builder.drop("unrecognized records");
|
|
723
871
|
continue;
|
|
724
872
|
}
|
|
725
873
|
sessionId ??= str(data.sessionId);
|
|
874
|
+
const title = Object.hasOwn(TITLES, type) ? str(data[TITLES[type]])?.trim() : void 0;
|
|
875
|
+
if (title) titles.set(type, title);
|
|
726
876
|
const uuid = str(data.uuid);
|
|
727
877
|
if (!TREE_TYPES.has(type) || !uuid) {
|
|
728
|
-
builder.drop(uuid ? "records
|
|
878
|
+
builder.drop(uuid ? "unrecognized records" : "Claude Code bookkeeping (titles, queue, file history)");
|
|
729
879
|
continue;
|
|
730
880
|
}
|
|
731
881
|
if (byUuid.has(uuid)) {
|
|
@@ -764,6 +914,10 @@ async function readClaudeCode(path, options = {}) {
|
|
|
764
914
|
chosen.add(current2.uuid);
|
|
765
915
|
const next = current2.parent ?? current2.logicalParent;
|
|
766
916
|
let found = next ? byUuid.get(next) : void 0;
|
|
917
|
+
if (!current2.parent && found) {
|
|
918
|
+
const before = (position.get(found.uuid) ?? 0) < (position.get(current2.uuid) ?? 0);
|
|
919
|
+
if (!before || chosen.has(found.uuid)) found = messageBefore(current2);
|
|
920
|
+
}
|
|
767
921
|
if (next && !found) {
|
|
768
922
|
if (!current2.parent) found = messageBefore(current2);
|
|
769
923
|
if (!found) builder.drop("links to records that are not in the file");
|
|
@@ -802,7 +956,7 @@ async function readClaudeCode(path, options = {}) {
|
|
|
802
956
|
if (inScope(item)) offBranch++;
|
|
803
957
|
else subAgent++;
|
|
804
958
|
}
|
|
805
|
-
builder.drop("
|
|
959
|
+
builder.drop("subagent records", subAgent);
|
|
806
960
|
builder.drop("records off the latest branch", offBranch);
|
|
807
961
|
const ordered = records.filter((item) => chosen.has(item.uuid));
|
|
808
962
|
const orderedIds = ordered.map((item) => item.uuid);
|
|
@@ -865,7 +1019,8 @@ async function readClaudeCode(path, options = {}) {
|
|
|
865
1019
|
if (partMessage.stop_reason === "max_tokens") stop = "length";
|
|
866
1020
|
for (const block of blocksOf(partMessage)) {
|
|
867
1021
|
if (block.type === "text") blocks.push({ type: "text", text: str(block.text) ?? "" });
|
|
868
|
-
else if (block.type === "thinking"
|
|
1022
|
+
else if (block.type === "thinking") blocks.push({ type: "thinking", thinking: str(block.thinking) ?? "" });
|
|
1023
|
+
else if (block.type === "redacted_thinking") builder.drop("redacted thinking");
|
|
869
1024
|
else if (block.type === "tool_use") {
|
|
870
1025
|
const callId = str(block.id) ?? part.uuid;
|
|
871
1026
|
if (callIds.has(callId)) {
|
|
@@ -918,7 +1073,7 @@ async function readClaudeCode(path, options = {}) {
|
|
|
918
1073
|
}
|
|
919
1074
|
if (block.type === "image") {
|
|
920
1075
|
if (meta) builder.drop("images");
|
|
921
|
-
else words.push(
|
|
1076
|
+
else words.push(imageBlock(block));
|
|
922
1077
|
continue;
|
|
923
1078
|
}
|
|
924
1079
|
if (block.type !== "text") {
|
|
@@ -980,7 +1135,7 @@ async function readClaudeCode(path, options = {}) {
|
|
|
980
1135
|
if (attachment.type === "queued_command") {
|
|
981
1136
|
const prompt = attachment.prompt;
|
|
982
1137
|
const words = typeof prompt === "string" ? [{ type: "text", text: prompt }] : list(prompt).map(record).filter((block) => block !== void 0).map(
|
|
983
|
-
(block) => block.type === "text" ? { type: "text", text: str(block.text) ?? "" } :
|
|
1138
|
+
(block) => block.type === "text" ? { type: "text", text: str(block.text) ?? "" } : imageBlock(block)
|
|
984
1139
|
);
|
|
985
1140
|
const typed = attachment.commandMode === "prompt" && attachment.isMeta !== true;
|
|
986
1141
|
if (typed) {
|
|
@@ -1027,9 +1182,10 @@ async function readClaudeCode(path, options = {}) {
|
|
|
1027
1182
|
sourceId: agentId ? `agent-${agentId}` : sessionId ?? basename(source, ".jsonl"),
|
|
1028
1183
|
cwd: cwd ?? options.cwd ?? process.cwd(),
|
|
1029
1184
|
started: startedIso,
|
|
1030
|
-
name: name ?? firstCommand,
|
|
1185
|
+
name: titles.get("custom-title") ?? titles.get("ai-title") ?? titles.get("summary") ?? name ?? firstCommand,
|
|
1031
1186
|
model,
|
|
1032
|
-
|
|
1187
|
+
stamp,
|
|
1188
|
+
options
|
|
1033
1189
|
});
|
|
1034
1190
|
}
|
|
1035
1191
|
|
|
@@ -1070,7 +1226,7 @@ function outputOf(output, counts) {
|
|
|
1070
1226
|
if (!block) continue;
|
|
1071
1227
|
if (block.type === "input_text" || block.type === "output_text" || block.type === "text")
|
|
1072
1228
|
blocks.push({ type: "text", text: str(block.text) ?? "" });
|
|
1073
|
-
else if (block.type === "input_image") blocks.push(
|
|
1229
|
+
else if (block.type === "input_image") blocks.push(imageFromUrl(block.image_url));
|
|
1074
1230
|
else {
|
|
1075
1231
|
counts.asText++;
|
|
1076
1232
|
blocks.push({ type: "text", text: unknownBlockText(block) });
|
|
@@ -1090,6 +1246,7 @@ ${messages}`);
|
|
|
1090
1246
|
}
|
|
1091
1247
|
async function readCodex(path, options = {}) {
|
|
1092
1248
|
const source = absolute(path);
|
|
1249
|
+
const stamp = fileStamp(source);
|
|
1093
1250
|
const builder = new SessionBuilder("codex");
|
|
1094
1251
|
const { counts } = builder;
|
|
1095
1252
|
let sourceId = "";
|
|
@@ -1122,7 +1279,7 @@ async function readCodex(path, options = {}) {
|
|
|
1122
1279
|
};
|
|
1123
1280
|
for (const block of blocks) {
|
|
1124
1281
|
if (block.type === "input_image") {
|
|
1125
|
-
words.push(
|
|
1282
|
+
words.push(imageFromUrl(block.image_url));
|
|
1126
1283
|
continue;
|
|
1127
1284
|
}
|
|
1128
1285
|
if (block.type !== "input_text" && block.type !== "text") {
|
|
@@ -1181,9 +1338,14 @@ async function readCodex(path, options = {}) {
|
|
|
1181
1338
|
else builder.drop("Codex instructions");
|
|
1182
1339
|
return;
|
|
1183
1340
|
}
|
|
1184
|
-
case "reasoning":
|
|
1185
|
-
|
|
1341
|
+
case "reasoning": {
|
|
1342
|
+
const read2 = (blocks) => list(blocks).map((block) => str(record(block)?.text)?.trim()).filter((text2) => Boolean(text2));
|
|
1343
|
+
const content = read2(payload.content);
|
|
1344
|
+
const text = content.length > 0 ? content : read2(payload.summary);
|
|
1345
|
+
if (text.length === 0) builder.drop("unreadable thinking");
|
|
1346
|
+
else answer().blocks.push({ type: "thinking", thinking: text.join("\n\n") });
|
|
1186
1347
|
return;
|
|
1348
|
+
}
|
|
1187
1349
|
case "function_call":
|
|
1188
1350
|
case "tool_search_call":
|
|
1189
1351
|
answer().blocks.push({
|
|
@@ -1244,7 +1406,7 @@ ${text}`, at);
|
|
|
1244
1406
|
return;
|
|
1245
1407
|
}
|
|
1246
1408
|
default:
|
|
1247
|
-
builder.drop("records
|
|
1409
|
+
builder.drop("unrecognized records");
|
|
1248
1410
|
}
|
|
1249
1411
|
};
|
|
1250
1412
|
const compacted = (payload) => {
|
|
@@ -1270,7 +1432,7 @@ ${text}`, at);
|
|
|
1270
1432
|
}
|
|
1271
1433
|
const data = record(line.value);
|
|
1272
1434
|
if (!data) {
|
|
1273
|
-
builder.drop("records
|
|
1435
|
+
builder.drop("unrecognized records");
|
|
1274
1436
|
continue;
|
|
1275
1437
|
}
|
|
1276
1438
|
const payload = record(data.payload);
|
|
@@ -1296,7 +1458,7 @@ ${text}`, at);
|
|
|
1296
1458
|
if (typeof input === "number") tokens = input;
|
|
1297
1459
|
}
|
|
1298
1460
|
builder.drop("Codex events and state");
|
|
1299
|
-
} else builder.drop("records
|
|
1461
|
+
} else builder.drop("unrecognized records");
|
|
1300
1462
|
continue;
|
|
1301
1463
|
}
|
|
1302
1464
|
if (data.record_type === "state") {
|
|
@@ -1308,15 +1470,15 @@ ${text}`, at);
|
|
|
1308
1470
|
continue;
|
|
1309
1471
|
}
|
|
1310
1472
|
const id = str(data.id);
|
|
1311
|
-
const
|
|
1312
|
-
if (id &&
|
|
1473
|
+
const stamp2 = str(data.timestamp);
|
|
1474
|
+
if (id && stamp2) {
|
|
1313
1475
|
if (!sourceId) sourceId = id;
|
|
1314
|
-
started ??=
|
|
1315
|
-
const time = Date.parse(
|
|
1476
|
+
started ??= stamp2;
|
|
1477
|
+
const time = Date.parse(stamp2);
|
|
1316
1478
|
if (!Number.isNaN(time)) at = time;
|
|
1317
1479
|
continue;
|
|
1318
1480
|
}
|
|
1319
|
-
builder.drop("records
|
|
1481
|
+
builder.drop("unrecognized records");
|
|
1320
1482
|
}
|
|
1321
1483
|
flush();
|
|
1322
1484
|
let startedIso = started && !Number.isNaN(Date.parse(started)) ? new Date(Date.parse(started)).toISOString() : void 0;
|
|
@@ -1335,7 +1497,365 @@ ${text}`, at);
|
|
|
1335
1497
|
started: startedIso,
|
|
1336
1498
|
name: name ?? firstCommand,
|
|
1337
1499
|
model: lastModel ?? model,
|
|
1338
|
-
|
|
1500
|
+
stamp,
|
|
1501
|
+
options
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
// packages/kyrn-judge/src/import/zcode.ts
|
|
1506
|
+
import { execFileSync } from "node:child_process";
|
|
1507
|
+
import { statSync as statSync4 } from "node:fs";
|
|
1508
|
+
import { createRequire } from "node:module";
|
|
1509
|
+
import { homedir as homedir2 } from "node:os";
|
|
1510
|
+
import { join as join2 } from "node:path";
|
|
1511
|
+
function zcodeDatabase(home = homedir2()) {
|
|
1512
|
+
return join2(home, ".zcode", "cli", "db", "db.sqlite");
|
|
1513
|
+
}
|
|
1514
|
+
function zcodeSource(database, id) {
|
|
1515
|
+
return `${database}#${id}`;
|
|
1516
|
+
}
|
|
1517
|
+
function parseZcodeSource(source) {
|
|
1518
|
+
const match = /^(.+\.(?:sqlite3?|db))#([\w.:-]+)$/.exec(source);
|
|
1519
|
+
return match ? { database: absolute(match[1]), id: match[2] } : void 0;
|
|
1520
|
+
}
|
|
1521
|
+
var load = createRequire(new URL("src/import/zcode.ts", new URL("../", import.meta.url)).href);
|
|
1522
|
+
var nodeSqlite;
|
|
1523
|
+
function sqliteModule() {
|
|
1524
|
+
if (nodeSqlite === void 0) {
|
|
1525
|
+
const emit = process.emitWarning;
|
|
1526
|
+
process.emitWarning = ((warning, ...rest) => {
|
|
1527
|
+
if (/sqlite/i.test(typeof warning === "string" ? warning : warning.message)) return;
|
|
1528
|
+
emit.call(process, warning, ...rest);
|
|
1529
|
+
});
|
|
1530
|
+
try {
|
|
1531
|
+
nodeSqlite = load("node:sqlite");
|
|
1532
|
+
} catch {
|
|
1533
|
+
nodeSqlite = null;
|
|
1534
|
+
} finally {
|
|
1535
|
+
process.emitWarning = emit;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
return nodeSqlite ?? void 0;
|
|
1539
|
+
}
|
|
1540
|
+
var quote = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
1541
|
+
function openZcode(database) {
|
|
1542
|
+
statSync4(database);
|
|
1543
|
+
const sqlite = sqliteModule();
|
|
1544
|
+
if (sqlite) {
|
|
1545
|
+
const db = new sqlite.DatabaseSync(database, { readOnly: true, timeout: 2e3 });
|
|
1546
|
+
return {
|
|
1547
|
+
all: (sql, ...params) => db.prepare(sql).all(...params),
|
|
1548
|
+
close: () => db.close()
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
return {
|
|
1552
|
+
all: (sql, ...params) => {
|
|
1553
|
+
const bound = sql.replace(/\?/g, () => quote(params.shift() ?? ""));
|
|
1554
|
+
const out = execFileSync("sqlite3", ["-readonly", "-json", database, bound], {
|
|
1555
|
+
encoding: "utf8",
|
|
1556
|
+
maxBuffer: 2 * 1024 * 1024 * 1024,
|
|
1557
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1558
|
+
});
|
|
1559
|
+
return out.trim() ? JSON.parse(out) : [];
|
|
1560
|
+
},
|
|
1561
|
+
close: () => {
|
|
1562
|
+
}
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
function columns(db, table) {
|
|
1566
|
+
return new Set(db.all(`PRAGMA table_info(${table})`).map((row) => String(row.name)));
|
|
1567
|
+
}
|
|
1568
|
+
var json = (value) => {
|
|
1569
|
+
if (typeof value !== "string") return {};
|
|
1570
|
+
try {
|
|
1571
|
+
return record(JSON.parse(value)) ?? {};
|
|
1572
|
+
} catch {
|
|
1573
|
+
return {};
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1576
|
+
var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1577
|
+
var SESSION_COLUMNS = [
|
|
1578
|
+
"id",
|
|
1579
|
+
"parent_id",
|
|
1580
|
+
"task_type",
|
|
1581
|
+
"directory",
|
|
1582
|
+
"title",
|
|
1583
|
+
"title_source",
|
|
1584
|
+
"revert",
|
|
1585
|
+
"time_created",
|
|
1586
|
+
"time_updated",
|
|
1587
|
+
"time_archived"
|
|
1588
|
+
];
|
|
1589
|
+
function sessionsSql(db, where = "") {
|
|
1590
|
+
const have = columns(db, "session");
|
|
1591
|
+
const pick = SESSION_COLUMNS.map((name) => have.has(name) ? name : `NULL AS ${name}`).join(", ");
|
|
1592
|
+
return `SELECT ${pick} FROM session${where}`;
|
|
1593
|
+
}
|
|
1594
|
+
var order = (db, table, tail, prefix = "") => columns(db, table).has("sequence") ? `${prefix}sequence IS NULL, ${prefix}sequence, ${prefix}time_created, ${tail}` : `${prefix}time_created, ${tail}`;
|
|
1595
|
+
var SIDE_THREADS = /* @__PURE__ */ new Set(["subagent_child", "selection_side_chat"]);
|
|
1596
|
+
var iso = (ms) => ms && ms > 0 ? new Date(ms).toISOString() : void 0;
|
|
1597
|
+
var titleOf = (session) => session.title_source === "default" || session.title_source === "first_input" ? "" : firstLine(str(session.title) ?? "");
|
|
1598
|
+
function firstPrompt(db, id) {
|
|
1599
|
+
const rows = db.all(
|
|
1600
|
+
`SELECT p.data AS data FROM message m JOIN part p ON p.message_id = m.id
|
|
1601
|
+
WHERE m.session_id = ? AND json_extract(m.data, '$.role') = 'user'
|
|
1602
|
+
AND coalesce(json_extract(m.data, '$.semantics.kind'), 'user_prompt') = 'user_prompt'
|
|
1603
|
+
AND json_extract(p.data, '$.type') = 'text'
|
|
1604
|
+
ORDER BY ${order(db, "message", "m.rowid", "m.")} LIMIT 1`,
|
|
1605
|
+
id
|
|
1606
|
+
);
|
|
1607
|
+
return firstLine(str(json(rows[0]?.data).text) ?? "");
|
|
1608
|
+
}
|
|
1609
|
+
function listZcode(database, options = {}) {
|
|
1610
|
+
let db;
|
|
1611
|
+
try {
|
|
1612
|
+
db = openZcode(database);
|
|
1613
|
+
} catch {
|
|
1614
|
+
return [];
|
|
1615
|
+
}
|
|
1616
|
+
try {
|
|
1617
|
+
const sessions = db.all(sessionsSql(db));
|
|
1618
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
1619
|
+
for (const table of ["message", "part"]) {
|
|
1620
|
+
for (const row of db.all(
|
|
1621
|
+
`SELECT session_id AS id, COUNT(*) AS rows, COALESCE(SUM(LENGTH(data)), 0) AS bytes FROM ${table} GROUP BY session_id`
|
|
1622
|
+
)) {
|
|
1623
|
+
const id = String(row.id);
|
|
1624
|
+
const size = sizes.get(id) ?? { messages: 0, bytes: 0 };
|
|
1625
|
+
if (table === "message") size.messages = num(row.rows) ?? 0;
|
|
1626
|
+
size.bytes += num(row.bytes) ?? 0;
|
|
1627
|
+
sizes.set(id, size);
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
const found = [];
|
|
1631
|
+
for (const session of sessions) {
|
|
1632
|
+
const id = str(session.id);
|
|
1633
|
+
if (!id || session.time_archived !== null) continue;
|
|
1634
|
+
const subAgent = SIDE_THREADS.has(str(session.task_type) ?? "");
|
|
1635
|
+
if (subAgent && !options.subAgents) continue;
|
|
1636
|
+
const size = sizes.get(id) ?? { messages: 0, bytes: 0 };
|
|
1637
|
+
if (size.messages === 0) continue;
|
|
1638
|
+
const updated = num(session.time_updated);
|
|
1639
|
+
found.push({
|
|
1640
|
+
id,
|
|
1641
|
+
source: zcodeSource(database, id),
|
|
1642
|
+
...str(session.directory) ? { cwd: str(session.directory) } : {},
|
|
1643
|
+
title: titleOf(session) || firstPrompt(db, id),
|
|
1644
|
+
...iso(num(session.time_created)) ? { started: iso(num(session.time_created)) } : {},
|
|
1645
|
+
modified: iso(updated) ?? (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
1646
|
+
size: size.bytes,
|
|
1647
|
+
subAgent,
|
|
1648
|
+
stamp: `${updated ?? 0}:${size.messages}:${size.bytes}`
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
return found;
|
|
1652
|
+
} catch {
|
|
1653
|
+
return [];
|
|
1654
|
+
} finally {
|
|
1655
|
+
db.close();
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
function afterRewind(messages, revert) {
|
|
1659
|
+
const target = str(revert.targetMessageID);
|
|
1660
|
+
if (!target) return messages;
|
|
1661
|
+
const index = (id) => typeof id === "string" ? messages.findIndex((message) => message.id === id) : -1;
|
|
1662
|
+
const keptIds = list(revert.keptMessageIDs).filter((id) => typeof id === "string");
|
|
1663
|
+
const kept = Array.isArray(revert.keptMessageIDs) ? messages.filter((message) => keptIds.includes(message.id)) : messages.slice(0, Math.max(0, index(target)));
|
|
1664
|
+
const cut = index(revert.branchCutAfterMessageID);
|
|
1665
|
+
if (cut >= 0) return [...kept, ...messages.slice(cut + 1)];
|
|
1666
|
+
const created = index(revert.createdMessageID);
|
|
1667
|
+
if (created >= 0) return [...kept, ...messages.slice(created)];
|
|
1668
|
+
return Array.isArray(revert.keptMessageIDs) || index(target) >= 0 ? kept : messages;
|
|
1669
|
+
}
|
|
1670
|
+
var textOf = (parts) => parts.filter((part) => part.type === "text").map((part) => str(part.text) ?? "").join("\n").trim();
|
|
1671
|
+
var startOf = (part, fallback) => num(record(part.time)?.start) ?? num(record(record(part.state)?.time)?.start) ?? fallback;
|
|
1672
|
+
function toolOutput(state) {
|
|
1673
|
+
const isError = state.status === "error";
|
|
1674
|
+
const value = isError ? state.error : state.output;
|
|
1675
|
+
const text = typeof value === "string" ? value : value === void 0 || value === null ? "" : JSON.stringify(value);
|
|
1676
|
+
const content = [{ type: "text", text }];
|
|
1677
|
+
for (const attachment of list(state.attachments).map(record)) {
|
|
1678
|
+
if (attachment && String(attachment.mime ?? "").startsWith("image/")) content.push(imageFromUrl(attachment.url));
|
|
1679
|
+
}
|
|
1680
|
+
return { content, isError };
|
|
1681
|
+
}
|
|
1682
|
+
async function readZcode(sourcePath, options = {}) {
|
|
1683
|
+
const where = parseZcodeSource(sourcePath);
|
|
1684
|
+
if (!where) throw new Error("not a ZCode conversation");
|
|
1685
|
+
const source = zcodeSource(where.database, where.id);
|
|
1686
|
+
const db = openZcode(where.database);
|
|
1687
|
+
let session;
|
|
1688
|
+
let messages;
|
|
1689
|
+
let stamp;
|
|
1690
|
+
try {
|
|
1691
|
+
session = db.all(sessionsSql(db, " WHERE id = ?"), where.id)[0];
|
|
1692
|
+
if (!session) throw new Error("no such ZCode conversation");
|
|
1693
|
+
const parts = /* @__PURE__ */ new Map();
|
|
1694
|
+
const partRows = db.all(
|
|
1695
|
+
`SELECT message_id, time_created, data FROM part WHERE session_id = ? ORDER BY ${order(db, "part", "id")}`,
|
|
1696
|
+
where.id
|
|
1697
|
+
);
|
|
1698
|
+
const starts = /* @__PURE__ */ new Map();
|
|
1699
|
+
let bytes = 0;
|
|
1700
|
+
for (const row of partRows) {
|
|
1701
|
+
const data = json(row.data);
|
|
1702
|
+
bytes += String(row.data ?? "").length;
|
|
1703
|
+
starts.set(data, startOf(data, num(row.time_created) ?? 0));
|
|
1704
|
+
const id = String(row.message_id);
|
|
1705
|
+
const own = parts.get(id) ?? [];
|
|
1706
|
+
own.push(data);
|
|
1707
|
+
parts.set(id, own);
|
|
1708
|
+
}
|
|
1709
|
+
const messageRows = db.all(
|
|
1710
|
+
`SELECT id, time_created, data FROM message WHERE session_id = ? ORDER BY ${order(db, "message", "rowid")}`,
|
|
1711
|
+
where.id
|
|
1712
|
+
);
|
|
1713
|
+
messages = messageRows.map((row) => {
|
|
1714
|
+
const data = json(row.data);
|
|
1715
|
+
bytes += String(row.data ?? "").length;
|
|
1716
|
+
const id = String(row.id);
|
|
1717
|
+
const own = parts.get(id) ?? [];
|
|
1718
|
+
const sorted = own.map((part, index) => ({ part, index }));
|
|
1719
|
+
sorted.sort((a, b) => (starts.get(a.part) ?? 0) - (starts.get(b.part) ?? 0) || a.index - b.index);
|
|
1720
|
+
return {
|
|
1721
|
+
id,
|
|
1722
|
+
at: num(record(data.time)?.created) ?? num(row.time_created) ?? 0,
|
|
1723
|
+
data,
|
|
1724
|
+
parts: sorted.map((each) => each.part)
|
|
1725
|
+
};
|
|
1726
|
+
});
|
|
1727
|
+
stamp = `${num(session.time_updated) ?? 0}:${messageRows.length}:${bytes}`;
|
|
1728
|
+
} finally {
|
|
1729
|
+
db.close();
|
|
1730
|
+
}
|
|
1731
|
+
const builder = new SessionBuilder("zcode");
|
|
1732
|
+
const { counts } = builder;
|
|
1733
|
+
counts.records = messages.length;
|
|
1734
|
+
const active = afterRewind(messages, json(session.revert));
|
|
1735
|
+
builder.drop("messages a rewind took back", messages.length - active.length);
|
|
1736
|
+
let cwd = str(session.directory);
|
|
1737
|
+
let model;
|
|
1738
|
+
let name;
|
|
1739
|
+
let firstCommand;
|
|
1740
|
+
for (const message of active) {
|
|
1741
|
+
const { data, parts, at } = message;
|
|
1742
|
+
const semantics = record(data.semantics) ?? {};
|
|
1743
|
+
const kind = str(semantics.kind);
|
|
1744
|
+
const shown = semantics.uiVisibility !== "hidden";
|
|
1745
|
+
const sent = semantics.providerVisibility !== "hidden";
|
|
1746
|
+
if (kind === "timeline_event") {
|
|
1747
|
+
builder.drop("ZCode timeline lines (model changes, compactions)");
|
|
1748
|
+
continue;
|
|
1749
|
+
}
|
|
1750
|
+
if (kind === "compact_summary") {
|
|
1751
|
+
const boundary = record(parts.find((part) => part.type === "compaction")?.compactBoundary) ?? {};
|
|
1752
|
+
const summary = str(record(data.summary)?.body)?.trim() || textOf(parts);
|
|
1753
|
+
const segment = record(boundary.preservedSegment);
|
|
1754
|
+
const head = active.findIndex((each) => each.id === segment?.headMessageId);
|
|
1755
|
+
const tail = active.findIndex((each) => each.id === segment?.tailMessageId);
|
|
1756
|
+
const keep = head >= 0 && tail >= head ? active.slice(head, tail + 1).map((each) => each.id) : [];
|
|
1757
|
+
builder.compaction(summary, num(boundary.preCompactTokenCount) ?? 0, at, keep);
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
1760
|
+
if (data.role === "user") {
|
|
1761
|
+
if (!sent) {
|
|
1762
|
+
builder.drop("ZCode notes for the app only");
|
|
1763
|
+
continue;
|
|
1764
|
+
}
|
|
1765
|
+
if (!shown) {
|
|
1766
|
+
builder.context(textOf(parts), at, message.id);
|
|
1767
|
+
continue;
|
|
1768
|
+
}
|
|
1769
|
+
const words = [];
|
|
1770
|
+
for (const part of parts) {
|
|
1771
|
+
if (part.type === "text") {
|
|
1772
|
+
const text = str(part.text) ?? "";
|
|
1773
|
+
if (part.synthetic === true) {
|
|
1774
|
+
builder.context(text, at, message.id);
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1777
|
+
const command = commandText(text);
|
|
1778
|
+
if (command !== void 0) firstCommand ??= command;
|
|
1779
|
+
else if (name === void 0 && text.trim()) name = firstLine(text);
|
|
1780
|
+
words.push({ type: "text", text: command ?? text });
|
|
1781
|
+
} else if (part.type === "file") {
|
|
1782
|
+
const mime = String(part.mime ?? "");
|
|
1783
|
+
const file = str(part.filename) ?? str(record(part.source)?.path) ?? str(part.url) ?? "a file";
|
|
1784
|
+
if (mime.startsWith("image/")) {
|
|
1785
|
+
words.push(imageFromUrl(part.url));
|
|
1786
|
+
continue;
|
|
1787
|
+
}
|
|
1788
|
+
words.push({ type: "text", text: `[${file}]` });
|
|
1789
|
+
const preview = str(record(record(part.metadata)?.preview)?.text);
|
|
1790
|
+
if (preview) builder.context(`Contents of ${file}:
|
|
1791
|
+
${preview}`, at, message.id);
|
|
1792
|
+
} else builder.drop("ZCode bookkeeping parts");
|
|
1793
|
+
}
|
|
1794
|
+
builder.user(words, at, message.id);
|
|
1795
|
+
continue;
|
|
1796
|
+
}
|
|
1797
|
+
if (data.role !== "assistant") {
|
|
1798
|
+
builder.drop("unrecognized records");
|
|
1799
|
+
continue;
|
|
1800
|
+
}
|
|
1801
|
+
const used = str(data.modelId) ?? str(data.modelID) ?? "";
|
|
1802
|
+
if (used) model = used;
|
|
1803
|
+
cwd ??= str(record(data.path)?.cwd);
|
|
1804
|
+
const blocks = [];
|
|
1805
|
+
const tools = [];
|
|
1806
|
+
for (const part of parts) {
|
|
1807
|
+
if (part.type === "text") blocks.push({ type: "text", text: str(part.text) ?? "" });
|
|
1808
|
+
else if (part.type === "reasoning") blocks.push({ type: "thinking", thinking: str(part.text) ?? "" });
|
|
1809
|
+
else if (part.type === "tool") {
|
|
1810
|
+
const id = str(part.callID);
|
|
1811
|
+
if (!id) continue;
|
|
1812
|
+
const state = record(part.state) ?? {};
|
|
1813
|
+
blocks.push({
|
|
1814
|
+
type: "toolCall",
|
|
1815
|
+
id,
|
|
1816
|
+
name: str(part.tool) ?? "unknown",
|
|
1817
|
+
arguments: toolArguments(state.input)
|
|
1818
|
+
});
|
|
1819
|
+
tools.push(part);
|
|
1820
|
+
} else if (!["step-start", "step-finish", "snapshot", "patch", "retry", "agent"].includes(String(part.type)))
|
|
1821
|
+
builder.drop("ZCode bookkeeping parts");
|
|
1822
|
+
}
|
|
1823
|
+
const error = record(data.error);
|
|
1824
|
+
const failed = error && !blocks.some((block) => block.type !== "thinking");
|
|
1825
|
+
builder.assistant({
|
|
1826
|
+
blocks: failed ? [] : blocks,
|
|
1827
|
+
model: used,
|
|
1828
|
+
at,
|
|
1829
|
+
key: message.id,
|
|
1830
|
+
...failed ? {
|
|
1831
|
+
error: str(record(error.data)?.message) ?? str(error.message) ?? str(error.name) ?? "The request failed."
|
|
1832
|
+
} : {},
|
|
1833
|
+
...data.finish === "length" ? { stopReason: "length" } : {}
|
|
1834
|
+
});
|
|
1835
|
+
for (const part of tools) {
|
|
1836
|
+
const state = record(part.state) ?? {};
|
|
1837
|
+
if (state.status !== "completed" && state.status !== "error") continue;
|
|
1838
|
+
const output = toolOutput(state);
|
|
1839
|
+
builder.toolResult({
|
|
1840
|
+
id: str(part.callID) ?? "",
|
|
1841
|
+
name: str(part.tool),
|
|
1842
|
+
content: output.content,
|
|
1843
|
+
isError: output.isError,
|
|
1844
|
+
at: num(record(state.time)?.end) ?? at,
|
|
1845
|
+
key: message.id
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
const started = iso(num(session.time_created)) ?? iso(messages[0]?.at) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1850
|
+
return complete(builder, {
|
|
1851
|
+
source,
|
|
1852
|
+
sourceId: where.id,
|
|
1853
|
+
cwd: cwd ?? options.cwd ?? process.cwd(),
|
|
1854
|
+
started,
|
|
1855
|
+
name: titleOf(session) || name || firstCommand,
|
|
1856
|
+
model,
|
|
1857
|
+
stamp,
|
|
1858
|
+
options
|
|
1339
1859
|
});
|
|
1340
1860
|
}
|
|
1341
1861
|
|
|
@@ -1350,6 +1870,7 @@ var CODEX_ITEMS = /* @__PURE__ */ new Set([
|
|
|
1350
1870
|
"reasoning"
|
|
1351
1871
|
]);
|
|
1352
1872
|
function detectTool(path) {
|
|
1873
|
+
if (parseZcodeSource(path)) return "zcode";
|
|
1353
1874
|
let tool;
|
|
1354
1875
|
let lines = 0;
|
|
1355
1876
|
readHead(
|
|
@@ -1372,6 +1893,27 @@ function detectTool(path) {
|
|
|
1372
1893
|
);
|
|
1373
1894
|
return tool;
|
|
1374
1895
|
}
|
|
1896
|
+
var CLAUDE_TITLES = [
|
|
1897
|
+
["custom-title", "customTitle"],
|
|
1898
|
+
["ai-title", "aiTitle"],
|
|
1899
|
+
["summary", "summary"]
|
|
1900
|
+
];
|
|
1901
|
+
function claudeTitle(path, head) {
|
|
1902
|
+
let tail = [];
|
|
1903
|
+
try {
|
|
1904
|
+
tail = readTail(path);
|
|
1905
|
+
} catch {
|
|
1906
|
+
}
|
|
1907
|
+
const found = /* @__PURE__ */ new Map();
|
|
1908
|
+
for (const value of [...head, ...tail]) {
|
|
1909
|
+
const data = record(value);
|
|
1910
|
+
const type = str(data?.type);
|
|
1911
|
+
const field = CLAUDE_TITLES.find(([name]) => name === type)?.[1];
|
|
1912
|
+
const title = field ? str(data?.[field])?.trim() : void 0;
|
|
1913
|
+
if (type && title) found.set(type, firstLine(title));
|
|
1914
|
+
}
|
|
1915
|
+
return CLAUDE_TITLES.map(([type]) => found.get(type)).find((title) => title !== void 0);
|
|
1916
|
+
}
|
|
1375
1917
|
var TREE_TYPES2 = /* @__PURE__ */ new Set(["user", "assistant", "system", "attachment"]);
|
|
1376
1918
|
function titleParts(text) {
|
|
1377
1919
|
if (classifyUserText(text) !== "user") return {};
|
|
@@ -1388,16 +1930,14 @@ function describeClaudeCode(path) {
|
|
|
1388
1930
|
let started;
|
|
1389
1931
|
let title;
|
|
1390
1932
|
let command;
|
|
1391
|
-
let named;
|
|
1392
1933
|
let first;
|
|
1934
|
+
const titles = [];
|
|
1393
1935
|
readHead(path, (value) => {
|
|
1394
1936
|
const data = record(value);
|
|
1395
1937
|
if (!data) return false;
|
|
1396
1938
|
id ??= str(data.sessionId);
|
|
1397
1939
|
const type = str(data.type);
|
|
1398
|
-
if (
|
|
1399
|
-
else if (type === "ai-title") named ??= str(data.aiTitle);
|
|
1400
|
-
else if (type === "summary") named ??= str(data.summary);
|
|
1940
|
+
if (CLAUDE_TITLES.some(([name]) => name === type)) titles.push(data);
|
|
1401
1941
|
if (!type || !TREE_TYPES2.has(type)) return false;
|
|
1402
1942
|
first ??= data.isSidechain === true;
|
|
1403
1943
|
if (first) agentId ??= str(data.agentId);
|
|
@@ -1419,7 +1959,7 @@ function describeClaudeCode(path) {
|
|
|
1419
1959
|
return {
|
|
1420
1960
|
id: first && agentId ? `agent-${agentId}` : id ?? "",
|
|
1421
1961
|
cwd,
|
|
1422
|
-
title:
|
|
1962
|
+
title: claudeTitle(path, titles) ?? title ?? command ?? "",
|
|
1423
1963
|
started,
|
|
1424
1964
|
subAgent: first === true
|
|
1425
1965
|
};
|
|
@@ -1467,11 +2007,11 @@ function describeCodex(path) {
|
|
|
1467
2007
|
const fromName = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i.exec(path)?.[1];
|
|
1468
2008
|
return { id: id ?? fromName ?? "", cwd, title: title ?? command ?? "", started, subAgent };
|
|
1469
2009
|
}
|
|
1470
|
-
function claudeProjectsDir(env = process.env, home =
|
|
1471
|
-
return
|
|
2010
|
+
function claudeProjectsDir(env = process.env, home = homedir3()) {
|
|
2011
|
+
return join3(env.CLAUDE_CONFIG_DIR ? absolute(env.CLAUDE_CONFIG_DIR) : join3(home, ".claude"), "projects");
|
|
1472
2012
|
}
|
|
1473
|
-
function codexSessionsDir(env = process.env, home =
|
|
1474
|
-
return
|
|
2013
|
+
function codexSessionsDir(env = process.env, home = homedir3()) {
|
|
2014
|
+
return join3(env.CODEX_HOME ? absolute(env.CODEX_HOME) : join3(home, ".codex"), "sessions");
|
|
1475
2015
|
}
|
|
1476
2016
|
function sameFolder(a, b) {
|
|
1477
2017
|
const canonical = (dir) => {
|
|
@@ -1493,17 +2033,43 @@ function jsonlFiles(dir, depth) {
|
|
|
1493
2033
|
}
|
|
1494
2034
|
const files = [];
|
|
1495
2035
|
for (const name of names.sort()) {
|
|
1496
|
-
const path =
|
|
2036
|
+
const path = join3(dir, name);
|
|
1497
2037
|
if (name.endsWith(".jsonl")) files.push(path);
|
|
1498
2038
|
else if (depth > 0 && !name.startsWith(".")) {
|
|
1499
2039
|
try {
|
|
1500
|
-
if (
|
|
2040
|
+
if (statSync5(path).isDirectory()) files.push(...jsonlFiles(path, depth - 1));
|
|
1501
2041
|
} catch {
|
|
1502
2042
|
}
|
|
1503
2043
|
}
|
|
1504
2044
|
}
|
|
1505
2045
|
return files;
|
|
1506
2046
|
}
|
|
2047
|
+
var checksFile = (store) => join3(absolute(store.agentDir), "import-checks.json");
|
|
2048
|
+
function readChecks(store) {
|
|
2049
|
+
try {
|
|
2050
|
+
const checks = record(JSON.parse(readFileSync(checksFile(store), "utf8"))) ?? {};
|
|
2051
|
+
return Object.fromEntries(
|
|
2052
|
+
Object.entries(checks).filter((entry) => typeof entry[1] === "string")
|
|
2053
|
+
);
|
|
2054
|
+
} catch {
|
|
2055
|
+
return {};
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
function writeChecks(store, checks) {
|
|
2059
|
+
const file = checksFile(store);
|
|
2060
|
+
try {
|
|
2061
|
+
mkdirSync(absolute(store.agentDir), { recursive: true });
|
|
2062
|
+
writeFileSync(`${file}.tmp`, `${JSON.stringify(checks, null, 1)}
|
|
2063
|
+
`, { mode: 384 });
|
|
2064
|
+
renameSync2(`${file}.tmp`, file);
|
|
2065
|
+
} catch {
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
function changedSince(origin, stamp, modified, checked) {
|
|
2069
|
+
if (stamp !== void 0 && stamp === checked) return false;
|
|
2070
|
+
if (origin.stamp !== void 0) return stamp !== void 0 && stamp !== origin.stamp;
|
|
2071
|
+
return modified > origin.importedAt;
|
|
2072
|
+
}
|
|
1507
2073
|
function listConversations(options = {}) {
|
|
1508
2074
|
const claudeProjects = options.claudeProjects ?? claudeProjectsDir();
|
|
1509
2075
|
const codexSessions = options.codexSessions ?? codexSessionsDir();
|
|
@@ -1514,22 +2080,22 @@ function listConversations(options = {}) {
|
|
|
1514
2080
|
} catch {
|
|
1515
2081
|
}
|
|
1516
2082
|
for (const project of projects) {
|
|
1517
|
-
for (const path of jsonlFiles(
|
|
2083
|
+
for (const path of jsonlFiles(join3(claudeProjects, project), 0)) candidates.push({ tool: "claude-code", path });
|
|
1518
2084
|
}
|
|
1519
2085
|
for (const path of jsonlFiles(codexSessions, 4)) candidates.push({ tool: "codex", path });
|
|
1520
|
-
const
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
2086
|
+
const imports = options.store ? findImports(options.store) : [];
|
|
2087
|
+
const checks = options.store ? readChecks(options.store) : {};
|
|
2088
|
+
const earlier = (tool, id, path) => findImport(imports, tool, id, path);
|
|
2089
|
+
const mark = (found, stamp, modified) => found ? {
|
|
2090
|
+
importedAs: found.sessionFile,
|
|
2091
|
+
...changedSince(found.origin, stamp, modified, checks[found.origin.source]) ? { changed: true } : {}
|
|
2092
|
+
} : {};
|
|
1527
2093
|
const conversations = [];
|
|
1528
2094
|
for (const { tool, path } of candidates) {
|
|
1529
2095
|
let size;
|
|
1530
2096
|
let modified;
|
|
1531
2097
|
try {
|
|
1532
|
-
const stats =
|
|
2098
|
+
const stats = statSync5(path);
|
|
1533
2099
|
size = stats.size;
|
|
1534
2100
|
modified = stats.mtime.toISOString();
|
|
1535
2101
|
} catch {
|
|
@@ -1543,16 +2109,75 @@ function listConversations(options = {}) {
|
|
|
1543
2109
|
}
|
|
1544
2110
|
if (described.subAgent && !options.subAgents) continue;
|
|
1545
2111
|
if (options.cwd && !(described.cwd && sameFolder(described.cwd, options.cwd))) continue;
|
|
1546
|
-
const
|
|
1547
|
-
conversations.push({ tool, path, ...described, modified, size, ...
|
|
2112
|
+
const found = earlier(tool, described.id, path);
|
|
2113
|
+
conversations.push({ tool, path, ...described, modified, size, ...mark(found, fileStamp(path), modified) });
|
|
2114
|
+
}
|
|
2115
|
+
for (const zcode of listZcode(options.zcodeDatabase ?? zcodeDatabase(), { subAgents: options.subAgents })) {
|
|
2116
|
+
if (options.cwd && !(zcode.cwd && sameFolder(zcode.cwd, options.cwd))) continue;
|
|
2117
|
+
const { source, stamp, ...described } = zcode;
|
|
2118
|
+
const found = earlier("zcode", zcode.id, source);
|
|
2119
|
+
conversations.push({ tool: "zcode", path: source, ...described, ...mark(found, stamp, zcode.modified) });
|
|
1548
2120
|
}
|
|
1549
2121
|
return conversations.sort((a, b) => b.modified.localeCompare(a.modified));
|
|
1550
2122
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
2123
|
+
var NOT_A_TRANSCRIPT = "not a Claude Code, Codex or ZCode transcript";
|
|
2124
|
+
function read(tool, source, options) {
|
|
2125
|
+
if (tool === "zcode") return readZcode(source, options);
|
|
2126
|
+
return tool === "codex" ? readCodex(source, options) : readClaudeCode(source, options);
|
|
2127
|
+
}
|
|
2128
|
+
function laterEntries(file, origin) {
|
|
2129
|
+
const written = origin.counts?.entries;
|
|
2130
|
+
if (!written) return void 0;
|
|
2131
|
+
let lines;
|
|
2132
|
+
try {
|
|
2133
|
+
lines = readFileSync(file, "utf8").split("\n");
|
|
2134
|
+
} catch {
|
|
2135
|
+
return void 0;
|
|
2136
|
+
}
|
|
2137
|
+
const later = [];
|
|
2138
|
+
for (const line of lines.slice(written + 1)) {
|
|
2139
|
+
if (!line.trim()) continue;
|
|
2140
|
+
try {
|
|
2141
|
+
const entry = record(JSON.parse(line));
|
|
2142
|
+
if (entry) later.push(entry);
|
|
2143
|
+
} catch {
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
return later;
|
|
2147
|
+
}
|
|
2148
|
+
var SETTINGS_ENTRIES = /* @__PURE__ */ new Set([
|
|
2149
|
+
"session_info",
|
|
2150
|
+
"model_change",
|
|
2151
|
+
"thinking_level_change",
|
|
2152
|
+
"custom",
|
|
2153
|
+
"label"
|
|
2154
|
+
]);
|
|
2155
|
+
function updatedEntries(earlier, converted) {
|
|
2156
|
+
const later = laterEntries(earlier.sessionFile, earlier.origin);
|
|
2157
|
+
if (!later || later.some((entry) => !SETTINGS_ENTRIES.has(String(entry.type)))) return void 0;
|
|
2158
|
+
const entries = [...converted.entries];
|
|
2159
|
+
const ids = new Set(later.map((entry) => entry.id));
|
|
2160
|
+
let leaf = entries.at(-1).id ?? null;
|
|
2161
|
+
for (const entry of later) {
|
|
2162
|
+
const parentId = typeof entry.parentId === "string" && ids.has(entry.parentId) ? entry.parentId : leaf;
|
|
2163
|
+
entries.push({ ...entry, parentId });
|
|
2164
|
+
leaf = String(entry.id);
|
|
2165
|
+
}
|
|
2166
|
+
return entries;
|
|
2167
|
+
}
|
|
2168
|
+
function sameAs(earlier, converted) {
|
|
2169
|
+
const digest = record(record(converted.entries[1])?.details)?.digest;
|
|
2170
|
+
if (earlier.digest !== void 0) return earlier.digest === digest;
|
|
2171
|
+
const before = earlier.counts;
|
|
2172
|
+
const after = converted.counts;
|
|
2173
|
+
return before?.user === after.user && before.assistant === after.assistant && before.toolCalls === after.toolCalls;
|
|
2174
|
+
}
|
|
2175
|
+
async function importOne(path, options, imports, checks) {
|
|
2176
|
+
const zcode = parseZcodeSource(path);
|
|
2177
|
+
const source = zcode ? zcodeSource(zcode.database, zcode.id) : absolute(path);
|
|
1553
2178
|
const failed = (error) => ({ status: "failed", source, error });
|
|
1554
2179
|
try {
|
|
1555
|
-
if (!
|
|
2180
|
+
if (!statSync5(zcode ? zcode.database : source).isFile()) return failed("not a file");
|
|
1556
2181
|
} catch {
|
|
1557
2182
|
return failed("no such file");
|
|
1558
2183
|
}
|
|
@@ -1565,39 +2190,61 @@ async function importOne(path, options, imports) {
|
|
|
1565
2190
|
});
|
|
1566
2191
|
try {
|
|
1567
2192
|
const tool = detectTool(source);
|
|
1568
|
-
if (!tool) return failed(
|
|
1569
|
-
const quick = (tool === "codex" ? describeCodex(source) : describeClaudeCode(source)).id;
|
|
1570
|
-
|
|
1571
|
-
if (
|
|
1572
|
-
|
|
1573
|
-
const
|
|
1574
|
-
|
|
2193
|
+
if (!tool) return failed(NOT_A_TRANSCRIPT);
|
|
2194
|
+
const quick = zcode ? zcode.id : (tool === "codex" ? describeCodex(source) : describeClaudeCode(source)).id;
|
|
2195
|
+
let earlier = findImport(imports, tool, quick, source);
|
|
2196
|
+
if (earlier?.origin.stamp !== void 0 && earlier.origin.stamp === (zcode ? void 0 : fileStamp(source)))
|
|
2197
|
+
return already(tool, earlier);
|
|
2198
|
+
const readOptions = (keep) => ({
|
|
2199
|
+
cwd: options.cwd,
|
|
2200
|
+
now: options.now,
|
|
2201
|
+
...options.model ? { model: options.model } : {},
|
|
2202
|
+
...keep ? { sessionId: keep.sessionId } : {}
|
|
2203
|
+
});
|
|
2204
|
+
let converted = await read(tool, source, readOptions(earlier));
|
|
2205
|
+
if (!earlier) {
|
|
2206
|
+
earlier = findImport(imports, tool, converted.sourceId, source);
|
|
2207
|
+
if (earlier) converted = await read(tool, source, readOptions(earlier));
|
|
2208
|
+
}
|
|
2209
|
+
if (earlier && sameAs(earlier.origin, converted)) {
|
|
2210
|
+
const stamp = record(record(converted.entries[1])?.details)?.stamp;
|
|
2211
|
+
if (typeof stamp === "string") checks[source] = stamp;
|
|
2212
|
+
return already(tool, earlier);
|
|
2213
|
+
}
|
|
1575
2214
|
if (!converted.hasConversation) return failed("no messages in this transcript");
|
|
2215
|
+
const written = {
|
|
2216
|
+
source,
|
|
2217
|
+
tool,
|
|
2218
|
+
sessionFile: "",
|
|
2219
|
+
sessionId: converted.sessionId,
|
|
2220
|
+
name: converted.name,
|
|
2221
|
+
cwd: converted.cwd,
|
|
2222
|
+
counts: converted.counts
|
|
2223
|
+
};
|
|
2224
|
+
const update = earlier ? updatedEntries(earlier, converted) : void 0;
|
|
2225
|
+
const origin = record(converted.entries[1])?.details;
|
|
2226
|
+
if (earlier && update) {
|
|
2227
|
+
replaceSessionFile(earlier.sessionFile, update);
|
|
2228
|
+
const before = earlier.origin.counts;
|
|
2229
|
+
imports.splice(imports.indexOf(earlier), 1, { ...earlier, origin });
|
|
2230
|
+
return { status: "updated", ...written, sessionFile: earlier.sessionFile, before };
|
|
2231
|
+
}
|
|
2232
|
+
if (earlier) converted = await read(tool, source, readOptions());
|
|
1576
2233
|
const dir = sessionDirFor(converted.cwd, options);
|
|
1577
2234
|
mkdirSync(dir, { recursive: true });
|
|
1578
|
-
const sessionFile =
|
|
2235
|
+
const sessionFile = join3(dir, sessionFileName(converted.started, converted.sessionId));
|
|
1579
2236
|
writeSessionFile(sessionFile, converted.entries);
|
|
1580
2237
|
imports.push({
|
|
1581
2238
|
sessionFile,
|
|
1582
2239
|
sessionId: converted.sessionId,
|
|
1583
|
-
origin:
|
|
1584
|
-
version: 1,
|
|
1585
|
-
tool,
|
|
1586
|
-
source,
|
|
1587
|
-
sourceId: converted.sourceId,
|
|
1588
|
-
importedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1589
|
-
counts: converted.counts
|
|
1590
|
-
}
|
|
2240
|
+
origin: record(converted.entries[1])?.details
|
|
1591
2241
|
});
|
|
1592
2242
|
return {
|
|
1593
2243
|
status: "imported",
|
|
1594
|
-
|
|
1595
|
-
tool,
|
|
1596
|
-
sessionFile,
|
|
2244
|
+
...written,
|
|
1597
2245
|
sessionId: converted.sessionId,
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
counts: converted.counts
|
|
2246
|
+
sessionFile,
|
|
2247
|
+
...earlier ? { earlier: earlier.sessionFile } : {}
|
|
1601
2248
|
};
|
|
1602
2249
|
} catch (error) {
|
|
1603
2250
|
return failed(error instanceof Error ? error.message : String(error));
|
|
@@ -1605,23 +2252,27 @@ async function importOne(path, options, imports) {
|
|
|
1605
2252
|
}
|
|
1606
2253
|
async function importTranscripts(paths, options) {
|
|
1607
2254
|
const imports = findImports(options);
|
|
2255
|
+
const checks = readChecks(options);
|
|
2256
|
+
const before = JSON.stringify(checks);
|
|
1608
2257
|
const results = [];
|
|
1609
|
-
for (const path of paths) results.push(await importOne(path, options, imports));
|
|
2258
|
+
for (const path of paths) results.push(await importOne(path, options, imports, checks));
|
|
2259
|
+
if (JSON.stringify(checks) !== before) writeChecks(options, checks);
|
|
1610
2260
|
return results;
|
|
1611
2261
|
}
|
|
1612
2262
|
|
|
1613
2263
|
// packages/kyrn-judge/src/import/command.ts
|
|
1614
2264
|
var USAGE = [
|
|
1615
|
-
"mu import:
|
|
2265
|
+
"mu import: import Claude Code, Codex and ZCode conversations so you can continue them in mu.",
|
|
1616
2266
|
"",
|
|
1617
|
-
" mu import --list [--json] [--cwd <dir>]
|
|
1618
|
-
" mu import <file>... [--json] import these
|
|
2267
|
+
" mu import --list [--json] [--cwd <dir>] list conversations on this machine (with --cwd, only <dir>'s)",
|
|
2268
|
+
" mu import <file>... [--json] import these conversations; each becomes a mu session",
|
|
1619
2269
|
"",
|
|
1620
|
-
" --agent-dir <dir> mu's agent folder (default:
|
|
1621
|
-
" --sub-agents also list
|
|
2270
|
+
" --agent-dir <dir> mu's agent folder (default: MU_AGENT_DIR, else ~/.mu/agent)",
|
|
2271
|
+
" --sub-agents also list subagent threads",
|
|
1622
2272
|
"",
|
|
1623
|
-
"Looks in ~/.claude/projects (CLAUDE_CONFIG_DIR)
|
|
1624
|
-
"A conversation
|
|
2273
|
+
"Looks in ~/.claude/projects (CLAUDE_CONFIG_DIR), ~/.codex/sessions (CODEX_HOME) and ~/.zcode/cli/db/db.sqlite.",
|
|
2274
|
+
"A ZCode conversation is named <database>#<id>, as --list shows it.",
|
|
2275
|
+
"Importing a conversation again brings over what's new; one that hasn't changed is skipped."
|
|
1625
2276
|
].join("\n");
|
|
1626
2277
|
function parseArgs(argv) {
|
|
1627
2278
|
const parsed = { help: false, list: false, json: false, subAgents: false, files: [] };
|
|
@@ -1647,24 +2298,33 @@ function parseArgs(argv) {
|
|
|
1647
2298
|
} else if (arg.startsWith("--")) return { error: `unknown option ${arg}` };
|
|
1648
2299
|
else parsed.files.push(arg);
|
|
1649
2300
|
}
|
|
1650
|
-
if (parsed.list && parsed.files.length > 0) return { error: "--list
|
|
2301
|
+
if (parsed.list && parsed.files.length > 0) return { error: "--list doesn't take files" };
|
|
1651
2302
|
return parsed;
|
|
1652
2303
|
}
|
|
1653
2304
|
function storeFor(agentDirArg, io) {
|
|
1654
2305
|
const agentDir = absolute(
|
|
1655
|
-
agentDirArg ?? io.env.MU_CODING_AGENT_DIR ?? io.env.KYRN_CODING_AGENT_DIR ??
|
|
2306
|
+
agentDirArg ?? io.env.MU_CODING_AGENT_DIR ?? io.env.KYRN_CODING_AGENT_DIR ?? join4(io.home, ".mu", "agent"),
|
|
1656
2307
|
io.cwd
|
|
1657
2308
|
);
|
|
1658
2309
|
const fromEnv = io.env.MU_CODING_AGENT_SESSION_DIR;
|
|
1659
2310
|
if (fromEnv) return { agentDir, sessionDir: absolute(fromEnv, io.cwd) };
|
|
2311
|
+
const sessionDir = settingsOf(agentDir).sessionDir;
|
|
2312
|
+
if (typeof sessionDir === "string" && sessionDir.trim())
|
|
2313
|
+
return { agentDir, sessionDir: absolute(sessionDir, io.cwd) };
|
|
2314
|
+
return { agentDir };
|
|
2315
|
+
}
|
|
2316
|
+
function settingsOf(agentDir) {
|
|
1660
2317
|
try {
|
|
1661
|
-
|
|
1662
|
-
const sessionDir = settings !== null && typeof settings === "object" && "sessionDir" in settings ? settings.sessionDir : void 0;
|
|
1663
|
-
if (typeof sessionDir === "string" && sessionDir.trim())
|
|
1664
|
-
return { agentDir, sessionDir: absolute(sessionDir, io.cwd) };
|
|
2318
|
+
return record(JSON.parse(readFileSync2(join4(agentDir, "settings.json"), "utf8"))) ?? {};
|
|
1665
2319
|
} catch {
|
|
2320
|
+
return {};
|
|
1666
2321
|
}
|
|
1667
|
-
|
|
2322
|
+
}
|
|
2323
|
+
function defaultModelOf(agentDir) {
|
|
2324
|
+
const settings = settingsOf(agentDir);
|
|
2325
|
+
const provider = settings.defaultProvider;
|
|
2326
|
+
const modelId = settings.defaultModel;
|
|
2327
|
+
return typeof provider === "string" && provider && typeof modelId === "string" && modelId ? { provider, modelId } : void 0;
|
|
1668
2328
|
}
|
|
1669
2329
|
function formatSize(bytes) {
|
|
1670
2330
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -1677,8 +2337,8 @@ function formatSize(bytes) {
|
|
|
1677
2337
|
}
|
|
1678
2338
|
return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;
|
|
1679
2339
|
}
|
|
1680
|
-
function formatDate(
|
|
1681
|
-
const date =
|
|
2340
|
+
function formatDate(iso2) {
|
|
2341
|
+
const date = iso2 ? new Date(iso2) : void 0;
|
|
1682
2342
|
if (!date || Number.isNaN(date.getTime())) return "????-??-?? ??:??";
|
|
1683
2343
|
const two = (value) => String(value).padStart(2, "0");
|
|
1684
2344
|
return `${date.getFullYear()}-${two(date.getMonth() + 1)}-${two(date.getDate())} ${two(date.getHours())}:${two(date.getMinutes())}`;
|
|
@@ -1687,7 +2347,7 @@ function tilde(path, home) {
|
|
|
1687
2347
|
return path === home ? "~" : path.startsWith(home + sep) ? `~${path.slice(home.length)}` : path;
|
|
1688
2348
|
}
|
|
1689
2349
|
function formatList(conversations, home) {
|
|
1690
|
-
if (conversations.length === 0) return "No Claude Code or
|
|
2350
|
+
if (conversations.length === 0) return "No Claude Code, Codex or ZCode conversations found.";
|
|
1691
2351
|
const groups = /* @__PURE__ */ new Map();
|
|
1692
2352
|
for (const conversation of conversations) {
|
|
1693
2353
|
const key = conversation.cwd ?? "";
|
|
@@ -1699,35 +2359,52 @@ function formatList(conversations, home) {
|
|
|
1699
2359
|
const toolWidth = Math.max(...Object.values(TOOL_NAMES).map((name) => name.length));
|
|
1700
2360
|
for (const [cwd, group] of groups) {
|
|
1701
2361
|
if (lines.length > 0) lines.push("");
|
|
1702
|
-
lines.push(cwd ? tilde(cwd, home) : "(folder
|
|
2362
|
+
lines.push(cwd ? tilde(cwd, home) : "(unknown folder)");
|
|
1703
2363
|
for (const conversation of group) {
|
|
1704
2364
|
const title = conversation.title || "(no message found)";
|
|
1705
|
-
const mark = conversation.importedAs ? " (imported)" : "";
|
|
2365
|
+
const mark = !conversation.importedAs ? "" : conversation.changed ? " (imported, changed since)" : " (imported)";
|
|
1706
2366
|
lines.push(
|
|
1707
2367
|
` ${formatDate(conversation.modified)} ${TOOL_NAMES[conversation.tool].padEnd(toolWidth)} ${formatSize(conversation.size).padStart(7)} ${title}${mark}`
|
|
1708
2368
|
);
|
|
1709
2369
|
lines.push(`${" ".repeat(20)}${tilde(conversation.path, home)}`);
|
|
1710
2370
|
}
|
|
1711
2371
|
}
|
|
1712
|
-
const
|
|
1713
|
-
lines.push("", `${
|
|
2372
|
+
const count2 = conversations.length;
|
|
2373
|
+
lines.push("", `${count2} conversation${count2 === 1 ? "" : "s"}. Import one with: mu import <file>`);
|
|
1714
2374
|
return lines.join("\n");
|
|
1715
2375
|
}
|
|
1716
2376
|
function formatResult(result, home) {
|
|
1717
|
-
if (result.status === "failed") return `
|
|
2377
|
+
if (result.status === "failed") return `Couldn't import ${tilde(result.source, home)}: ${result.error}`;
|
|
1718
2378
|
if (result.status === "already-imported")
|
|
1719
2379
|
return [
|
|
1720
|
-
`Already imported: ${tilde(result.source, home)}`,
|
|
1721
|
-
`
|
|
2380
|
+
`Already imported and unchanged: ${tilde(result.source, home)}`,
|
|
2381
|
+
` as session ${tilde(result.sessionFile, home)}`
|
|
1722
2382
|
].join("\n");
|
|
1723
2383
|
const { counts } = result;
|
|
1724
|
-
const
|
|
1725
|
-
|
|
2384
|
+
const skipped = Object.entries(counts.dropped).sort((a, b) => b[1] - a[1]);
|
|
2385
|
+
const width = Math.max(0, ...skipped.map(([, count2]) => String(count2).length));
|
|
2386
|
+
const left = skipped.map(([reason, count2]) => ` ${String(count2).padStart(width)} ${reason}`);
|
|
2387
|
+
const kept = [
|
|
2388
|
+
...counts.thinking > 0 ? [count(counts.thinking, "thinking block")] : [],
|
|
2389
|
+
...counts.images > 0 ? [count(counts.images, "image")] : [],
|
|
2390
|
+
...counts.compactions > 0 ? [count(counts.compactions, "compaction")] : []
|
|
2391
|
+
];
|
|
2392
|
+
const head = result.status === "updated" ? [
|
|
2393
|
+
`Brought up to date from ${TOOL_NAMES[result.tool]}: "${result.name}"`,
|
|
2394
|
+
` ${tilde(result.sessionFile, home)}`,
|
|
2395
|
+
` new since the last import: ${count(Math.max(0, counts.user - (result.before?.user ?? 0)), "message")} from you, ${count(Math.max(0, counts.assistant - (result.before?.assistant ?? 0)), "reply", "replies")}`
|
|
2396
|
+
] : [
|
|
1726
2397
|
`Imported from ${TOOL_NAMES[result.tool]}: "${result.name}"`,
|
|
1727
2398
|
` ${tilde(result.sessionFile, home)}`,
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
2399
|
+
...result.earlier ? [
|
|
2400
|
+
` the earlier import was continued in mu, so it was left untouched: ${tilde(result.earlier, home)}`
|
|
2401
|
+
] : []
|
|
2402
|
+
];
|
|
2403
|
+
return [
|
|
2404
|
+
...head,
|
|
2405
|
+
` ${count(counts.user, "message")} from you, ${count(counts.assistant, "reply", "replies")}, ${count(counts.toolCalls, "tool call")}${kept.length > 0 ? `, ${kept.join(", ")}` : ""}`,
|
|
2406
|
+
...left.length > 0 ? [" skipped:", ...left] : [],
|
|
2407
|
+
` to continue: mu --session ${result.sessionFile}`
|
|
1731
2408
|
].join("\n");
|
|
1732
2409
|
}
|
|
1733
2410
|
async function main(argv, io) {
|
|
@@ -1754,7 +2431,7 @@ ${USAGE}`);
|
|
|
1754
2431
|
}
|
|
1755
2432
|
const results = await importTranscripts(
|
|
1756
2433
|
parsed.files.map((file) => absolute(file, io.cwd)),
|
|
1757
|
-
{ ...store, cwd: io.cwd }
|
|
2434
|
+
{ ...store, cwd: io.cwd, model: defaultModelOf(store.agentDir) }
|
|
1758
2435
|
);
|
|
1759
2436
|
io.out(
|
|
1760
2437
|
parsed.json ? JSON.stringify({ results }, null, 2) : results.map((result) => formatResult(result, io.home)).join("\n\n")
|
|
@@ -1769,7 +2446,7 @@ function processIo() {
|
|
|
1769
2446
|
`),
|
|
1770
2447
|
env: process.env,
|
|
1771
2448
|
cwd: process.cwd(),
|
|
1772
|
-
home:
|
|
2449
|
+
home: homedir4()
|
|
1773
2450
|
};
|
|
1774
2451
|
}
|
|
1775
2452
|
|