synartesis 0.6.16 → 0.6.18

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 CHANGED
@@ -2,6 +2,81 @@
2
2
 
3
3
  What changed, and why it mattered. Dates are release dates.
4
4
 
5
+ ## 0.6.18 — 2026-09-15
6
+
7
+ A pass over the terminal and the site, most of it drafted by another model and
8
+ reviewed here.
9
+
10
+ ### Changed
11
+
12
+ - **`show` leads each action with the tool that ran.** It used to begin the row
13
+ with sequence, class and status, putting the tool name at column thirty-eight
14
+ where a long server name could push it off. The class and status now sit on
15
+ the line below it, and each action is separated by a blank line rather than
16
+ each action's recovery data being separated from the action.
17
+
18
+ - **A held call is set in the accent colour**, which the palette reserves for
19
+ the one thing that wants a person. It was set in ink, like an ordinary fact.
20
+
21
+ - **The site keeps its columns on a phone.** At 375px the interactive example's
22
+ caption left the undo control stranded alone on a second line; it takes its
23
+ own row now, with a tap target that meets the usual minimum. Figures in the
24
+ evidence blocks are set in tabular figures so they hold their columns as the
25
+ numbers change, and the install guide's two opening columns share a top edge.
26
+
27
+ ### Fixed
28
+
29
+ - **Trailing whitespace on every action in `show`.** The padding that used to
30
+ line up the next column had nothing after it once the tool name moved, so it
31
+ dangled at the end of each line -- inside the escape codes, where trimming
32
+ the finished line cannot reach. Padded now only where a third column follows,
33
+ which is `--live` and usually absent.
34
+
35
+ ### Kept
36
+
37
+ - **The turning mark in `watch` and the screen stays.** It was read as implying
38
+ an agent was at work on an empty journal. It sits beside "watching" and "no
39
+ journal here yet", which are statements about the command rather than about
40
+ an agent, and it answers the only question a quiet screen raises -- whether
41
+ this is still running. Nothing else in the frame changes to answer it: every
42
+ time shown is a wall clock reading of something that already happened, so
43
+ without the mark a live `watch` on a quiet journal is indistinguishable from
44
+ a crashed one. That is the command meant to be left running. There is now a
45
+ test.
46
+
47
+ ## 0.6.17 — 2026-09-15
48
+
49
+ ### Changed
50
+
51
+ - **The session list says what each session did.** Three sessions a second
52
+ apart -- one that wrote a file, one that read one, one that did nothing --
53
+ printed as three identical rows distinguished only by a uuid, so the command
54
+ you run to find the session you want told you nothing about which session you
55
+ want. There are two new columns and one fewer:
56
+
57
+ ```
58
+ session started did state agent
59
+ 331e43de 16:41:21 write_file report.txt done, can undo agent
60
+ ac635294 16:41:21 read only agent
61
+ 5856e14a 16:41:21 create_directory x waiting for you agent
62
+ e52213e6 16:41:20 write_file notes.md undone agent
63
+ ```
64
+
65
+ `did` is the last action that changed something, named the way `watch` and
66
+ the screen already name one. `state` is where that leaves it, including the
67
+ two that look identical otherwise: a run that has been undone and one that
68
+ has not.
69
+
70
+ Ids are printed at eight characters, widened only if that would not tell the
71
+ runs apart -- any unambiguous prefix has always been accepted, and thirty-six
72
+ characters of hex on every line was the price of a collision that has not
73
+ happened. The `status` column is gone: `complete` is true of nearly every
74
+ line and pushed the column that varies off the edge. `active` is not true of
75
+ nearly every line, so it survives as "still open" -- it means a proxy still
76
+ working or one that was killed, and it is the whole reason `close` exists.
77
+
78
+ `--json` is untouched: full ids, same fields. That is what scripts read.
79
+
5
80
  ## 0.6.16 — 2026-09-15
6
81
 
7
82
  ### Fixed
@@ -313,6 +313,18 @@ CREATE INDEX IF NOT EXISTS actions_undoable ON actions(run_id)
313
313
  -- rows it saves reading are the ones carrying the snapshots.
