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.
Files changed (25) hide show
  1. package/dist/{archymedes-aj30app4.mjs → archymedes-06fh0z84.mjs} +370 -159
  2. package/dist/{archymedes-sf9t1hz9.mjs → archymedes-086xsz64.mjs} +176 -77
  3. package/dist/{archymedes-d5wqcmdr.mjs → archymedes-0jxzsh4z.mjs} +97 -97
  4. package/dist/{archymedes-h13ap0za.mjs → archymedes-1g1y6q74.mjs} +1 -1
  5. package/dist/{archymedes-4kx65anz.mjs → archymedes-1jcn326m.mjs} +43 -12
  6. package/dist/{archymedes-g8y3rt6t.mjs → archymedes-kn6wtq32.mjs} +332 -9
  7. package/dist/archymedes-main.mjs +31010 -27842
  8. package/dist/archymedes-s4qcy0q4.mjs +1250 -0
  9. package/dist/{archymedes-twbks7g3.mjs → archymedes-t1jtzkmp.mjs} +214 -5
  10. package/dist/{archymedes-tsne2m7p.mjs → archymedes-twanftqk.mjs} +2 -2
  11. package/dist/archymedes-w4bjfye7.mjs +280 -0
  12. package/dist/{archymedes-dz59nvw7.mjs → archymedes-zvcs68bv.mjs} +21 -11
  13. package/dist/{editor-screen-g08zzt70.mjs → editor-screen-pf252j2r.mjs} +49 -14
  14. package/dist/{factory-7vtm2fed.mjs → factory-hyv8agr0.mjs} +326 -326
  15. package/dist/{file-screen-ma9yxq2b.mjs → file-screen-1j65f7b4.mjs} +11 -9
  16. package/dist/{guide-screen-gd129txd.mjs → guide-screen-9vxsk83x.mjs} +10 -8
  17. package/dist/{index-qvgcwsvm.mjs → index-teq1cta4.mjs} +1 -1
  18. package/dist/{node-mdgzx68h.mjs → node-0fh63p8z.mjs} +7 -7
  19. package/dist/pager-screen-aamt6xv0.mjs +155 -0
  20. package/dist/projection-x6x6hm9t.mjs +248 -0
  21. package/dist/{sandbox-templates-9jcjws23.mjs → sandbox-templates-8sd1psez.mjs} +2 -2
  22. package/dist/workspace-screen-ntdcgfq9.mjs +70 -0
  23. package/package.json +2 -2
  24. package/dist/archymedes-a10k8yrk.mjs +0 -464
  25. 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-g8y3rt6t.mjs";
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-a10k8yrk.mjs";
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
- export { barChart, lineChart, plotSeries, scatterChart, waveLine, StreamSeries, heatStrip };
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-a10k8yrk.mjs";
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-tsne2m7p.mjs";
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-a10k8yrk.mjs";
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) => lines.slice(from, to).map((line, index) => {
250
- const number = paint(String(start + from + index).padStart(numberWidth), DIM, style.depth);
251
- const painted = options.highlight === false ? line : highlightCode(line, style.depth, style.palette);
252
- return `${number} ${clip(painted, bodyWidth, glyphs)}`;
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}`;