archymedes-cli 2.3.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{archymedes-aj30app4.mjs → archymedes-06fh0z84.mjs} +370 -159
- package/dist/{archymedes-sf9t1hz9.mjs → archymedes-086xsz64.mjs} +176 -77
- package/dist/{archymedes-d5wqcmdr.mjs → archymedes-0jxzsh4z.mjs} +97 -97
- package/dist/{archymedes-h13ap0za.mjs → archymedes-1g1y6q74.mjs} +1 -1
- package/dist/{archymedes-4kx65anz.mjs → archymedes-1jcn326m.mjs} +43 -12
- package/dist/{archymedes-g8y3rt6t.mjs → archymedes-kn6wtq32.mjs} +332 -9
- package/dist/archymedes-main.mjs +31010 -27842
- package/dist/archymedes-s4qcy0q4.mjs +1250 -0
- package/dist/{archymedes-twbks7g3.mjs → archymedes-t1jtzkmp.mjs} +214 -5
- package/dist/{archymedes-tsne2m7p.mjs → archymedes-twanftqk.mjs} +2 -2
- package/dist/archymedes-w4bjfye7.mjs +280 -0
- package/dist/{archymedes-dz59nvw7.mjs → archymedes-zvcs68bv.mjs} +21 -11
- package/dist/{editor-screen-g08zzt70.mjs → editor-screen-pf252j2r.mjs} +49 -14
- package/dist/{factory-7vtm2fed.mjs → factory-hyv8agr0.mjs} +326 -326
- package/dist/{file-screen-ma9yxq2b.mjs → file-screen-1j65f7b4.mjs} +11 -9
- package/dist/{guide-screen-gd129txd.mjs → guide-screen-9vxsk83x.mjs} +10 -8
- package/dist/{index-qvgcwsvm.mjs → index-teq1cta4.mjs} +1 -1
- package/dist/{node-mdgzx68h.mjs → node-0fh63p8z.mjs} +7 -7
- package/dist/pager-screen-aamt6xv0.mjs +155 -0
- package/dist/projection-x6x6hm9t.mjs +248 -0
- package/dist/{sandbox-templates-9jcjws23.mjs → sandbox-templates-8sd1psez.mjs} +2 -2
- package/dist/workspace-screen-ntdcgfq9.mjs +70 -0
- package/package.json +2 -2
- package/dist/archymedes-a10k8yrk.mjs +0 -464
- package/dist/workspace-screen-8cwk6jz9.mjs +0 -241
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
padToWidth,
|
|
3
3
|
progressBar,
|
|
4
|
+
scrollIndicator,
|
|
5
|
+
scrollPercent,
|
|
4
6
|
sliceToWidth
|
|
5
|
-
} from "./archymedes-
|
|
7
|
+
} from "./archymedes-kn6wtq32.mjs";
|
|
8
|
+
import {
|
|
9
|
+
clip
|
|
10
|
+
} from "./archymedes-twanftqk.mjs";
|
|
6
11
|
import {
|
|
7
12
|
ASCII_GLYPHS,
|
|
8
13
|
UNICODE_GLYPHS,
|
|
9
14
|
visibleWidth
|
|
10
|
-
} from "./archymedes-
|
|
15
|
+
} from "./archymedes-s4qcy0q4.mjs";
|
|
11
16
|
|
|
12
|
-
// src/render/canvas.ts
|
|
17
|
+
// packages/archymedes-cli/src/render/canvas.ts
|
|
13
18
|
var BRAILLE_DOTS = [
|
|
14
19
|
[1, 2, 4, 64],
|
|
15
20
|
[8, 16, 32, 128]
|
|
@@ -95,7 +100,7 @@ function seriesBounds(values) {
|
|
|
95
100
|
return { low, high, span: high - low === 0 ? 1 : high - low };
|
|
96
101
|
}
|
|
97
102
|
|
|
98
|
-
// src/render/charts.ts
|
|
103
|
+
// packages/archymedes-cli/src/render/charts.ts
|
|
99
104
|
function barChart(data, options) {
|
|
100
105
|
if (data.length === 0)
|
|
101
106
|
return [];
|
|
@@ -253,4 +258,208 @@ function heatStrip(data, options) {
|
|
|
253
258
|
});
|
|
254
259
|
}
|
|
255
260
|
|
|
256
|
-
|
|
261
|
+
// packages/archymedes-cli/src/ui/workspace-model.ts
|
|
262
|
+
function paneHeight(rows) {
|
|
263
|
+
return Math.max(1, rows - 4);
|
|
264
|
+
}
|
|
265
|
+
function selectedPane(snapshot) {
|
|
266
|
+
if (snapshot.panes.length === 0)
|
|
267
|
+
return;
|
|
268
|
+
const index = Math.max(0, Math.min(snapshot.selected, snapshot.panes.length - 1));
|
|
269
|
+
return snapshot.panes[index];
|
|
270
|
+
}
|
|
271
|
+
function visibleLines(pane, height, scroll) {
|
|
272
|
+
const clamped = Math.max(0, Math.min(scroll, Math.max(0, pane.lines.length - height)));
|
|
273
|
+
const end = pane.lines.length - clamped;
|
|
274
|
+
return pane.lines.slice(Math.max(0, end - height), end);
|
|
275
|
+
}
|
|
276
|
+
function atLiveEdge(pane, height, scroll) {
|
|
277
|
+
return Math.min(scroll, Math.max(0, pane.lines.length - height)) === 0;
|
|
278
|
+
}
|
|
279
|
+
function keyToAction(key, character) {
|
|
280
|
+
const name = key.name ?? "";
|
|
281
|
+
const isEscape = name === "escape" || name === "\x1B";
|
|
282
|
+
const isEnter = name === "return" || name === "enter" || name === "\r" || name === `
|
|
283
|
+
`;
|
|
284
|
+
if (isEscape || name === "q" && !key.ctrl || key.ctrl && name === "c")
|
|
285
|
+
return { kind: "exit" };
|
|
286
|
+
if (isEnter)
|
|
287
|
+
return { kind: "focus" };
|
|
288
|
+
if (name === "tab")
|
|
289
|
+
return { kind: "cycle", step: key.shift ? -1 : 1 };
|
|
290
|
+
if (name === "left")
|
|
291
|
+
return { kind: "cycle", step: -1 };
|
|
292
|
+
if (name === "right")
|
|
293
|
+
return { kind: "cycle", step: 1 };
|
|
294
|
+
if (name === "up" || name === "k")
|
|
295
|
+
return { kind: "scroll", lines: 1 };
|
|
296
|
+
if (name === "down" || name === "j")
|
|
297
|
+
return { kind: "scroll", lines: -1 };
|
|
298
|
+
if (name === "pageup")
|
|
299
|
+
return { kind: "scroll", lines: 10 };
|
|
300
|
+
if (name === "pagedown")
|
|
301
|
+
return { kind: "scroll", lines: -10 };
|
|
302
|
+
if (name === "home" || character === "g")
|
|
303
|
+
return { kind: "top" };
|
|
304
|
+
if (name === "end" || character === "G")
|
|
305
|
+
return { kind: "bottom" };
|
|
306
|
+
const digit = character && /^[1-9]$/.test(character) ? Number(character) : undefined;
|
|
307
|
+
if (digit !== undefined)
|
|
308
|
+
return { kind: "select", index: digit - 1 };
|
|
309
|
+
return { kind: "none" };
|
|
310
|
+
}
|
|
311
|
+
function applyAction(snapshot, action) {
|
|
312
|
+
const count = snapshot.panes.length;
|
|
313
|
+
if (count === 0 || action.kind === "none" || action.kind === "exit" || action.kind === "focus")
|
|
314
|
+
return snapshot;
|
|
315
|
+
if (action.kind === "select") {
|
|
316
|
+
if (action.index < 0 || action.index >= count)
|
|
317
|
+
return snapshot;
|
|
318
|
+
return { ...snapshot, selected: action.index, scroll: 0 };
|
|
319
|
+
}
|
|
320
|
+
if (action.kind === "cycle") {
|
|
321
|
+
const next = ((snapshot.selected + action.step) % count + count) % count;
|
|
322
|
+
return { ...snapshot, selected: next, scroll: 0 };
|
|
323
|
+
}
|
|
324
|
+
const pane = selectedPane(snapshot);
|
|
325
|
+
const height = paneHeight(snapshot.rows);
|
|
326
|
+
const furthest = Math.max(0, (pane?.lines.length ?? 0) - height);
|
|
327
|
+
if (action.kind === "bottom")
|
|
328
|
+
return { ...snapshot, scroll: 0 };
|
|
329
|
+
if (action.kind === "top")
|
|
330
|
+
return { ...snapshot, scroll: furthest };
|
|
331
|
+
return { ...snapshot, scroll: Math.max(0, Math.min(snapshot.scroll + action.lines, furthest)) };
|
|
332
|
+
}
|
|
333
|
+
function paneTabs(snapshot) {
|
|
334
|
+
const selected = Math.max(0, Math.min(snapshot.selected, snapshot.panes.length - 1));
|
|
335
|
+
return snapshot.panes.map((pane, index) => ({
|
|
336
|
+
label: `${index + 1} ${pane.title}`,
|
|
337
|
+
active: index === selected,
|
|
338
|
+
status: pane.status
|
|
339
|
+
}));
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
class PaneActivity {
|
|
343
|
+
capacity;
|
|
344
|
+
series = new Map;
|
|
345
|
+
seen = new Map;
|
|
346
|
+
constructor(capacity = 48) {
|
|
347
|
+
this.capacity = capacity;
|
|
348
|
+
}
|
|
349
|
+
sample(panes) {
|
|
350
|
+
const live = new Set(panes.map((pane) => pane.key));
|
|
351
|
+
for (const key of [...this.series.keys()]) {
|
|
352
|
+
if (!live.has(key)) {
|
|
353
|
+
this.series.delete(key);
|
|
354
|
+
this.seen.delete(key);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const result = new Map;
|
|
358
|
+
for (const pane of panes) {
|
|
359
|
+
const produced = pane.lines.length + pane.dropped;
|
|
360
|
+
const previous = this.seen.get(pane.key);
|
|
361
|
+
this.seen.set(pane.key, produced);
|
|
362
|
+
const stream = this.series.get(pane.key) ?? new StreamSeries(this.capacity);
|
|
363
|
+
this.series.set(pane.key, stream);
|
|
364
|
+
if (previous !== undefined)
|
|
365
|
+
stream.push(Math.max(0, produced - previous));
|
|
366
|
+
result.set(pane.key, stream.values());
|
|
367
|
+
}
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function tabPanes(views, linesFor) {
|
|
372
|
+
return views.map((view) => {
|
|
373
|
+
const record = linesFor(view.id);
|
|
374
|
+
const where = view.backend && view.backend !== "local" ? ` · ${view.backend}` : "";
|
|
375
|
+
return {
|
|
376
|
+
kind: "tab",
|
|
377
|
+
key: `${view.id}`,
|
|
378
|
+
title: view.title,
|
|
379
|
+
subtitle: `${view.model ?? "model unknown"}${where}${view.cost ? ` · ${view.cost}` : ""}`,
|
|
380
|
+
status: view.status,
|
|
381
|
+
lines: record.lines,
|
|
382
|
+
dropped: record.dropped
|
|
383
|
+
};
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
var STATUS_MARK = {
|
|
387
|
+
running: "●",
|
|
388
|
+
failed: "✕",
|
|
389
|
+
done: "✓",
|
|
390
|
+
idle: " "
|
|
391
|
+
};
|
|
392
|
+
var WORKSPACE_LEGEND = "1-9 pane · ←→ move · ↑↓ scroll · g/G ends · Esc back";
|
|
393
|
+
function paneBar(snapshot, columns) {
|
|
394
|
+
const tabs = paneTabs(snapshot);
|
|
395
|
+
const cells = tabs.map((tab) => ` ${STATUS_MARK[tab.status] ?? " "}${tab.label} `);
|
|
396
|
+
const full = cells.join("");
|
|
397
|
+
if (full.length <= columns)
|
|
398
|
+
return full || " no panes ";
|
|
399
|
+
const selected = Math.max(0, Math.min(snapshot.selected, tabs.length - 1));
|
|
400
|
+
return `${selected > 0 ? "‹ " : ""}${cells[selected] ?? " no panes "}${selected < tabs.length - 1 ? " ›" : ""}`;
|
|
401
|
+
}
|
|
402
|
+
function composeFrame(snapshot) {
|
|
403
|
+
const rows = [];
|
|
404
|
+
const columns = Math.max(1, Math.floor(snapshot.columns));
|
|
405
|
+
const rowCount = Math.max(1, Math.floor(snapshot.rows));
|
|
406
|
+
const pane = selectedPane(snapshot);
|
|
407
|
+
const height = paneHeight(rowCount);
|
|
408
|
+
const theme = snapshot.palette.tokens;
|
|
409
|
+
const bar = paneBar(snapshot, columns);
|
|
410
|
+
rows.push({ text: bar || " no panes ", bold: true, color: theme.primary });
|
|
411
|
+
const scrolled = pane && !atLiveEdge(pane, height, snapshot.scroll);
|
|
412
|
+
const position = (() => {
|
|
413
|
+
if (!pane || !scrolled)
|
|
414
|
+
return "";
|
|
415
|
+
const maxOffset = Math.max(0, pane.lines.length - height);
|
|
416
|
+
const offsetFromTop = maxOffset - Math.min(snapshot.scroll, maxOffset);
|
|
417
|
+
return scrollIndicator(scrollPercent(offsetFromTop, pane.lines.length, height));
|
|
418
|
+
})();
|
|
419
|
+
const dropped = pane && pane.dropped > 0 ? ` (${pane.dropped} earlier lines dropped)` : "";
|
|
420
|
+
const header = pane ? `${pane.title} ${pane.subtitle}${dropped}` : "nothing open";
|
|
421
|
+
const activityWidth = Math.min(24, Math.max(0, columns - visibleWidth(header) - 3));
|
|
422
|
+
const activity = pane?.activity && pane.activity.length > 1 && activityWidth >= 8 ? ` ${waveLine(pane.activity, { width: activityWidth })}` : "";
|
|
423
|
+
rows.push({
|
|
424
|
+
text: pane ? `${header}${activity}` : header,
|
|
425
|
+
bold: true,
|
|
426
|
+
color: pane?.status === "failed" ? theme.error : pane?.status === "running" ? theme.success : theme.accent
|
|
427
|
+
});
|
|
428
|
+
const body = pane ? visibleLines(pane, height, snapshot.scroll) : [];
|
|
429
|
+
for (let index = 0;index < height; index += 1)
|
|
430
|
+
rows.push({ text: body[index] ?? "" });
|
|
431
|
+
while (rows.length < rowCount - 1)
|
|
432
|
+
rows.push({ text: "" });
|
|
433
|
+
rows.length = Math.max(0, rowCount - 1);
|
|
434
|
+
rows.push({
|
|
435
|
+
text: `${WORKSPACE_LEGEND}${scrolled ? ` ▲ scrolled back ${position}` : ""}`,
|
|
436
|
+
dim: true,
|
|
437
|
+
color: theme.textMuted
|
|
438
|
+
});
|
|
439
|
+
return rows.map((row) => ({ ...row, text: clip(row.text, columns) }));
|
|
440
|
+
}
|
|
441
|
+
function buildWorkspaceSnapshot(input) {
|
|
442
|
+
const panes = [
|
|
443
|
+
...tabPanes(input.views, input.linesFor),
|
|
444
|
+
...input.jobs.map((job) => ({
|
|
445
|
+
kind: "job",
|
|
446
|
+
key: job.id,
|
|
447
|
+
title: `job ${job.id.slice(-6)}`,
|
|
448
|
+
subtitle: job.objective,
|
|
449
|
+
status: job.done ? "done" : "running",
|
|
450
|
+
lines: job.lines,
|
|
451
|
+
dropped: job.dropped
|
|
452
|
+
}))
|
|
453
|
+
];
|
|
454
|
+
const activity = input.activity.sample(panes);
|
|
455
|
+
return {
|
|
456
|
+
panes: panes.map((pane) => ({ ...pane, activity: activity.get(pane.key) })),
|
|
457
|
+
selected: Math.max(0, input.views.findIndex((view) => view.active)),
|
|
458
|
+
scroll: 0,
|
|
459
|
+
palette: input.palette,
|
|
460
|
+
columns: input.columns,
|
|
461
|
+
rows: input.rows
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export { barChart, lineChart, plotSeries, scatterChart, waveLine, heatStrip, keyToAction, applyAction, PaneActivity, composeFrame, buildWorkspaceSnapshot };
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
paintAll,
|
|
8
8
|
roleCode,
|
|
9
9
|
visibleWidth
|
|
10
|
-
} from "./archymedes-
|
|
10
|
+
} from "./archymedes-s4qcy0q4.mjs";
|
|
11
11
|
|
|
12
|
-
// src/render/sections.ts
|
|
12
|
+
// packages/archymedes-cli/src/render/sections.ts
|
|
13
13
|
var TONE_ROLE = {
|
|
14
14
|
good: "success",
|
|
15
15
|
bad: "error",
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
// packages/core/src/cli/protocol.ts
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { promises as fs2 } from "node:fs";
|
|
4
|
+
import path2 from "node:path";
|
|
5
|
+
|
|
6
|
+
// packages/core/src/cli/file-ownership.ts
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
import { promises as fs } from "node:fs";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
|
|
12
|
+
class OwnershipHeldError extends Error {
|
|
13
|
+
pid;
|
|
14
|
+
constructor(pid) {
|
|
15
|
+
super(`Session is already owned by process ${pid}; close or detach the other writer first.`);
|
|
16
|
+
this.pid = pid;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function processStart(pid) {
|
|
20
|
+
if (process.platform !== "linux")
|
|
21
|
+
return;
|
|
22
|
+
try {
|
|
23
|
+
const stat = await fs.readFile(`/proc/${pid}/stat`, "utf8");
|
|
24
|
+
const boot = await fs.readFile("/proc/sys/kernel/random/boot_id", "utf8");
|
|
25
|
+
return `${boot.trim()}:${stat.slice(stat.lastIndexOf(")") + 2).split(" ")[19]}`;
|
|
26
|
+
} catch {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function readOwner(file) {
|
|
31
|
+
try {
|
|
32
|
+
const owner = JSON.parse(await fs.readFile(file, "utf8"));
|
|
33
|
+
if (!owner || typeof owner.token !== "string" || !Number.isSafeInteger(owner.pid) || owner.pid < 1 || typeof owner.host !== "string") {
|
|
34
|
+
throw new Error(`Invalid ownership record: ${file}`);
|
|
35
|
+
}
|
|
36
|
+
return owner;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error.code === "ENOENT")
|
|
39
|
+
return null;
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function isAlive(owner) {
|
|
44
|
+
if (owner.host !== os.hostname())
|
|
45
|
+
return true;
|
|
46
|
+
try {
|
|
47
|
+
process.kill(owner.pid, 0);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
return error.code !== "ESRCH";
|
|
50
|
+
}
|
|
51
|
+
const started = await processStart(owner.pid);
|
|
52
|
+
return !owner.started || !started || owner.started === started;
|
|
53
|
+
}
|
|
54
|
+
async function acquireFileOwnership(file, depth = 0) {
|
|
55
|
+
if (depth > 8)
|
|
56
|
+
throw new Error(`Ownership recovery requires inspection: ${file}`);
|
|
57
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
58
|
+
const owner = { token: randomUUID(), pid: process.pid, host: os.hostname(), started: await processStart(process.pid) };
|
|
59
|
+
const candidate = `${file}.${owner.token}.tmp`;
|
|
60
|
+
const handle = await fs.open(candidate, "wx", 384);
|
|
61
|
+
try {
|
|
62
|
+
await handle.writeFile(JSON.stringify(owner));
|
|
63
|
+
await handle.sync();
|
|
64
|
+
} finally {
|
|
65
|
+
await handle.close();
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
for (let attempt = 0;attempt < 8; attempt++) {
|
|
69
|
+
try {
|
|
70
|
+
await fs.link(candidate, file);
|
|
71
|
+
return async () => {
|
|
72
|
+
if ((await readOwner(file))?.token === owner.token)
|
|
73
|
+
await fs.unlink(file);
|
|
74
|
+
};
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (error.code !== "EEXIST")
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
const current = await readOwner(file);
|
|
80
|
+
if (!current)
|
|
81
|
+
continue;
|
|
82
|
+
if (await isAlive(current))
|
|
83
|
+
throw new OwnershipHeldError(current.pid);
|
|
84
|
+
const release = await acquireFileOwnership(`${file}.reap`, depth + 1);
|
|
85
|
+
try {
|
|
86
|
+
if ((await readOwner(file))?.token === current.token)
|
|
87
|
+
await fs.unlink(file);
|
|
88
|
+
} finally {
|
|
89
|
+
await release();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
throw new Error(`Could not acquire session ownership: ${file}`);
|
|
93
|
+
} finally {
|
|
94
|
+
await fs.unlink(candidate).catch(() => {
|
|
95
|
+
return;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// packages/core/src/cli/protocol.ts
|
|
101
|
+
var ARCHYMEDES_PROTOCOL_VERSION = 1;
|
|
102
|
+
var JOURNAL_GENESIS_HASH = "0".repeat(64);
|
|
103
|
+
var TRANSITIONS = {
|
|
104
|
+
queued: ["running", "cancelled", "failed"],
|
|
105
|
+
running: ["waiting_approval", "completed", "blocked", "needs_verification", "cancelled", "failed", "iteration_limit"],
|
|
106
|
+
waiting_approval: ["running", "blocked", "cancelled", "failed"],
|
|
107
|
+
completed: [],
|
|
108
|
+
blocked: [],
|
|
109
|
+
needs_verification: [],
|
|
110
|
+
cancelled: [],
|
|
111
|
+
failed: [],
|
|
112
|
+
iteration_limit: []
|
|
113
|
+
};
|
|
114
|
+
function assertTurnTransition(from, to) {
|
|
115
|
+
if (!TRANSITIONS[from].includes(to))
|
|
116
|
+
throw new Error(`Illegal Archymedes turn transition: ${from} -> ${to}`);
|
|
117
|
+
}
|
|
118
|
+
var SENSITIVE_KEY = /(authorization|cookie|credential|password|private.?key|secret|token)/i;
|
|
119
|
+
var SECRET_ASSIGNMENT = /((?:[A-Za-z0-9_]*(?:api[_-]?key|access[_-]?key(?:_id)?|authorization|credential|password|passwd|private[_-]?key|secret|token)[A-Za-z0-9_]*)\s*[:=]\s*)([^\s,;]+)/gi;
|
|
120
|
+
var PRIVATE_KEY_BLOCK = /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g;
|
|
121
|
+
var AUTHENTICATED_URL = /([a-z][a-z0-9+.-]*:\/\/)([^/\s:@]+):([^@\s/]+)@/gi;
|
|
122
|
+
function redactJournalString(value) {
|
|
123
|
+
return value.replace(PRIVATE_KEY_BLOCK, "[REDACTED PRIVATE KEY]").replace(AUTHENTICATED_URL, "$1[REDACTED]@").replace(SECRET_ASSIGNMENT, "$1[REDACTED]");
|
|
124
|
+
}
|
|
125
|
+
function boundedJournalValue(value, key = "") {
|
|
126
|
+
if (SENSITIVE_KEY.test(key))
|
|
127
|
+
return "[REDACTED]";
|
|
128
|
+
if (typeof value === "string") {
|
|
129
|
+
const redacted = redactJournalString(value);
|
|
130
|
+
if (redacted.length <= 4000)
|
|
131
|
+
return redacted;
|
|
132
|
+
const digest = createHash("sha256").update(value).digest("hex");
|
|
133
|
+
return `${redacted.slice(0, 4000)}
|
|
134
|
+
...[${redacted.length - 4000} chars omitted; original sha256=${digest}]`;
|
|
135
|
+
}
|
|
136
|
+
if (Array.isArray(value))
|
|
137
|
+
return value.map((item) => boundedJournalValue(item));
|
|
138
|
+
if (value && typeof value === "object") {
|
|
139
|
+
return Object.fromEntries(Object.entries(value).map(([childKey, item]) => [childKey, boundedJournalValue(item, childKey)]));
|
|
140
|
+
}
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
function runtimeEventForJournal(event) {
|
|
144
|
+
if (event.type === "tool_call")
|
|
145
|
+
return { ...event, arguments: boundedJournalValue(event.arguments) };
|
|
146
|
+
if (event.type === "tool_result") {
|
|
147
|
+
return {
|
|
148
|
+
...event,
|
|
149
|
+
content: boundedJournalValue(event.content),
|
|
150
|
+
...event.data ? { data: boundedJournalValue(event.data) } : {}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return event;
|
|
154
|
+
}
|
|
155
|
+
function hashEnvelope(envelope) {
|
|
156
|
+
return createHash("sha256").update(JSON.stringify(envelope)).digest("hex");
|
|
157
|
+
}
|
|
158
|
+
function eventJournalPath(root, sessionId) {
|
|
159
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(sessionId))
|
|
160
|
+
throw new Error("Session id contains unsafe characters");
|
|
161
|
+
return path2.join(root, ".archymedes", "events", `${sessionId}.jsonl`);
|
|
162
|
+
}
|
|
163
|
+
async function readEventJournal(root, sessionId) {
|
|
164
|
+
return (await readJournalFile(root, sessionId)).events;
|
|
165
|
+
}
|
|
166
|
+
async function readJournalFile(root, sessionId) {
|
|
167
|
+
const file = eventJournalPath(root, sessionId);
|
|
168
|
+
let text;
|
|
169
|
+
try {
|
|
170
|
+
text = await fs2.readFile(file, "utf8");
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (error.code === "ENOENT")
|
|
173
|
+
return { events: [], text: "", completeText: "" };
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
const completeText = text.endsWith(`
|
|
177
|
+
`) ? text : text.slice(0, text.lastIndexOf(`
|
|
178
|
+
`) + 1);
|
|
179
|
+
const events = completeText.split(`
|
|
180
|
+
`).filter(Boolean).map((line) => JSON.parse(line));
|
|
181
|
+
let previousHash = JOURNAL_GENESIS_HASH;
|
|
182
|
+
for (let index = 0;index < events.length; index += 1) {
|
|
183
|
+
const event = events[index];
|
|
184
|
+
if (event.protocolVersion !== ARCHYMEDES_PROTOCOL_VERSION)
|
|
185
|
+
throw new Error(`Unsupported Archymedes protocol version ${event.protocolVersion}`);
|
|
186
|
+
if (event.sessionId !== sessionId || event.sequence !== index + 1 || event.previousHash !== previousHash) {
|
|
187
|
+
throw new Error(`Invalid Archymedes event chain at sequence ${event.sequence}`);
|
|
188
|
+
}
|
|
189
|
+
const { hash, ...withoutHash } = event;
|
|
190
|
+
if (hash !== hashEnvelope(withoutHash))
|
|
191
|
+
throw new Error(`Archymedes event integrity check failed at sequence ${event.sequence}`);
|
|
192
|
+
previousHash = hash;
|
|
193
|
+
}
|
|
194
|
+
return { events, text, completeText };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
class EventJournal {
|
|
198
|
+
root;
|
|
199
|
+
sessionId;
|
|
200
|
+
handle = null;
|
|
201
|
+
releaseOwnership;
|
|
202
|
+
closed = false;
|
|
203
|
+
sequence = 0;
|
|
204
|
+
previousHash = JOURNAL_GENESIS_HASH;
|
|
205
|
+
initialized = null;
|
|
206
|
+
tail = Promise.resolve();
|
|
207
|
+
constructor(root, sessionId) {
|
|
208
|
+
this.root = root;
|
|
209
|
+
this.sessionId = sessionId;
|
|
210
|
+
}
|
|
211
|
+
async initialize() {
|
|
212
|
+
this.releaseOwnership = await acquireFileOwnership(`${eventJournalPath(this.root, this.sessionId)}.owner`);
|
|
213
|
+
try {
|
|
214
|
+
const { events, text, completeText } = await readJournalFile(this.root, this.sessionId);
|
|
215
|
+
this.sequence = events.length;
|
|
216
|
+
this.previousHash = events.at(-1)?.hash ?? JOURNAL_GENESIS_HASH;
|
|
217
|
+
const file = eventJournalPath(this.root, this.sessionId);
|
|
218
|
+
await fs2.mkdir(path2.dirname(file), { recursive: true });
|
|
219
|
+
if (text.length !== completeText.length)
|
|
220
|
+
await fs2.truncate(file, Buffer.byteLength(completeText, "utf8"));
|
|
221
|
+
this.handle = await fs2.open(file, "a", 384);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
await this.releaseOwnership();
|
|
224
|
+
this.releaseOwnership = undefined;
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async open() {
|
|
229
|
+
if (this.closed)
|
|
230
|
+
throw new Error("Session journal is closed");
|
|
231
|
+
this.initialized ??= this.initialize();
|
|
232
|
+
await this.initialized;
|
|
233
|
+
}
|
|
234
|
+
append(payload, options = {}) {
|
|
235
|
+
if (this.closed)
|
|
236
|
+
return Promise.reject(new Error("Session journal is closed"));
|
|
237
|
+
let written;
|
|
238
|
+
const operation = this.tail.then(async () => {
|
|
239
|
+
await this.open();
|
|
240
|
+
const withoutHash = {
|
|
241
|
+
protocolVersion: ARCHYMEDES_PROTOCOL_VERSION,
|
|
242
|
+
sequence: this.sequence + 1,
|
|
243
|
+
sessionId: this.sessionId,
|
|
244
|
+
timestamp: new Date().toISOString(),
|
|
245
|
+
previousHash: this.previousHash,
|
|
246
|
+
payload
|
|
247
|
+
};
|
|
248
|
+
written = { ...withoutHash, hash: hashEnvelope(withoutHash) };
|
|
249
|
+
await this.handle.write(`${JSON.stringify(written)}
|
|
250
|
+
`);
|
|
251
|
+
if (options.durable)
|
|
252
|
+
await this.handle.sync();
|
|
253
|
+
this.sequence = written.sequence;
|
|
254
|
+
this.previousHash = written.hash;
|
|
255
|
+
});
|
|
256
|
+
this.tail = operation;
|
|
257
|
+
return operation.then(() => written);
|
|
258
|
+
}
|
|
259
|
+
async flush() {
|
|
260
|
+
await this.tail;
|
|
261
|
+
await this.handle?.sync();
|
|
262
|
+
}
|
|
263
|
+
async close() {
|
|
264
|
+
try {
|
|
265
|
+
await this.initialized;
|
|
266
|
+
await this.flush();
|
|
267
|
+
} finally {
|
|
268
|
+
this.closed = true;
|
|
269
|
+
try {
|
|
270
|
+
await this.handle?.close();
|
|
271
|
+
} finally {
|
|
272
|
+
this.handle = null;
|
|
273
|
+
await this.releaseOwnership?.();
|
|
274
|
+
this.releaseOwnership = undefined;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export { OwnershipHeldError, acquireFileOwnership, assertTurnTransition, runtimeEventForJournal, eventJournalPath, readEventJournal, EventJournal };
|
|
@@ -2,18 +2,21 @@ import {
|
|
|
2
2
|
GUTTER,
|
|
3
3
|
clip,
|
|
4
4
|
panel
|
|
5
|
-
} from "./archymedes-
|
|
5
|
+
} from "./archymedes-twanftqk.mjs";
|
|
6
6
|
import {
|
|
7
7
|
BOLD,
|
|
8
8
|
DIM,
|
|
9
9
|
UNICODE_GLYPHS,
|
|
10
|
+
codeStyle,
|
|
11
|
+
highlightSyntax,
|
|
12
|
+
newSyntaxState,
|
|
10
13
|
paint,
|
|
11
14
|
paintAll,
|
|
12
15
|
roleCode,
|
|
13
16
|
visibleWidth
|
|
14
|
-
} from "./archymedes-
|
|
17
|
+
} from "./archymedes-s4qcy0q4.mjs";
|
|
15
18
|
|
|
16
|
-
// src/render/code-view.ts
|
|
19
|
+
// packages/archymedes-cli/src/render/code-view.ts
|
|
17
20
|
var MAX_DIFF_CELLS = 250000;
|
|
18
21
|
function diffLines(before, after) {
|
|
19
22
|
const left = before === "" ? [] : before.split(`
|
|
@@ -193,7 +196,9 @@ var KEYWORDS = new Set([
|
|
|
193
196
|
"yield"
|
|
194
197
|
]);
|
|
195
198
|
var LITERALS = new Set(["true", "false", "null", "nil", "None", "True", "False", "undefined", "self", "this"]);
|
|
196
|
-
function highlightCode(line, depth, palette) {
|
|
199
|
+
function highlightCode(line, depth, palette, language, state) {
|
|
200
|
+
if (codeStyle().colors === "vscode")
|
|
201
|
+
return highlightSyntax(line, { depth, palette, ...language ? { language } : {}, ...state ? { state } : {} });
|
|
197
202
|
if (depth === "none")
|
|
198
203
|
return line;
|
|
199
204
|
const commentAt = findCommentStart(line);
|
|
@@ -246,11 +251,14 @@ function renderCode(content, style, options = {}) {
|
|
|
246
251
|
const start = options.startLine ?? 1;
|
|
247
252
|
const numberWidth = String(start + lines.length - 1).length;
|
|
248
253
|
const bodyWidth = Math.max(8, style.width - GUTTER.length - numberWidth - 4);
|
|
249
|
-
const render = (from, to) =>
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
+
const render = (from, to) => {
|
|
255
|
+
const syntax = newSyntaxState();
|
|
256
|
+
return lines.slice(from, to).map((line, index) => {
|
|
257
|
+
const number = paint(String(start + from + index).padStart(numberWidth), DIM, style.depth);
|
|
258
|
+
const painted = options.highlight === false ? line : highlightCode(line, style.depth, style.palette, options.language, syntax);
|
|
259
|
+
return `${number} ${clip(painted, bodyWidth, glyphs)}`;
|
|
260
|
+
});
|
|
261
|
+
};
|
|
254
262
|
const hidden = Math.max(0, lines.length - maxLines);
|
|
255
263
|
const shown = render(0, hidden > 0 ? maxLines : lines.length);
|
|
256
264
|
return {
|
|
@@ -266,12 +274,14 @@ function renderDiff(diff, style, options = {}) {
|
|
|
266
274
|
const maxLines = options.maxLines ?? 16;
|
|
267
275
|
const condensed = condenseDiff(diff);
|
|
268
276
|
const bodyWidth = Math.max(8, style.width - GUTTER.length - 4);
|
|
277
|
+
const vscode = codeStyle().colors === "vscode" && style.depth !== "none";
|
|
278
|
+
const bandWidth = Math.min(bodyWidth, Math.max(0, ...diff.map((line) => [...line.text].length)));
|
|
269
279
|
const row = (line) => {
|
|
270
280
|
if (line.kind === "gap")
|
|
271
281
|
return paint(`${glyphs.middot.repeat(3)} ${line.text}`, DIM, style.depth);
|
|
272
282
|
const mark = line.kind === "add" ? glyphs.plus : line.kind === "remove" ? glyphs.minus : " ";
|
|
273
283
|
const code = line.kind === "add" ? roleCode("success", style.palette, style.depth) : line.kind === "remove" ? roleCode("error", style.palette, style.depth) : DIM;
|
|
274
|
-
const body = line.kind === "context" ? paint(line.text, DIM, style.depth) : highlightCode(line.text, style.depth, style.palette);
|
|
284
|
+
const body = line.kind === "context" ? vscode ? highlightCode(line.text, style.depth, style.palette, options.language) : paint(line.text, DIM, style.depth) : vscode ? highlightSyntax(line.text, { depth: style.depth, palette: style.palette, band: line.kind, padTo: bandWidth, ...options.language ? { language: options.language } : {} }) : highlightCode(line.text, style.depth, style.palette);
|
|
275
285
|
return `${paint(mark, code, style.depth)} ${clip(body, bodyWidth, glyphs)}`;
|
|
276
286
|
};
|
|
277
287
|
const rows = condensed.map(row);
|
|
@@ -286,7 +296,7 @@ function renderDiff(diff, style, options = {}) {
|
|
|
286
296
|
}
|
|
287
297
|
function renderFileChange(change, style, options = {}) {
|
|
288
298
|
const language = languageOf(change.path);
|
|
289
|
-
const block = change.kind === "edit" ? renderDiff(diffLines(change.before ?? "", change.after ?? ""), style, { maxLines: options.maxLines }) : renderCode(change.content ?? "", style, { maxLines: options.maxLines, language });
|
|
299
|
+
const block = change.kind === "edit" ? renderDiff(diffLines(change.before ?? "", change.after ?? ""), style, { maxLines: options.maxLines, language }) : renderCode(change.content ?? "", style, { maxLines: options.maxLines, language });
|
|
290
300
|
const badge = change.kind === "edit" ? (() => {
|
|
291
301
|
const stat = diffStat(diffLines(change.before ?? "", change.after ?? ""));
|
|
292
302
|
return `+${stat.added} -${stat.removed}`;
|