synartesis 0.6.16 → 0.6.19

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,104 @@
2
2
 
3
3
  What changed, and why it mattered. Dates are release dates.
4
4
 
5
+ ## 0.6.19 — 2026-09-15
6
+
7
+ ### Fixed
8
+
9
+ - **Both guides showed output the command no longer produces.** The worked
10
+ examples of `show` were still the pre-0.6.18 single-line row, and the session
11
+ lists were still the pre-0.6.17 table of full uuids -- in the runbook, whose
12
+ pdf had just been rebuilt from that stale text, and in the user guide, whose
13
+ pdf had not been rebuilt at all, so the two shipped documents disagreed with
14
+ each other as well as with the terminal. Regenerated from real runs and both
15
+ pdfs built together.
16
+
17
+ - **Four tap targets touching on a phone.** A rule added with the last release
18
+ set `gap:0` on the screenshot tabs, overriding the deliberate `gap:8px` set
19
+ with the two columns further up the same stylesheet. The buttons carry their
20
+ own border, so the four of them closed into one block with doubled seams and
21
+ no separation between adjacent targets. Measured at 375px: they abutted
22
+ exactly, at x=22 and x=188 across a width of 166.
23
+
24
+ - `badgeOf` and `statusOf` took a `pad` that defaulted to true, and the one
25
+ call site of each passes it explicitly -- so the default could never apply,
26
+ and it was the wrong answer for the ordinary case anyway. Required now.
27
+
28
+ ## 0.6.18 — 2026-09-15
29
+
30
+ A pass over the terminal and the site, most of it drafted by another model and
31
+ reviewed here.
32
+
33
+ ### Changed
34
+
35
+ - **`show` leads each action with the tool that ran.** It used to begin the row
36
+ with sequence, class and status, putting the tool name at column thirty-eight
37
+ where a long server name could push it off. The class and status now sit on
38
+ the line below it, and each action is separated by a blank line rather than
39
+ each action's recovery data being separated from the action.
40
+
41
+ - **A held call is set in the accent colour**, which the palette reserves for
42
+ the one thing that wants a person. It was set in ink, like an ordinary fact.
43
+
44
+ - **The site keeps its columns on a phone.** At 375px the interactive example's
45
+ caption left the undo control stranded alone on a second line; it takes its
46
+ own row now, with a tap target that meets the usual minimum. Figures in the
47
+ evidence blocks are set in tabular figures so they hold their columns as the
48
+ numbers change, and the install guide's two opening columns share a top edge.
49
+
50
+ ### Fixed
51
+
52
+ - **Trailing whitespace on every action in `show`.** The padding that used to
53
+ line up the next column had nothing after it once the tool name moved, so it
54
+ dangled at the end of each line -- inside the escape codes, where trimming
55
+ the finished line cannot reach. Padded now only where a third column follows,
56
+ which is `--live` and usually absent.
57
+
58
+ ### Kept
59
+
60
+ - **The turning mark in `watch` and the screen stays.** It was read as implying
61
+ an agent was at work on an empty journal. It sits beside "watching" and "no
62
+ journal here yet", which are statements about the command rather than about
63
+ an agent, and it answers the only question a quiet screen raises -- whether
64
+ this is still running. Nothing else in the frame changes to answer it: every
65
+ time shown is a wall clock reading of something that already happened, so
66
+ without the mark a live `watch` on a quiet journal is indistinguishable from
67
+ a crashed one. That is the command meant to be left running. There is now a
68
+ test.
69
+
70
+ ## 0.6.17 — 2026-09-15
71
+
72
+ ### Changed
73
+
74
+ - **The session list says what each session did.** Three sessions a second
75
+ apart -- one that wrote a file, one that read one, one that did nothing --
76
+ printed as three identical rows distinguished only by a uuid, so the command
77
+ you run to find the session you want told you nothing about which session you
78
+ want. There are two new columns and one fewer:
79
+
80
+ ```
81
+ session started did state agent
82
+ 331e43de 16:41:21 write_file report.txt done, can undo agent
83
+ ac635294 16:41:21 read only agent
84
+ 5856e14a 16:41:21 create_directory x waiting for you agent
85
+ e52213e6 16:41:20 write_file notes.md undone agent
86
+ ```
87
+
88
+ `did` is the last action that changed something, named the way `watch` and
89
+ the screen already name one. `state` is where that leaves it, including the
90
+ two that look identical otherwise: a run that has been undone and one that
91
+ has not.
92
+
93
+ Ids are printed at eight characters, widened only if that would not tell the
94
+ runs apart -- any unambiguous prefix has always been accepted, and thirty-six
95
+ characters of hex on every line was the price of a collision that has not
96
+ happened. The `status` column is gone: `complete` is true of nearly every
97
+ line and pushed the column that varies off the edge. `active` is not true of
98
+ nearly every line, so it survives as "still open" -- it means a proxy still
99
+ working or one that was killed, and it is the whole reason `close` exists.
100
+
101
+ `--json` is untouched: full ids, same fields. That is what scripts read.
102
+
5
103
  ## 0.6.16 — 2026-09-15
6
104
 
7
105
  ### 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) {
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) {
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.19",
4
4
  "description": "An undo layer for AI agents.",
5
5
  "type": "module",
6
6
  "private": false,