synartesis 0.6.18 → 0.6.20

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,61 @@
2
2
 
3
3
  What changed, and why it mattered. Dates are release dates.
4
4
 
5
+ ## 0.6.20 — 2026-09-15
6
+
7
+ ### Fixed
8
+
9
+ - **The undo preview showed a write nobody confirmed as one that was
10
+ confirmed.** A tool call can land and then fail to say so -- the server
11
+ writes the record and times out, or answers with an error on its way out.
12
+ Synartesis already handled this correctly: it refuses to read `isError` as a
13
+ promise that nothing happened, reads the resource back, and records the
14
+ change so it stays recoverable. What it did not do was say so afterwards.
15
+ The row that came out of that path carried no trace of it at all -- the
16
+ refusal was written to the log, which nobody reads, at the moment it
17
+ happens, which is not the moment it matters -- so `undo --dry-run` printed
18
+ `revert ... state matches; applying inverse`, character for character what a
19
+ confirmed write prints. The row now records how it was established, and the
20
+ preview prints it under the step as `caveat`. Same in the app's
21
+ `preview_undo`, so a model cannot describe an inferred write to somebody as
22
+ a confirmed one.
23
+
24
+ - **A halt that named the consequence and hid the cause.** Where the transport
25
+ failed and the read-back proved the write had landed, undo correctly stopped
26
+ rather than reverting on an unverified assumption -- but said only "the
27
+ post-state was never captured, so drift could not be ruled out", which reads
28
+ as a missed reading. The reason there was no reading, which the row knew all
29
+ along, is now printed with it.
30
+
31
+ - An action whose outcome is unknown was told it got that way because "the
32
+ process died mid-call". A timeout, or an error the read-back could not
33
+ settle, arrives in exactly the same state and was told the same wrong story.
34
+ It now says what is actually known, and the row's own error says which of
35
+ them it was.
36
+
37
+ ## 0.6.19 — 2026-09-15
38
+
39
+ ### Fixed
40
+
41
+ - **Both guides showed output the command no longer produces.** The worked
42
+ examples of `show` were still the pre-0.6.18 single-line row, and the session
43
+ lists were still the pre-0.6.17 table of full uuids -- in the runbook, whose
44
+ pdf had just been rebuilt from that stale text, and in the user guide, whose
45
+ pdf had not been rebuilt at all, so the two shipped documents disagreed with
46
+ each other as well as with the terminal. Regenerated from real runs and both
47
+ pdfs built together.
48
+
49
+ - **Four tap targets touching on a phone.** A rule added with the last release
50
+ set `gap:0` on the screenshot tabs, overriding the deliberate `gap:8px` set
51
+ with the two columns further up the same stylesheet. The buttons carry their
52
+ own border, so the four of them closed into one block with doubled seams and
53
+ no separation between adjacent targets. Measured at 375px: they abutted
54
+ exactly, at x=22 and x=188 across a width of 166.
55
+
56
+ - `badgeOf` and `statusOf` took a `pad` that defaulted to true, and the one
57
+ call site of each passes it explicitly -- so the default could never apply,
58
+ and it was the wrong answer for the ordinary case anyway. Required now.
59
+
5
60
  ## 0.6.18 — 2026-09-15
6
61
 
7
62
  A pass over the terminal and the site, most of it drafted by another model and
package/dist/cli.js CHANGED
@@ -375,7 +375,12 @@ function classify(action, replanning, goAhead) {
375
375
  case "pending":
376
376
  return {
377
377
  kind: "halt",
378
- reason: "outcome unknown: the process died mid-call, so whether this applied cannot be determined",
378
+ // Not "the process died mid-call". That is one of the ways a row ends
379
+ // up here; a server that timed out, or answered with an error the
380
+ // read-back could not settle, arrives in exactly the same state and is
381
+ // told the same wrong story. The row's own error says which it was,
382
+ // and the halt prints it directly underneath.
383
+ reason: "outcome unknown: the call went out and its outcome was never established, so whether this applied cannot be determined",
379
384
  verified: false
380
385
  };
381
386
  case "gated":
@@ -452,7 +457,14 @@ ${seen}` : seen;
452
457
  if (!parsedPlan.success) {
453
458
  const approved = action.approvedBy === void 0 ? "" : `, approved by ${action.approvedBy}`;
454
459
  const reason = action.class === "irreversible" ? `cannot be undone${approved}; left in place` : `no usable inverse was recorded${action.error === void 0 ? "" : `: ${action.error}`}; left in place`;
455
- steps.push({ ...describeStep(action), kind: "permanent", reason, verified: false });
460
+ const unconfirmed = action.class === "irreversible" ? caveat(action) : void 0;
461
+ steps.push({
462
+ ...describeStep(action),
463
+ kind: "permanent",
464
+ reason,
465
+ verified: false,
466
+ ...unconfirmed === void 0 ? {} : { note: unconfirmed }
467
+ });
456
468
  leftInPlace = true;
457
469
  continue;
458
470
  }
@@ -517,13 +529,23 @@ ${seen}` : seen;
517
529
  }