314
314
  CREATE INDEX IF NOT EXISTS actions_unresolved ON actions(run_id, server, tool)
315
315
  WHERE status = 'pending';
316
+
317
+ -- Partial again, and for the third time the partial half is the point. The
318
+ -- session list says what each session did, which means the last action in it
319
+ -- that changed something. Without this, a run that read ten thousand files and
320
+ -- wrote once is walked backwards through all ten thousand reads to find the
321
+ -- write -- and those rows carry the snapshots.
322
+ --
323
+ -- The one of these three the planner finds on its own, so lastWrite does not
324
+ -- name it: the query's WHERE clause is this predicate exactly, and seq is the
325
+ -- ordering it asks for, so the index both filters and sorts.
326
+ CREATE INDEX IF NOT EXISTS actions_writes ON actions(run_id, seq)
327
+ WHERE class <> 'readonly';
316
328
  `;
317
329
 
318
330
  // src/journal/journal.ts
@@ -824,6 +836,17 @@ var SqliteJournal = class {
824
836
  return raw === void 0 ? void 0 : toRun(raw);
825
837
  });
826
838
  }
839
+ lastWrite(runId) {
840
+ return this.#run("lastWrite", () => {
841
+ const raw = this.#db.prepare(
842
+ `SELECT * FROM actions
843
+ WHERE run_id = ? AND class <> 'readonly'
844
+ ORDER BY seq DESC
845
+ LIMIT 1`
846
+ ).get(runId);
847
+ return raw === void 0 ? void 0 : toAction(raw);
848
+ });
849
+ }
827
850
  recentActions(limit) {
828
851
  return this.#run(
829
852
  "recentActions",
@@ -1907,4 +1930,4 @@ export {
1907
1930
  observeState,
1908
1931
  connectStdioUpstream
1909
1932
  };
1910
- //# sourceMappingURL=chunk-OODDXM36.js.map
1933
+ //# sourceMappingURL=chunk-SZHVLCJR.js.map
package/dist/cli.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  verifyAgainstServers,
37
37
  warnUntested,
38
38
  wasRefused
39
- } from "./chunk-OODDXM36.js";
39
+ } from "./chunk-SZHVLCJR.js";
40
40
  import {
41
41
  DriftConflict,
42
42
  ManifestError,
@@ -3279,6 +3279,62 @@ function hint(candidate) {
3279
3279
  out(hintLine(candidate, pathArgs(candidate.needs)));
3280
3280
  out("");
3281
3281
  }
3282
+ function idWidth(runs) {
3283
+ for (let width = 8; width < 36; width += 1) {
3284
+ const seen = new Set(runs.map((run) => run.id.slice(0, width)));
3285
+ if (seen.size === runs.length) {
3286
+ return width;
3287
+ }
3288
+ }
3289
+ return 36;
3290
+ }
3291
+ function didWhat(journal, run, total) {
3292
+ const write = journal.lastWrite(run.id);
3293
+ if (write === void 0) {
3294
+ return {
3295
+ what: style.quiet(total === 0 ? "nothing" : "read only"),
3296
+ state: style.quiet(run.status === "active" ? "still open" : "")
3297
+ };
3298
+ }
3299
+ const what = subject(write.args);
3300
+ const said = plainly(write);
3301
+ const rest = total - 1;
3302
+ return {
3303
+ what: `${style.strong(write.tool)}${what === "" ? "" : ` ${what}`}` + (rest > 0 ? style.quiet(` +${String(rest)} more`) : ""),
3304
+ // A run still open outranks whatever its last action did. `complete` was
3305
+ // dropped as a column because it is true of nearly every line, but
3306
+ // `active` is not -- it is a proxy still working or one that was killed,
3307
+ // and it is the whole reason `close` exists. Losing it with the column it
3308
+ // shared would have been a real loss hidden inside a tidier table.
3309
+ //
3310
+ // Otherwise the action's own state, and always, not only when it asks
3311
+ // something of the reader: a run that has been undone and one that has not
3312
+ // look identical otherwise, and which of the two this is happens to be the
3313
+ // question the command gets asked.
3314
+ state: run.status === "active" ? style.accent("still open") : said.needs ? style.accent(said.text) : style.quiet(said.text)
3315
+ };
3316
+ }
3317
+ function laid(text, width) {
3318
+ const codes = /\u001b\[[0-9;]*m/g;
3319
+ const bare = text.replace(codes, "");
3320
+ if (bare.length <= width) {
3321
+ return text + " ".repeat(width - bare.length);
3322
+ }
3323
+ let kept = "";
3324
+ let shown = 0;
3325
+ let at = 0;
3326
+ for (const match of text.matchAll(codes)) {
3327
+ const plain = text.slice(at, match.index);
3328
+ const room = width - 1 - shown;
3329
+ if (plain.length >= room) {
3330
+ return `${kept}${plain.slice(0, room)}\u2026\x1B[0m`;
3331
+ }
3332
+ kept += plain + match[0];
3333
+ shown += plain.length;
3334
+ at = match.index + match[0].length;
3335
+ }
3336
+ return `${kept}${text.slice(at, at + width - 1 - shown)}\u2026`;
3337
+ }
3282
3338
  function runList(journal, asJson, journalPath) {
3283
3339
  const runs = [...journal.listRuns()].reverse();
3284
3340
  const tally2 = journal.tallyRuns();
@@ -3291,13 +3347,14 @@ function runList(journal, asJson, journalPath) {
3291
3347
  out("no runs recorded");
3292
3348
  return 0;
3293
3349
  }
3350
+ const width = idWidth(runs);
3294
3351
  out("");
3295
3352
  out(` ${style.label("sessions")} ${style.quiet("most recent first")}`);
3296
- out(` ${rule(96)}`);
3353
+ out(` ${rule(88)}`);
3297
3354
  out("");
3298
3355
  out(
3299
3356
  style.quiet(
3300
- ` ${"session".padEnd(36)} ${"started".padEnd(15)} ${"status".padEnd(12)} actions agent`
3357
+ ` ${"session".padEnd(width)} ${"started".padEnd(13)} ${"did".padEnd(26)} ${"state".padEnd(26)} agent`
3301
3358
  )
3302
3359
  );
3303
3360
  for (const run of runs) {
@@ -3305,11 +3362,12 @@ function runList(journal, asJson, journalPath) {
3305
3362
  const { unknown, waiting } = actions;
3306
3363
  const notes = [
3307
3364
  unknown === 0 ? "" : `${String(unknown)} of unknown outcome`,
3308
- waiting === 0 ? "" : `${String(waiting)} awaiting approval`
3365
+ waiting > 1 ? `${String(waiting)} awaiting approval` : ""
3309
3366
  ].filter((note2) => note2 !== "");
3310
3367
  const note = notes.length === 0 ? "" : ` ${style.accent(`(${notes.join("; ")})`)}`;
3368
+ const did = didWhat(journal, run, actions.actions);
3311
3369
  out(
3312
- ` ${style.strong(run.id)} ${style.quiet(shortTime(run.startedAt).trimEnd().padEnd(13))} ${run.status.padEnd(12)} ${String(actions.actions).padStart(7)} ${run.label ?? "-"}${note}`
3370
+ ` ${style.strong(run.id.slice(0, width))} ${style.quiet(shortTime(run.startedAt).trimEnd().padEnd(13))} ${laid(did.what, 26)} ${laid(did.state, 26)} ${style.quiet(run.label ?? "-")}${note}`
3313
3371
  );
3314
3372
  }
3315
3373
  out("");
@@ -3344,7 +3402,8 @@ async function runShow(argv, journal, asJson) {
3344
3402
  return 0;
3345
3403
  }
3346
3404
  out("");
3347
- out(` ${style.label("session")} ${style.strong(run.label ?? "an agent")} ${style.quiet(run.id)}`);
3405
+ out(` ${style.label("session")} ${style.strong(run.label ?? "an agent")}`);
3406
+ out(` ${style.quiet(run.id)}`);
3348
3407
  out(` ${rule(54)}`);
3349
3408
  out("");
3350
3409
  out(` ${style.quiet("agent ")} ${run.label ?? "-"}`);
@@ -3359,7 +3418,6 @@ async function runShow(argv, journal, asJson) {
3359
3418
  return 0;
3360
3419
  }
3361
3420
  out("");
3362
- out("");
3363
3421
  out(` ${style.label("timeline")}`);
3364
3422
  out(` ${rule(72)}`);
3365
3423
  out("");
@@ -3367,7 +3425,11 @@ async function runShow(argv, journal, asJson) {
3367
3425
  for (const action of actions) {
3368
3426
  const now = live.get(action.seq);
3369
3427
  out(
3370
- ` ${style.quiet(String(action.seq).padStart(3))} ${badgeOf(action)} ${statusOf(action)} ${style.strong(`${action.server}.${action.tool}`)}` + (now === void 0 ? "" : ` ${conditionOf(now)}`)
3428
+ ` ${style.quiet(String(action.seq).padStart(3))} ${style.strong(`${action.server}.${action.tool}`)}`
3429
+ );
3430
+ const alongside = now !== void 0;
3431
+ out(
3432
+ ` ${badgeOf(action, alongside)} ${statusOf(action, alongside)}` + (now === void 0 ? "" : ` ${conditionOf(now)}`)
3371
3433
  );
3372
3434
  if (now?.diff !== void 0) {
3373
3435
  for (const line2 of now.diff.split("\n")) {
@@ -3404,11 +3466,11 @@ async function runShow(argv, journal, asJson) {
3404
3466
  out(` ${line2}`);
3405
3467
  }
3406
3468
  } else {
3407
- out(` ${style.quiet("undo:")} ${style.quiet(summariseArgs(inverseArgs(action.inverse), 90))}`);
3469
+ out(` ${style.quiet("undo:")} ${style.strong(summariseArgs(inverseArgs(action.inverse), 90))}`);
3408
3470
  }
3409
3471
  }
3472
+ out("");
3410
3473
  }
3411
- out("");
3412
3474
  out(` ${summarise2(actions)}`);
3413
3475
  if (inspection !== void 0) {
3414
3476
  out("");
@@ -3462,17 +3524,19 @@ var CLASS_MARK = {
3462
3524
  unclassified: "?"
3463
3525
  };
3464
3526
  var BADGE_WIDTH = "irreversible".length + 2;
3465
- function badgeOf(action) {
3466
- const plain = `${CLASS_MARK[action.class]} ${action.class}`.padEnd(BADGE_WIDTH);
3527
+ function badgeOf(action, pad2 = true) {
3528
+ const name = `${CLASS_MARK[action.class]} ${action.class}`;
3529
+ const plain = pad2 ? name.padEnd(BADGE_WIDTH) : name;
3467
3530
  return action.class === "irreversible" ? style.accent(plain) : style.quiet(plain);
3468
3531
  }
3469
- function statusOf(action) {
3470
- const text = labelFor(action).padEnd(13);
3532
+ function statusOf(action, pad2 = true) {
3533
+ const label = labelFor(action);
3534
+ const text = pad2 ? label.padEnd(13) : label;
3471
3535
  if (wasRefused(action)) {
3472
3536
  return style.accent(text);
3473
3537
  }
3474
3538
  if (action.status === "gated") {
3475
- return style.strong(text);
3539
+ return style.accent(text);
3476
3540
  }
3477
3541
  return style.quiet(text);
3478
3542
  }
package/dist/proxy.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  verifyAgainstServers,
24
24
  warnUntested,
25
25
  withIdempotencyKey
26
- } from "./chunk-OODDXM36.js";
26
+ } from "./chunk-SZHVLCJR.js";
27
27
  import {
28
28
  SnapshotError,
29
29
  UpstreamError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synartesis",
3
- "version": "0.6.16",
3
+ "version": "0.6.18",
4
4
  "description": "An undo layer for AI agents.",
5
5
  "type": "module",
6
6
  "private": false,