mu-agent 0.1.10 → 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 +94 -0
- package/README.md +6 -6
- package/dist/bundle/chunks/{chunk-O7IPTASE.js → chunk-3J3HSHLX.js} +22 -21
- package/dist/bundle/chunks/{chunk-DNNHZBQR.js → chunk-6Z2347ML.js} +1 -1
- package/dist/bundle/chunks/{chunk-X234222Q.js → chunk-DYTENXHX.js} +2 -2
- package/dist/bundle/chunks/{chunk-RAQ6TU76.js → chunk-FLHKNMLQ.js} +1 -1
- package/dist/bundle/chunks/chunk-LHRPMXVI.js +2 -0
- package/dist/bundle/chunks/{chunk-HZNN636Z.js → chunk-LT2C56HV.js} +1 -1
- package/dist/bundle/chunks/{chunk-SGOTYFMT.js → chunk-MF3VB62R.js} +1 -1
- package/dist/bundle/chunks/{chunk-QOEEJKS4.js → chunk-URBBMV3M.js} +1 -1
- package/dist/bundle/chunks/{chunk-VJLOQZKR.js → chunk-WCJNJQO5.js} +1 -1
- package/dist/bundle/chunks/{chunk-FN6OEZYR.js → chunk-WDURT4ZM.js} +1 -1
- package/dist/bundle/chunks/{cli-722OL3K5.js → cli-M4YBZZ6Q.js} +1 -1
- package/dist/bundle/chunks/{easter-egg-3d-6H34EDGX.js → easter-egg-3d-VFY3YRQR.js} +1 -1
- package/dist/bundle/chunks/{execute-KVSEIDWL.js → execute-FKI7Y4XR.js} +1 -1
- package/dist/bundle/chunks/{runtime-O3ELDOM7.js → runtime-IKE5OQIW.js} +1 -1
- package/dist/bundle/chunks/{virtual-modules-IKEARUOZ.js → virtual-modules-TJPHLTRT.js} +1 -1
- package/dist/bundle/cli-runtime.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/docs/settings.md +1 -1
- package/judge/dist/auth.js +51 -26
- package/judge/dist/chrome.js +2 -2
- package/judge/dist/import.js +833 -161
- package/judge/dist/setup.js +129 -46
- package/judge/manifest.json +1935 -349
- 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/mu/index.js +4509 -32801
- package/judge/mu/multipart-parser-ZDXVIOOP.js +372 -0
- package/judge/mu/src-TVVA5ZK2.js +1409 -0
- package/kyrn/bin/kyrn-doctor +28 -3
- package/kyrn/bin/kyrn-judge-local +42 -13
- package/kyrn/bin/kyrn-ledger +16 -1
- package/kyrn/bin/mu.mjs +77 -23
- package/kyrn/local-judge/server.py +26 -14
- package/package.json +6 -3
- package/dist/bundle/chunks/chunk-BSS6T6KQ.js +0 -2
package/judge/dist/import.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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
7
|
|
|
8
8
|
// packages/kyrn-judge/src/language.ts
|
|
9
9
|
function count(n, one, many = `${one}s`) {
|
|
@@ -11,16 +11,16 @@ function count(n, one, many = `${one}s`) {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
// packages/kyrn-judge/src/import/index.ts
|
|
14
|
-
import { mkdirSync, readdirSync as readdirSync2, realpathSync, statSync as
|
|
15
|
-
import { homedir as
|
|
16
|
-
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";
|
|
17
17
|
|
|
18
18
|
// packages/kyrn-judge/src/import/claude-code.ts
|
|
19
19
|
import { statSync as statSync2 } from "node:fs";
|
|
20
20
|
import { basename } from "node:path";
|
|
21
21
|
|
|
22
22
|
// packages/kyrn-judge/src/import/jsonl.ts
|
|
23
|
-
import { closeSync, createReadStream, openSync, readSync } from "node:fs";
|
|
23
|
+
import { closeSync, createReadStream, fstatSync, openSync, readSync } from "node:fs";
|
|
24
24
|
var MAX_LINE_BYTES = 64 * 1024 * 1024;
|
|
25
25
|
function parse(bytes) {
|
|
26
26
|
const text = bytes.toString("utf8").trim();
|
|
@@ -74,9 +74,9 @@ function readHead(path, visit, limit = 1024 * 1024) {
|
|
|
74
74
|
try {
|
|
75
75
|
const chunk = Buffer.allocUnsafe(64 * 1024);
|
|
76
76
|
let pending = Buffer.alloc(0);
|
|
77
|
-
let
|
|
78
|
-
while (
|
|
79
|
-
const count2 = readSync(fd, chunk, 0, Math.min(chunk.length, limit -
|
|
77
|
+
let read2 = 0;
|
|
78
|
+
while (read2 < limit) {
|
|
79
|
+
const count2 = readSync(fd, chunk, 0, Math.min(chunk.length, limit - read2), null);
|
|
80
80
|
if (count2 === 0) {
|
|
81
81
|
if (!blank(pending)) {
|
|
82
82
|
const last = parse(pending);
|
|
@@ -84,7 +84,7 @@ function readHead(path, visit, limit = 1024 * 1024) {
|
|
|
84
84
|
}
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
87
|
-
|
|
87
|
+
read2 += count2;
|
|
88
88
|
pending = Buffer.concat([pending, chunk.subarray(0, count2)]);
|
|
89
89
|
let start = 0;
|
|
90
90
|
for (let newline = pending.indexOf(10, start); newline !== -1; newline = pending.indexOf(10, start)) {
|
|
@@ -100,6 +100,29 @@ function readHead(path, visit, limit = 1024 * 1024) {
|
|
|
100
100
|
closeSync(fd);
|
|
101
101
|
}
|
|
102
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
|
+
}
|
|
103
126
|
function record(value) {
|
|
104
127
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
105
128
|
}
|
|
@@ -111,16 +134,24 @@ function list(value) {
|
|
|
111
134
|
}
|
|
112
135
|
|
|
113
136
|
// packages/kyrn-judge/src/import/session.ts
|
|
114
|
-
import { randomBytes, randomUUID } from "node:crypto";
|
|
115
|
-
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";
|
|
116
139
|
import { homedir } from "node:os";
|
|
117
140
|
import { isAbsolute, join, resolve } from "node:path";
|
|
118
|
-
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);
|
|
119
147
|
var IMPORT_MARKER = "mu.import";
|
|
120
148
|
var IMPORT_CONTEXT = "mu.import.context";
|
|
121
149
|
var SESSION_VERSION = 3;
|
|
122
150
|
var MAX_RESULT_CHARS = 1e5;
|
|
123
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"]);
|
|
124
155
|
var NO_RESULT = "No result was recorded for this call.";
|
|
125
156
|
function emptyCounts() {
|
|
126
157
|
return {
|
|
@@ -130,6 +161,8 @@ function emptyCounts() {
|
|
|
130
161
|
assistant: 0,
|
|
131
162
|
toolCalls: 0,
|
|
132
163
|
toolResults: 0,
|
|
164
|
+
thinking: 0,
|
|
165
|
+
images: 0,
|
|
133
166
|
context: 0,
|
|
134
167
|
compactions: 0,
|
|
135
168
|
missingResults: 0,
|
|
@@ -193,8 +226,7 @@ function readOrigin(file) {
|
|
|
193
226
|
}
|
|
194
227
|
const details = record(marker?.details);
|
|
195
228
|
if (marker?.type !== "custom_message" || marker.customType !== IMPORT_MARKER || !details) return void 0;
|
|
196
|
-
|
|
197
|
-
if (tool !== "claude-code" && tool !== "codex") return void 0;
|
|
229
|
+
if (!isImportTool(details.tool)) return void 0;
|
|
198
230
|
if (typeof details.source !== "string") return void 0;
|
|
199
231
|
return { sessionId: str(header?.id) ?? "", origin: details };
|
|
200
232
|
}
|
|
@@ -221,15 +253,19 @@ function findImports(store) {
|
|
|
221
253
|
scan(sessionsRoot(store), 1);
|
|
222
254
|
const found = [];
|
|
223
255
|
for (const file of files) {
|
|
224
|
-
const
|
|
225
|
-
if (
|
|
256
|
+
const read2 = readOrigin(file);
|
|
257
|
+
if (read2) found.push({ sessionFile: file, sessionId: read2.sessionId, origin: read2.origin });
|
|
226
258
|
}
|
|
227
259
|
return found;
|
|
228
260
|
}
|
|
229
261
|
function findImport(imports, tool, sourceId, source) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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;
|
|
233
269
|
}
|
|
234
270
|
function firstLine(text, max = 80) {
|
|
235
271
|
const line = text.split(/\r?\n/).map((part) => part.replace(/\s+/g, " ").trim()).find((part) => part.length > 0) ?? "";
|
|
@@ -317,13 +353,13 @@ function toolArguments(raw) {
|
|
|
317
353
|
}
|
|
318
354
|
function unknownBlockText(block) {
|
|
319
355
|
const { type, ...rest } = block;
|
|
320
|
-
let
|
|
356
|
+
let json2;
|
|
321
357
|
try {
|
|
322
|
-
|
|
358
|
+
json2 = JSON.stringify(rest);
|
|
323
359
|
} catch {
|
|
324
|
-
|
|
360
|
+
json2 = "";
|
|
325
361
|
}
|
|
326
|
-
return `[${typeof type === "string" ? type : "block"}] ${clip(
|
|
362
|
+
return `[${typeof type === "string" ? type : "block"}] ${clip(json2, 2e3).text}`.trim();
|
|
327
363
|
}
|
|
328
364
|
var ZERO_USAGE = {
|
|
329
365
|
input: 0,
|
|
@@ -334,6 +370,29 @@ var ZERO_USAGE = {
|
|
|
334
370
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
|
|
335
371
|
};
|
|
336
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
|
+
}
|
|
337
396
|
var SessionBuilder = class {
|
|
338
397
|
tool;
|
|
339
398
|
counts = emptyCounts();
|
|
@@ -356,15 +415,17 @@ var SessionBuilder = class {
|
|
|
356
415
|
get hasConversation() {
|
|
357
416
|
return this.counts.user + this.counts.assistant > 0;
|
|
358
417
|
}
|
|
359
|
-
/**
|
|
360
|
-
|
|
418
|
+
/** Text and images as they were; an image without its data, or larger than any model takes, says so instead. */
|
|
419
|
+
blocksOf(content) {
|
|
361
420
|
if (typeof content === "string") return [{ type: "text", text: content }];
|
|
362
421
|
const blocks = [];
|
|
363
422
|
for (const block of content) {
|
|
364
|
-
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 {
|
|
365
426
|
this.drop("images");
|
|
366
427
|
blocks.push({ type: "text", text: IMAGE_MARKER });
|
|
367
|
-
}
|
|
428
|
+
}
|
|
368
429
|
}
|
|
369
430
|
return blocks;
|
|
370
431
|
}
|
|
@@ -405,7 +466,7 @@ var SessionBuilder = class {
|
|
|
405
466
|
this.held = [];
|
|
406
467
|
}
|
|
407
468
|
user(content, at, key) {
|
|
408
|
-
const blocks = this.
|
|
469
|
+
const blocks = this.blocksOf(content).filter((block) => block.type === "image" || block.text.length > 0);
|
|
409
470
|
if (blocks.length === 0) return;
|
|
410
471
|
this.counts.user++;
|
|
411
472
|
this.push({
|
|
@@ -423,7 +484,15 @@ var SessionBuilder = class {
|
|
|
423
484
|
}
|
|
424
485
|
assistant(input) {
|
|
425
486
|
this.settle();
|
|
426
|
-
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
|
+
}
|
|
427
496
|
if (blocks.length === 0 && !input.error) return;
|
|
428
497
|
const calls = blocks.filter((block) => block.type === "toolCall");
|
|
429
498
|
const message = {
|
|
@@ -440,6 +509,7 @@ var SessionBuilder = class {
|
|
|
440
509
|
this.counts.assistant++;
|
|
441
510
|
this.items.push({ kind: "message", message, at: input.at, key: input.key });
|
|
442
511
|
if (input.error) return;
|
|
512
|
+
this.counts.thinking += blocks.filter((block) => block.type === "thinking").length;
|
|
443
513
|
for (const call of calls) {
|
|
444
514
|
this.counts.toolCalls++;
|
|
445
515
|
this.calls.set(call.id, call.name);
|
|
@@ -448,18 +518,25 @@ var SessionBuilder = class {
|
|
|
448
518
|
}
|
|
449
519
|
toolResult(input) {
|
|
450
520
|
const name = input.name || this.calls.get(input.id) || "unknown";
|
|
451
|
-
const blocks = this.
|
|
521
|
+
const blocks = this.blocksOf(input.content);
|
|
452
522
|
if (!this.pending.has(input.id)) {
|
|
453
523
|
if (this.answered.has(input.id)) {
|
|
454
524
|
this.drop("repeated tool results");
|
|
455
525
|
return;
|
|
456
526
|
}
|
|
457
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
|
+
});
|
|
458
533
|
this.context(`[Result of an earlier ${name} call]
|
|
459
|
-
${
|
|
534
|
+
${text.join("\n")}`, input.at);
|
|
460
535
|
return;
|
|
461
536
|
}
|
|
462
|
-
const content = blocks.map(
|
|
537
|
+
const content = blocks.map(
|
|
538
|
+
(block) => block.type === "text" ? { type: "text", text: this.clipped(block.text) } : block
|
|
539
|
+
);
|
|
463
540
|
this.pending.delete(input.id);
|
|
464
541
|
this.answered.add(input.id);
|
|
465
542
|
this.counts.toolResults++;
|
|
@@ -492,25 +569,54 @@ ${blocks.map((block) => block.text).join("\n")}`, input.at);
|
|
|
492
569
|
this.counts.compactions++;
|
|
493
570
|
this.items.push({ kind: "compaction", summary, tokensBefore, at, keepFrom });
|
|
494
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
|
+
}
|
|
495
602
|
/** The session file's lines: header, marker, name, then the conversation as one chain. */
|
|
496
603
|
finish(input) {
|
|
497
604
|
this.settle();
|
|
605
|
+
this.budgetImages();
|
|
498
606
|
const taken = /* @__PURE__ */ new Set();
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
const id = randomUUID();
|
|
508
|
-
taken.add(id);
|
|
509
|
-
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);
|
|
510
614
|
};
|
|
511
615
|
const startedAt = Date.parse(input.started);
|
|
512
|
-
const
|
|
513
|
-
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
|
+
);
|
|
514
620
|
const byKey = /* @__PURE__ */ new Map();
|
|
515
621
|
this.items.forEach((item, index) => {
|
|
516
622
|
if (item.kind !== "compaction" && item.key && !byKey.has(item.key)) byKey.set(item.key, index);
|
|
@@ -519,7 +625,8 @@ ${blocks.map((block) => block.text).join("\n")}`, input.at);
|
|
|
519
625
|
this.items.forEach((item, index) => {
|
|
520
626
|
if (item.kind === "compaction") lastCompaction = index;
|
|
521
627
|
});
|
|
522
|
-
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);
|
|
523
630
|
const header = {
|
|
524
631
|
type: "session",
|
|
525
632
|
version: SESSION_VERSION,
|
|
@@ -529,17 +636,17 @@ ${blocks.map((block) => block.text).join("\n")}`, input.at);
|
|
|
529
636
|
};
|
|
530
637
|
const marker = {
|
|
531
638
|
type: "custom_message",
|
|
532
|
-
id: newId(),
|
|
639
|
+
id: newId("marker"),
|
|
533
640
|
parentId: null,
|
|
534
641
|
timestamp: input.started,
|
|
535
642
|
customType: IMPORT_MARKER,
|
|
536
643
|
content: input.note,
|
|
537
644
|
display: true,
|
|
538
|
-
details: { ...input.origin, counts: this.counts }
|
|
645
|
+
details: { ...input.origin, digest, counts: this.counts }
|
|
539
646
|
};
|
|
540
647
|
const info = {
|
|
541
648
|
type: "session_info",
|
|
542
|
-
id: newId(),
|
|
649
|
+
id: newId("name"),
|
|
543
650
|
parentId: marker.id,
|
|
544
651
|
timestamp: input.started,
|
|
545
652
|
name: input.name.replace(/[\r\n]+/g, " ").trim()
|
|
@@ -548,7 +655,7 @@ ${blocks.map((block) => block.text).join("\n")}`, input.at);
|
|
|
548
655
|
let parentId = info.id;
|
|
549
656
|
this.items.forEach((item, index) => {
|
|
550
657
|
const id = ids[index];
|
|
551
|
-
const timestamp =
|
|
658
|
+
const timestamp = iso2(item.at);
|
|
552
659
|
if (item.kind === "message") {
|
|
553
660
|
entries.push({ type: "message", id, parentId, timestamp, message: item.message });
|
|
554
661
|
} else if (item.kind === "context") {
|
|
@@ -564,7 +671,7 @@ ${blocks.map((block) => block.text).join("\n")}`, input.at);
|
|
|
564
671
|
entries.push(context);
|
|
565
672
|
} else {
|
|
566
673
|
const kept = item.keepFrom.map((key) => byKey.get(key)).find((at) => at !== void 0);
|
|
567
|
-
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");
|
|
568
675
|
const compaction = {
|
|
569
676
|
type: "compaction",
|
|
570
677
|
id,
|
|
@@ -578,25 +685,48 @@ ${blocks.map((block) => block.text).join("\n")}`, input.at);
|
|
|
578
685
|
}
|
|
579
686
|
parentId = id;
|
|
580
687
|
if (index === lastCompaction) {
|
|
581
|
-
const again = {
|
|
688
|
+
const again = {
|
|
689
|
+
...marker,
|
|
690
|
+
id: newId("marker again"),
|
|
691
|
+
parentId,
|
|
692
|
+
timestamp
|
|
693
|
+
};
|
|
582
694
|
entries.push(again);
|
|
583
695
|
parentId = again.id;
|
|
584
696
|
}
|
|
585
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
|
+
}
|
|
586
709
|
return [header, ...entries];
|
|
587
710
|
}
|
|
588
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
|
+
}
|
|
589
720
|
function importNote(tool, source) {
|
|
590
721
|
const name = TOOL_NAMES[tool];
|
|
591
722
|
return [
|
|
592
723
|
`This conversation was imported from ${name} (${source}) and continues here.`,
|
|
593
|
-
`Its tool calls use ${name}'s tool names, which may differ from the tools available now
|
|
594
|
-
"Thinking and images were not imported."
|
|
724
|
+
`Its tool calls use ${name}'s tool names, which may differ from the tools available now.`
|
|
595
725
|
].join(" ");
|
|
596
726
|
}
|
|
597
727
|
function complete(builder, input) {
|
|
598
728
|
const started = Number.isNaN(Date.parse(input.started)) ? (/* @__PURE__ */ new Date()).toISOString() : input.started;
|
|
599
|
-
const sessionId = uuidv7(Date.parse(started));
|
|
729
|
+
const sessionId = input.options.sessionId ?? uuidv7(Date.parse(started));
|
|
600
730
|
const name = input.name?.trim() || `${TOOL_NAMES[builder.tool]} conversation`;
|
|
601
731
|
const entries = builder.finish({
|
|
602
732
|
sessionId,
|
|
@@ -604,13 +734,15 @@ function complete(builder, input) {
|
|
|
604
734
|
started,
|
|
605
735
|
name,
|
|
606
736
|
note: importNote(builder.tool, input.source),
|
|
737
|
+
...input.options.model ? { model: input.options.model } : {},
|
|
607
738
|
origin: {
|
|
608
739
|
version: 1,
|
|
609
740
|
tool: builder.tool,
|
|
610
741
|
source: input.source,
|
|
611
742
|
sourceId: input.sourceId,
|
|
612
|
-
importedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
613
|
-
...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 } : {}
|
|
614
746
|
}
|
|
615
747
|
});
|
|
616
748
|
return {
|
|
@@ -636,24 +768,33 @@ function writeSessionFile(file, entries) {
|
|
|
636
768
|
closeSync2(fd);
|
|
637
769
|
}
|
|
638
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
|
+
}
|
|
639
781
|
|
|
640
782
|
// packages/kyrn-judge/src/import/claude-code.ts
|
|
641
783
|
var TREE_TYPES = /* @__PURE__ */ new Set(["user", "assistant", "system", "attachment"]);
|
|
642
784
|
function slim(data) {
|
|
643
785
|
delete data.toolUseResult;
|
|
644
|
-
for (const block of list(record(data.message)?.content)) {
|
|
645
|
-
const outer = record(block);
|
|
646
|
-
if (outer?.type === "image") delete outer.source;
|
|
647
|
-
if (outer?.type === "tool_result") {
|
|
648
|
-
for (const inner of list(outer.content)) {
|
|
649
|
-
const image = record(inner);
|
|
650
|
-
if (image?.type === "image") delete image.source;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
786
|
const file = record(record(record(data.attachment)?.content)?.file);
|
|
655
787
|
if (file) delete file.base64;
|
|
656
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
|
+
};
|
|
657
798
|
function blocksOf(message) {
|
|
658
799
|
const content = message?.content;
|
|
659
800
|
if (typeof content === "string") return [{ type: "text", text: content }];
|
|
@@ -668,7 +809,7 @@ function resultContent(content, counts) {
|
|
|
668
809
|
for (const block of list(content).map(record)) {
|
|
669
810
|
if (!block) continue;
|
|
670
811
|
if (block.type === "text") blocks.push({ type: "text", text: str(block.text) ?? "" });
|
|
671
|
-
else if (block.type === "image") blocks.push(
|
|
812
|
+
else if (block.type === "image") blocks.push(imageBlock(block));
|
|
672
813
|
else if (block.type === "tool_reference")
|
|
673
814
|
blocks.push({ type: "text", text: `[tool reference: ${str(block.tool_name) ?? "unknown"}]` });
|
|
674
815
|
else {
|
|
@@ -715,6 +856,8 @@ async function readClaudeCode(path, options = {}) {
|
|
|
715
856
|
const records = [];
|
|
716
857
|
const byUuid = /* @__PURE__ */ new Map();
|
|
717
858
|
let sessionId;
|
|
859
|
+
const titles = /* @__PURE__ */ new Map();
|
|
860
|
+
const stamp = fileStamp(source);
|
|
718
861
|
for await (const line of readJsonLines(source)) {
|
|
719
862
|
counts.records++;
|
|
720
863
|
if (!("value" in line)) {
|
|
@@ -724,13 +867,15 @@ async function readClaudeCode(path, options = {}) {
|
|
|
724
867
|
const data = record(line.value);
|
|
725
868
|
const type = str(data?.type);
|
|
726
869
|
if (!data || !type) {
|
|
727
|
-
builder.drop("records
|
|
870
|
+
builder.drop("unrecognized records");
|
|
728
871
|
continue;
|
|
729
872
|
}
|
|
730
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);
|
|
731
876
|
const uuid = str(data.uuid);
|
|
732
877
|
if (!TREE_TYPES.has(type) || !uuid) {
|
|
733
|
-
builder.drop(uuid ? "records
|
|
878
|
+
builder.drop(uuid ? "unrecognized records" : "Claude Code bookkeeping (titles, queue, file history)");
|
|
734
879
|
continue;
|
|
735
880
|
}
|
|
736
881
|
if (byUuid.has(uuid)) {
|
|
@@ -769,6 +914,10 @@ async function readClaudeCode(path, options = {}) {
|
|
|
769
914
|
chosen.add(current2.uuid);
|
|
770
915
|
const next = current2.parent ?? current2.logicalParent;
|
|
771
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
|
+
}
|
|
772
921
|
if (next && !found) {
|
|
773
922
|
if (!current2.parent) found = messageBefore(current2);
|
|
774
923
|
if (!found) builder.drop("links to records that are not in the file");
|
|
@@ -807,7 +956,7 @@ async function readClaudeCode(path, options = {}) {
|
|
|
807
956
|
if (inScope(item)) offBranch++;
|
|
808
957
|
else subAgent++;
|
|
809
958
|
}
|
|
810
|
-
builder.drop("
|
|
959
|
+
builder.drop("subagent records", subAgent);
|
|
811
960
|
builder.drop("records off the latest branch", offBranch);
|
|
812
961
|
const ordered = records.filter((item) => chosen.has(item.uuid));
|
|
813
962
|
const orderedIds = ordered.map((item) => item.uuid);
|
|
@@ -870,7 +1019,8 @@ async function readClaudeCode(path, options = {}) {
|
|
|
870
1019
|
if (partMessage.stop_reason === "max_tokens") stop = "length";
|
|
871
1020
|
for (const block of blocksOf(partMessage)) {
|
|
872
1021
|
if (block.type === "text") blocks.push({ type: "text", text: str(block.text) ?? "" });
|
|
873
|
-
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");
|
|
874
1024
|
else if (block.type === "tool_use") {
|
|
875
1025
|
const callId = str(block.id) ?? part.uuid;
|
|
876
1026
|
if (callIds.has(callId)) {
|
|
@@ -923,7 +1073,7 @@ async function readClaudeCode(path, options = {}) {
|
|
|
923
1073
|
}
|
|
924
1074
|
if (block.type === "image") {
|
|
925
1075
|
if (meta) builder.drop("images");
|
|
926
|
-
else words.push(
|
|
1076
|
+
else words.push(imageBlock(block));
|
|
927
1077
|
continue;
|
|
928
1078
|
}
|
|
929
1079
|
if (block.type !== "text") {
|
|
@@ -985,7 +1135,7 @@ async function readClaudeCode(path, options = {}) {
|
|
|
985
1135
|
if (attachment.type === "queued_command") {
|
|
986
1136
|
const prompt = attachment.prompt;
|
|
987
1137
|
const words = typeof prompt === "string" ? [{ type: "text", text: prompt }] : list(prompt).map(record).filter((block) => block !== void 0).map(
|
|
988
|
-
(block) => block.type === "text" ? { type: "text", text: str(block.text) ?? "" } :
|
|
1138
|
+
(block) => block.type === "text" ? { type: "text", text: str(block.text) ?? "" } : imageBlock(block)
|
|
989
1139
|
);
|
|
990
1140
|
const typed = attachment.commandMode === "prompt" && attachment.isMeta !== true;
|
|
991
1141
|
if (typed) {
|
|
@@ -1032,9 +1182,10 @@ async function readClaudeCode(path, options = {}) {
|
|
|
1032
1182
|
sourceId: agentId ? `agent-${agentId}` : sessionId ?? basename(source, ".jsonl"),
|
|
1033
1183
|
cwd: cwd ?? options.cwd ?? process.cwd(),
|
|
1034
1184
|
started: startedIso,
|
|
1035
|
-
name: name ?? firstCommand,
|
|
1185
|
+
name: titles.get("custom-title") ?? titles.get("ai-title") ?? titles.get("summary") ?? name ?? firstCommand,
|
|
1036
1186
|
model,
|
|
1037
|
-
|
|
1187
|
+
stamp,
|
|
1188
|
+
options
|
|
1038
1189
|
});
|
|
1039
1190
|
}
|
|
1040
1191
|
|
|
@@ -1075,7 +1226,7 @@ function outputOf(output, counts) {
|
|
|
1075
1226
|
if (!block) continue;
|
|
1076
1227
|
if (block.type === "input_text" || block.type === "output_text" || block.type === "text")
|
|
1077
1228
|
blocks.push({ type: "text", text: str(block.text) ?? "" });
|
|
1078
|
-
else if (block.type === "input_image") blocks.push(
|
|
1229
|
+
else if (block.type === "input_image") blocks.push(imageFromUrl(block.image_url));
|
|
1079
1230
|
else {
|
|
1080
1231
|
counts.asText++;
|
|
1081
1232
|
blocks.push({ type: "text", text: unknownBlockText(block) });
|
|
@@ -1095,6 +1246,7 @@ ${messages}`);
|
|
|
1095
1246
|
}
|
|
1096
1247
|
async function readCodex(path, options = {}) {
|
|
1097
1248
|
const source = absolute(path);
|
|
1249
|
+
const stamp = fileStamp(source);
|
|
1098
1250
|
const builder = new SessionBuilder("codex");
|
|
1099
1251
|
const { counts } = builder;
|
|
1100
1252
|
let sourceId = "";
|
|
@@ -1127,7 +1279,7 @@ async function readCodex(path, options = {}) {
|
|
|
1127
1279
|
};
|
|
1128
1280
|
for (const block of blocks) {
|
|
1129
1281
|
if (block.type === "input_image") {
|
|
1130
|
-
words.push(
|
|
1282
|
+
words.push(imageFromUrl(block.image_url));
|
|
1131
1283
|
continue;
|
|
1132
1284
|
}
|
|
1133
1285
|
if (block.type !== "input_text" && block.type !== "text") {
|
|
@@ -1186,9 +1338,14 @@ async function readCodex(path, options = {}) {
|
|
|
1186
1338
|
else builder.drop("Codex instructions");
|
|
1187
1339
|
return;
|
|
1188
1340
|
}
|
|
1189
|
-
case "reasoning":
|
|
1190
|
-
|
|
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") });
|
|
1191
1347
|
return;
|
|
1348
|
+
}
|
|
1192
1349
|
case "function_call":
|
|
1193
1350
|
case "tool_search_call":
|
|
1194
1351
|
answer().blocks.push({
|
|
@@ -1249,7 +1406,7 @@ ${text}`, at);
|
|
|
1249
1406
|
return;
|
|
1250
1407
|
}
|
|
1251
1408
|
default:
|
|
1252
|
-
builder.drop("records
|
|
1409
|
+
builder.drop("unrecognized records");
|
|
1253
1410
|
}
|
|
1254
1411
|
};
|
|
1255
1412
|
const compacted = (payload) => {
|
|
@@ -1275,7 +1432,7 @@ ${text}`, at);
|
|
|
1275
1432
|
}
|
|
1276
1433
|
const data = record(line.value);
|
|
1277
1434
|
if (!data) {
|
|
1278
|
-
builder.drop("records
|
|
1435
|
+
builder.drop("unrecognized records");
|
|
1279
1436
|
continue;
|
|
1280
1437
|
}
|
|
1281
1438
|
const payload = record(data.payload);
|
|
@@ -1301,7 +1458,7 @@ ${text}`, at);
|
|
|
1301
1458
|
if (typeof input === "number") tokens = input;
|
|
1302
1459
|
}
|
|
1303
1460
|
builder.drop("Codex events and state");
|
|
1304
|
-
} else builder.drop("records
|
|
1461
|
+
} else builder.drop("unrecognized records");
|
|
1305
1462
|
continue;
|
|
1306
1463
|
}
|
|
1307
1464
|
if (data.record_type === "state") {
|
|
@@ -1313,15 +1470,15 @@ ${text}`, at);
|
|
|
1313
1470
|
continue;
|
|
1314
1471
|
}
|
|
1315
1472
|
const id = str(data.id);
|
|
1316
|
-
const
|
|
1317
|
-
if (id &&
|
|
1473
|
+
const stamp2 = str(data.timestamp);
|
|
1474
|
+
if (id && stamp2) {
|
|
1318
1475
|
if (!sourceId) sourceId = id;
|
|
1319
|
-
started ??=
|
|
1320
|
-
const time = Date.parse(
|
|
1476
|
+
started ??= stamp2;
|
|
1477
|
+
const time = Date.parse(stamp2);
|
|
1321
1478
|
if (!Number.isNaN(time)) at = time;
|
|
1322
1479
|
continue;
|
|
1323
1480
|
}
|
|
1324
|
-
builder.drop("records
|
|
1481
|
+
builder.drop("unrecognized records");
|
|
1325
1482
|
}
|
|
1326
1483
|
flush();
|
|
1327
1484
|
let startedIso = started && !Number.isNaN(Date.parse(started)) ? new Date(Date.parse(started)).toISOString() : void 0;
|
|
@@ -1340,7 +1497,365 @@ ${text}`, at);
|
|
|
1340
1497
|
started: startedIso,
|
|
1341
1498
|
name: name ?? firstCommand,
|
|
1342
1499
|
model: lastModel ?? model,
|
|
1343
|
-
|
|
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
|
|
1344
1859
|
});
|
|
1345
1860
|
}
|
|
1346
1861
|
|
|
@@ -1355,6 +1870,7 @@ var CODEX_ITEMS = /* @__PURE__ */ new Set([
|
|
|
1355
1870
|
"reasoning"
|
|
1356
1871
|
]);
|
|
1357
1872
|
function detectTool(path) {
|
|
1873
|
+
if (parseZcodeSource(path)) return "zcode";
|
|
1358
1874
|
let tool;
|
|
1359
1875
|
let lines = 0;
|
|
1360
1876
|
readHead(
|
|
@@ -1377,6 +1893,27 @@ function detectTool(path) {
|
|
|
1377
1893
|
);
|
|
1378
1894
|
return tool;
|
|
1379
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
|
+
}
|
|
1380
1917
|
var TREE_TYPES2 = /* @__PURE__ */ new Set(["user", "assistant", "system", "attachment"]);
|
|
1381
1918
|
function titleParts(text) {
|
|
1382
1919
|
if (classifyUserText(text) !== "user") return {};
|
|
@@ -1393,16 +1930,14 @@ function describeClaudeCode(path) {
|
|
|
1393
1930
|
let started;
|
|
1394
1931
|
let title;
|
|
1395
1932
|
let command;
|
|
1396
|
-
let named;
|
|
1397
1933
|
let first;
|
|
1934
|
+
const titles = [];
|
|
1398
1935
|
readHead(path, (value) => {
|
|
1399
1936
|
const data = record(value);
|
|
1400
1937
|
if (!data) return false;
|
|
1401
1938
|
id ??= str(data.sessionId);
|
|
1402
1939
|
const type = str(data.type);
|
|
1403
|
-
if (
|
|
1404
|
-
else if (type === "ai-title") named ??= str(data.aiTitle);
|
|
1405
|
-
else if (type === "summary") named ??= str(data.summary);
|
|
1940
|
+
if (CLAUDE_TITLES.some(([name]) => name === type)) titles.push(data);
|
|
1406
1941
|
if (!type || !TREE_TYPES2.has(type)) return false;
|
|
1407
1942
|
first ??= data.isSidechain === true;
|
|
1408
1943
|
if (first) agentId ??= str(data.agentId);
|
|
@@ -1424,7 +1959,7 @@ function describeClaudeCode(path) {
|
|
|
1424
1959
|
return {
|
|
1425
1960
|
id: first && agentId ? `agent-${agentId}` : id ?? "",
|
|
1426
1961
|
cwd,
|
|
1427
|
-
title:
|
|
1962
|
+
title: claudeTitle(path, titles) ?? title ?? command ?? "",
|
|
1428
1963
|
started,
|
|
1429
1964
|
subAgent: first === true
|
|
1430
1965
|
};
|
|
@@ -1472,11 +2007,11 @@ function describeCodex(path) {
|
|
|
1472
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];
|
|
1473
2008
|
return { id: id ?? fromName ?? "", cwd, title: title ?? command ?? "", started, subAgent };
|
|
1474
2009
|
}
|
|
1475
|
-
function claudeProjectsDir(env = process.env, home =
|
|
1476
|
-
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");
|
|
1477
2012
|
}
|
|
1478
|
-
function codexSessionsDir(env = process.env, home =
|
|
1479
|
-
return
|
|
2013
|
+
function codexSessionsDir(env = process.env, home = homedir3()) {
|
|
2014
|
+
return join3(env.CODEX_HOME ? absolute(env.CODEX_HOME) : join3(home, ".codex"), "sessions");
|
|
1480
2015
|
}
|
|
1481
2016
|
function sameFolder(a, b) {
|
|
1482
2017
|
const canonical = (dir) => {
|
|
@@ -1498,17 +2033,43 @@ function jsonlFiles(dir, depth) {
|
|
|
1498
2033
|
}
|
|
1499
2034
|
const files = [];
|
|
1500
2035
|
for (const name of names.sort()) {
|
|
1501
|
-
const path =
|
|
2036
|
+
const path = join3(dir, name);
|
|
1502
2037
|
if (name.endsWith(".jsonl")) files.push(path);
|
|
1503
2038
|
else if (depth > 0 && !name.startsWith(".")) {
|
|
1504
2039
|
try {
|
|
1505
|
-
if (
|
|
2040
|
+
if (statSync5(path).isDirectory()) files.push(...jsonlFiles(path, depth - 1));
|
|
1506
2041
|
} catch {
|
|
1507
2042
|
}
|
|
1508
2043
|
}
|
|
1509
2044
|
}
|
|
1510
2045
|
return files;
|
|
1511
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
|
+
}
|
|
1512
2073
|
function listConversations(options = {}) {
|
|
1513
2074
|
const claudeProjects = options.claudeProjects ?? claudeProjectsDir();
|
|
1514
2075
|
const codexSessions = options.codexSessions ?? codexSessionsDir();
|
|
@@ -1519,22 +2080,22 @@ function listConversations(options = {}) {
|
|
|
1519
2080
|
} catch {
|
|
1520
2081
|
}
|
|
1521
2082
|
for (const project of projects) {
|
|
1522
|
-
for (const path of jsonlFiles(
|
|
2083
|
+
for (const path of jsonlFiles(join3(claudeProjects, project), 0)) candidates.push({ tool: "claude-code", path });
|
|
1523
2084
|
}
|
|
1524
2085
|
for (const path of jsonlFiles(codexSessions, 4)) candidates.push({ tool: "codex", path });
|
|
1525
|
-
const
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
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
|
+
} : {};
|
|
1532
2093
|
const conversations = [];
|
|
1533
2094
|
for (const { tool, path } of candidates) {
|
|
1534
2095
|
let size;
|
|
1535
2096
|
let modified;
|
|
1536
2097
|
try {
|
|
1537
|
-
const stats =
|
|
2098
|
+
const stats = statSync5(path);
|
|
1538
2099
|
size = stats.size;
|
|
1539
2100
|
modified = stats.mtime.toISOString();
|
|
1540
2101
|
} catch {
|
|
@@ -1548,16 +2109,75 @@ function listConversations(options = {}) {
|
|
|
1548
2109
|
}
|
|
1549
2110
|
if (described.subAgent && !options.subAgents) continue;
|
|
1550
2111
|
if (options.cwd && !(described.cwd && sameFolder(described.cwd, options.cwd))) continue;
|
|
1551
|
-
const
|
|
1552
|
-
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) });
|
|
1553
2120
|
}
|
|
1554
2121
|
return conversations.sort((a, b) => b.modified.localeCompare(a.modified));
|
|
1555
2122
|
}
|
|
1556
|
-
|
|
1557
|
-
|
|
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);
|
|
1558
2178
|
const failed = (error) => ({ status: "failed", source, error });
|
|
1559
2179
|
try {
|
|
1560
|
-
if (!
|
|
2180
|
+
if (!statSync5(zcode ? zcode.database : source).isFile()) return failed("not a file");
|
|
1561
2181
|
} catch {
|
|
1562
2182
|
return failed("no such file");
|
|
1563
2183
|
}
|
|
@@ -1570,39 +2190,61 @@ async function importOne(path, options, imports) {
|
|
|
1570
2190
|
});
|
|
1571
2191
|
try {
|
|
1572
2192
|
const tool = detectTool(source);
|
|
1573
|
-
if (!tool) return failed(
|
|
1574
|
-
const quick = (tool === "codex" ? describeCodex(source) : describeClaudeCode(source)).id;
|
|
1575
|
-
|
|
1576
|
-
if (
|
|
1577
|
-
|
|
1578
|
-
const
|
|
1579
|
-
|
|
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
|
+
}
|
|
1580
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());
|
|
1581
2233
|
const dir = sessionDirFor(converted.cwd, options);
|
|
1582
2234
|
mkdirSync(dir, { recursive: true });
|
|
1583
|
-
const sessionFile =
|
|
2235
|
+
const sessionFile = join3(dir, sessionFileName(converted.started, converted.sessionId));
|
|
1584
2236
|
writeSessionFile(sessionFile, converted.entries);
|
|
1585
2237
|
imports.push({
|
|
1586
2238
|
sessionFile,
|
|
1587
2239
|
sessionId: converted.sessionId,
|
|
1588
|
-
origin:
|
|
1589
|
-
version: 1,
|
|
1590
|
-
tool,
|
|
1591
|
-
source,
|
|
1592
|
-
sourceId: converted.sourceId,
|
|
1593
|
-
importedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1594
|
-
counts: converted.counts
|
|
1595
|
-
}
|
|
2240
|
+
origin: record(converted.entries[1])?.details
|
|
1596
2241
|
});
|
|
1597
2242
|
return {
|
|
1598
2243
|
status: "imported",
|
|
1599
|
-
|
|
1600
|
-
tool,
|
|
1601
|
-
sessionFile,
|
|
2244
|
+
...written,
|
|
1602
2245
|
sessionId: converted.sessionId,
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
counts: converted.counts
|
|
2246
|
+
sessionFile,
|
|
2247
|
+
...earlier ? { earlier: earlier.sessionFile } : {}
|
|
1606
2248
|
};
|
|
1607
2249
|
} catch (error) {
|
|
1608
2250
|
return failed(error instanceof Error ? error.message : String(error));
|
|
@@ -1610,23 +2252,27 @@ async function importOne(path, options, imports) {
|
|
|
1610
2252
|
}
|
|
1611
2253
|
async function importTranscripts(paths, options) {
|
|
1612
2254
|
const imports = findImports(options);
|
|
2255
|
+
const checks = readChecks(options);
|
|
2256
|
+
const before = JSON.stringify(checks);
|
|
1613
2257
|
const results = [];
|
|
1614
|
-
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);
|
|
1615
2260
|
return results;
|
|
1616
2261
|
}
|
|
1617
2262
|
|
|
1618
2263
|
// packages/kyrn-judge/src/import/command.ts
|
|
1619
2264
|
var USAGE = [
|
|
1620
|
-
"mu import: import Claude Code and
|
|
2265
|
+
"mu import: import Claude Code, Codex and ZCode conversations so you can continue them in mu.",
|
|
1621
2266
|
"",
|
|
1622
2267
|
" mu import --list [--json] [--cwd <dir>] list conversations on this machine (with --cwd, only <dir>'s)",
|
|
1623
|
-
" mu import <file>... [--json] import these
|
|
2268
|
+
" mu import <file>... [--json] import these conversations; each becomes a mu session",
|
|
1624
2269
|
"",
|
|
1625
|
-
" --agent-dir <dir> mu's agent folder (default:
|
|
2270
|
+
" --agent-dir <dir> mu's agent folder (default: MU_AGENT_DIR, else ~/.mu/agent)",
|
|
1626
2271
|
" --sub-agents also list subagent threads",
|
|
1627
2272
|
"",
|
|
1628
|
-
"Looks in ~/.claude/projects (CLAUDE_CONFIG_DIR)
|
|
1629
|
-
"
|
|
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."
|
|
1630
2276
|
].join("\n");
|
|
1631
2277
|
function parseArgs(argv) {
|
|
1632
2278
|
const parsed = { help: false, list: false, json: false, subAgents: false, files: [] };
|
|
@@ -1657,19 +2303,28 @@ function parseArgs(argv) {
|
|
|
1657
2303
|
}
|
|
1658
2304
|
function storeFor(agentDirArg, io) {
|
|
1659
2305
|
const agentDir = absolute(
|
|
1660
|
-
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"),
|
|
1661
2307
|
io.cwd
|
|
1662
2308
|
);
|
|
1663
2309
|
const fromEnv = io.env.MU_CODING_AGENT_SESSION_DIR;
|
|
1664
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) {
|
|
1665
2317
|
try {
|
|
1666
|
-
|
|
1667
|
-
const sessionDir = settings !== null && typeof settings === "object" && "sessionDir" in settings ? settings.sessionDir : void 0;
|
|
1668
|
-
if (typeof sessionDir === "string" && sessionDir.trim())
|
|
1669
|
-
return { agentDir, sessionDir: absolute(sessionDir, io.cwd) };
|
|
2318
|
+
return record(JSON.parse(readFileSync2(join4(agentDir, "settings.json"), "utf8"))) ?? {};
|
|
1670
2319
|
} catch {
|
|
2320
|
+
return {};
|
|
1671
2321
|
}
|
|
1672
|
-
|
|
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;
|
|
1673
2328
|
}
|
|
1674
2329
|
function formatSize(bytes) {
|
|
1675
2330
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -1682,8 +2337,8 @@ function formatSize(bytes) {
|
|
|
1682
2337
|
}
|
|
1683
2338
|
return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;
|
|
1684
2339
|
}
|
|
1685
|
-
function formatDate(
|
|
1686
|
-
const date =
|
|
2340
|
+
function formatDate(iso2) {
|
|
2341
|
+
const date = iso2 ? new Date(iso2) : void 0;
|
|
1687
2342
|
if (!date || Number.isNaN(date.getTime())) return "????-??-?? ??:??";
|
|
1688
2343
|
const two = (value) => String(value).padStart(2, "0");
|
|
1689
2344
|
return `${date.getFullYear()}-${two(date.getMonth() + 1)}-${two(date.getDate())} ${two(date.getHours())}:${two(date.getMinutes())}`;
|
|
@@ -1692,7 +2347,7 @@ function tilde(path, home) {
|
|
|
1692
2347
|
return path === home ? "~" : path.startsWith(home + sep) ? `~${path.slice(home.length)}` : path;
|
|
1693
2348
|
}
|
|
1694
2349
|
function formatList(conversations, home) {
|
|
1695
|
-
if (conversations.length === 0) return "No Claude Code or
|
|
2350
|
+
if (conversations.length === 0) return "No Claude Code, Codex or ZCode conversations found.";
|
|
1696
2351
|
const groups = /* @__PURE__ */ new Map();
|
|
1697
2352
|
for (const conversation of conversations) {
|
|
1698
2353
|
const key = conversation.cwd ?? "";
|
|
@@ -1707,7 +2362,7 @@ function formatList(conversations, home) {
|
|
|
1707
2362
|
lines.push(cwd ? tilde(cwd, home) : "(unknown folder)");
|
|
1708
2363
|
for (const conversation of group) {
|
|
1709
2364
|
const title = conversation.title || "(no message found)";
|
|
1710
|
-
const mark = conversation.importedAs ? " (imported)" : "";
|
|
2365
|
+
const mark = !conversation.importedAs ? "" : conversation.changed ? " (imported, changed since)" : " (imported)";
|
|
1711
2366
|
lines.push(
|
|
1712
2367
|
` ${formatDate(conversation.modified)} ${TOOL_NAMES[conversation.tool].padEnd(toolWidth)} ${formatSize(conversation.size).padStart(7)} ${title}${mark}`
|
|
1713
2368
|
);
|
|
@@ -1722,16 +2377,33 @@ function formatResult(result, home) {
|
|
|
1722
2377
|
if (result.status === "failed") return `Couldn't import ${tilde(result.source, home)}: ${result.error}`;
|
|
1723
2378
|
if (result.status === "already-imported")
|
|
1724
2379
|
return [
|
|
1725
|
-
`Already imported: ${tilde(result.source, home)}`,
|
|
2380
|
+
`Already imported and unchanged: ${tilde(result.source, home)}`,
|
|
1726
2381
|
` as session ${tilde(result.sessionFile, home)}`
|
|
1727
2382
|
].join("\n");
|
|
1728
2383
|
const { counts } = result;
|
|
1729
|
-
const
|
|
1730
|
-
|
|
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
|
+
] : [
|
|
1731
2397
|
`Imported from ${TOOL_NAMES[result.tool]}: "${result.name}"`,
|
|
1732
2398
|
` ${tilde(result.sessionFile, home)}`,
|
|
1733
|
-
|
|
1734
|
-
|
|
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] : [],
|
|
1735
2407
|
` to continue: mu --session ${result.sessionFile}`
|
|
1736
2408
|
].join("\n");
|
|
1737
2409
|
}
|
|
@@ -1759,7 +2431,7 @@ ${USAGE}`);
|
|
|
1759
2431
|
}
|
|
1760
2432
|
const results = await importTranscripts(
|
|
1761
2433
|
parsed.files.map((file) => absolute(file, io.cwd)),
|
|
1762
|
-
{ ...store, cwd: io.cwd }
|
|
2434
|
+
{ ...store, cwd: io.cwd, model: defaultModelOf(store.agentDir) }
|
|
1763
2435
|
);
|
|
1764
2436
|
io.out(
|
|
1765
2437
|
parsed.json ? JSON.stringify({ results }, null, 2) : results.map((result) => formatResult(result, io.home)).join("\n\n")
|
|
@@ -1774,7 +2446,7 @@ function processIo() {
|
|
|
1774
2446
|
`),
|
|
1775
2447
|
env: process.env,
|
|
1776
2448
|
cwd: process.cwd(),
|
|
1777
|
-
home:
|
|
2449
|
+
home: homedir4()
|
|
1778
2450
|
};
|
|
1779
2451
|
}
|
|
1780
2452
|
|