518
530
  if (!verified && action.class === "reversible" && !force) {
519
531
  const reason = unverifiedBecause(action);
532
+ const because = caveat(action);
520
533
  halted = {
521
534
  seq: action.seq,
522
535
  reason,
523
- detail: "Without it there is no way to tell this resource from one somebody has edited since, so the recorded value was not written.",
536
+ detail: (because === void 0 ? "" : `${because}
537
+
538
+ `) + "Without it there is no way to tell this resource from one somebody has edited since, so the recorded value was not written.",
524
539
  conflict: true
525
540
  };
526
- steps.push({ ...describeStep(action), kind: "halt", reason, verified: false, plan });
541
+ steps.push({
542
+ ...describeStep(action),
543
+ kind: "halt",
544
+ reason,
545
+ verified: false,
546
+ plan,
547
+ ...because === void 0 ? {} : { note: because }
548
+ });
527
549
  break;
528
550
  }
529
551
  if (!verified && action.status === "rolling_back") {
@@ -539,13 +561,15 @@ ${seen}` : seen;
539
561
  const after = intendedAfterInverse(action);
540
562
  projected.set(resourceKey(toResolvedRead(verifyRead.data)), after ?? UNFORESEEABLE);
541
563
  }
564
+ const recordedWith = caveat(action);
542
565
  steps.push({
543
566
  ...describeStep(action),
544
567
  kind: "revert",
545
568
  reason: verified ? "state matches; applying inverse" : forcedOver ?? unverifiedBecause(action),
546
569
  verified,
547
570
  plan,
548
- ...rebuilt.inverse === void 0 ? {} : { replanned: true }
571
+ ...rebuilt.inverse === void 0 ? {} : { replanned: true },
572
+ ...recordedWith === void 0 ? {} : { note: recordedWith }
549
573
  });
550
574
  if (dryRun) {
551
575
  continue;
@@ -613,6 +637,9 @@ ${seen}` : seen;
613
637
  function unverifiedBecause(action) {
614
638
  return action.verify === void 0 ? "no read declared for this tool, so drift could not be ruled out -- a `verify` read in its policy would give it one" : "the post-state was never captured, so drift could not be ruled out";
615
639
  }
640
+ function caveat(action) {
641
+ return action.status === "applied" && action.error !== void 0 && action.error !== "" ? action.error : void 0;
642
+ }
616
643
  function describeStep(action) {
617
644
  return { seq: action.seq, server: action.server, tool: action.tool };
618
645
  }
@@ -3524,12 +3551,12 @@ var CLASS_MARK = {
3524
3551
  unclassified: "?"
3525
3552
  };
3526
3553
  var BADGE_WIDTH = "irreversible".length + 2;
3527
- function badgeOf(action, pad2 = true) {
3554
+ function badgeOf(action, pad2) {
3528
3555
  const name = `${CLASS_MARK[action.class]} ${action.class}`;
3529
3556
  const plain = pad2 ? name.padEnd(BADGE_WIDTH) : name;
3530
3557
  return action.class === "irreversible" ? style.accent(plain) : style.quiet(plain);
3531
3558
  }
3532
- function statusOf(action, pad2 = true) {
3559
+ function statusOf(action, pad2) {
3533
3560
  const label = labelFor(action);
3534
3561
  const text = pad2 ? label.padEnd(13) : label;
3535
3562
  if (wasRefused(action)) {
@@ -3783,6 +3810,11 @@ function report(result, alreadyForcing = false, as = "") {
3783
3810
  out(
3784
3811
  ` ${style.quiet(String(step.seq).padStart(3))} ${kind} ${style.strong(`${step.server}.${step.tool}`)} ${style.quiet(step.reason)}${unverified}`
3785
3812
  );
3813
+ if (step.note !== void 0 && step.kind !== "halt") {
3814
+ for (const [at, line2] of wrapped(step.note, 62).entries()) {
3815
+ out(` ${at === 0 ? style.accent("caveat") : " "} ${style.quiet(line2)}`);
3816
+ }
3817
+ }
3786
3818
  if (step.plan !== void 0 && step.kind === "revert") {
3787
3819
  const verb = `${step.replanned === true ? "replanned, " : ""}${result.dryRun ? "would call" : "called"}`;
3788
3820
  out(
package/dist/proxy.js CHANGED
@@ -755,6 +755,7 @@ function createProxyServer(options) {
755
755
  const result = await route.upstream.client.request(forwarded, PassthroughResult, {
756
756
  signal: extra.signal
757
757
  });
758
+ let inferred;
758
759
  const refused = refusal(result);
759
760
  if (refused !== void 0) {
760
761
  const settled = policy.refusal === "clean" ? "none" : await whatHappened(router, probe, priorState, extra.signal);
@@ -774,6 +775,7 @@ function createProxyServer(options) {
774
775
  );
775
776
  return result;
776
777
  }
778
+ inferred = `the upstream answered with an error and the change was established by reading the resource back, so nothing confirmed it: ${refused}`;
777
779
  log?.warn(
778
780
  { seq: pending.seq, tool: route.tool, reason: refused },
779
781
  "the upstream reported an error after changing the resource"
@@ -781,6 +783,9 @@ function createProxyServer(options) {
781
783
  }
782
784
  const context = { args, snapshot, result: toPayload(result) };
783
785
  const warnings = [];
786
+ if (inferred !== void 0) {
787
+ warnings.push(inferred);
788
+ }
784
789
  if (missingPriorState !== void 0) {
785
790
  warnings.push(
786
791
  `no prior state existed, so there is nothing to restore: ${missingPriorState}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synartesis",
3
- "version": "0.6.18",
3
+ "version": "0.6.20",
4
4
  "description": "An undo layer for AI agents.",
5
5
  "type": "module",
6
6
  "private": false,