synartesis 0.8.8 → 0.9.1

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,185 @@
2
2
 
3
3
  What changed, and why it mattered. Dates are release dates.
4
4
 
5
+ ## 0.9.1 — 2026-09-24
6
+
7
+ This release attacks undo instead of describing it. The new tests generate the
8
+ agent sessions rather than script them, kill undo with SIGKILL partway through,
9
+ and race several agents against one approval. They found five bugs that no
10
+ hand-written scenario had reached.
11
+
12
+ ### Fixed
13
+
14
+ - **An undo that succeeded could be stuck for good.** A server that applies an
15
+ inverse and then fails before answering (a crashed handler, a gateway timeout)
16
+ was read as having refused it. The next attempt found the resource already put
17
+ back, called that drift, and every attempt after that refused on it. Undo now
18
+ reads the resource when an inverse errors, and counts it as done when the
19
+ resource is where that inverse puts it.
20
+ - **An undo killed at the wrong moment could block that action for ever.**
21
+ Claiming an action and recording which process holds it were two separate
22
+ writes, though the code's own comment said they were one transaction. A kill
23
+ between them left an action claimed by nobody, which no later undo could tell
24
+ from one still in progress. They are now one transaction.
25
+ - **An undo killed while compensating stopped on its own success.** The delete
26
+ that undoes a create went through, and the resumed undo called the record's
27
+ absence drift. It now recognises it, but only when the owner is certainly
28
+ gone and the record is certainly gone. An edit a person made instead still
29
+ stops it.
30
+ - **Running `install` from your home directory covered servers twice.** There,
31
+ a client's project config and its global one are the same file. It was listed
32
+ twice, and every server in it was drafted into the policy a second time under
33
+ another name.
34
+ - **"Covered, nothing through it yet" about a server in daily use.** The connect
35
+ screen looked up the last use under the client entry's name, not the name the
36
+ policy gives the server, and those differ when two clients list a server with
37
+ the same name.
38
+ - **A policy reading a field every object inherits** (`constructor`,
39
+ `toString`) resolved to nothing when the record lacked it, instead of saying
40
+ it was absent. The inverse then quietly left that field out of what it
41
+ restored.
42
+
43
+ ### Faster
44
+
45
+ - **Servers start together.** Every path that starts more than one server
46
+ (a proxy serving several, `undo`, `check`, `pin`) waited for each before
47
+ starting the next. Three servers that take 800 ms each answered after 2.9 s;
48
+ now it is under 1.6 s, and the order they are reported in is unchanged.
49
+ - **Each server's tools are listed once at start-up**, not three times.
50
+
51
+ ### Tests
52
+
53
+ - `tests/stress-undo.test.ts`: random sessions with awkward values (unicode,
54
+ 5,000-character strings, `__proto__`, text that looks like a template), undone
55
+ through injected crashes before and after writes, undone to random steps, and
56
+ with a person's edit that must survive.
57
+ - `tests/kill-undo.test.ts`: `synartesis undo` killed with SIGKILL the moment a
58
+ random number of actions have been undone, again and again, with a real server
59
+ and store.
60
+ - `tests/race-approval.test.ts`: eight separate agent processes retrying one
61
+ approved email in the same instant. Exactly one is sent.
62
+ - Direct tests for the code only child processes reached before: which clients
63
+ are covered, the environment an undo starts a server with, and the Linux
64
+ notifier.
65
+ - `pnpm stress` runs all three at full strength; `pnpm coverage` reports what
66
+ the suite reaches.
67
+
68
+ ## 0.9.0 — 2026-09-24
69
+
70
+ 0.8.4 to 0.8.8 made Synartesis correct once you were using it. This release is
71
+ about the part before and around that: connecting what you already have without
72
+ breaking it, and being asked less once you have. Several bugs made someone who
73
+ followed the instructions worse off than someone who never installed it. Those
74
+ come first.
75
+
76
+ ### Fixed: wrapping a server no longer breaks it
77
+
78
+ - **Wrapping a server dropped its API keys.** `install` copied the client
79
+ entry's `env` onto the proxy's entry, and the proxy then started the real
80
+ server with almost none of it. Every server now starts through one path,
81
+ with the environment it was given. Existing installs are fixed without
82
+ re-running anything.
83
+ - **`undo` could not authenticate, and in one case undid the wrong store.** It
84
+ started servers with only what the policy declared, so a token held in the
85
+ client config was missing. For the memory server that was silent: without
86
+ `MEMORY_FILE_PATH` it opened its default file, sent the inverse there, and
87
+ reported success. `undo` now reads the environment and working directory from
88
+ the client entry that wraps the server. Each session records a keyed
89
+ fingerprint of every declared variable, never the value, and an undo whose
90
+ variables differ refuses and names them.
91
+ - **One server's missing token was everybody's problem.** `${VAR}` in a policy
92
+ was expanded for every server when the file was read, so undoing a filesystem
93
+ session failed unless the GitHub token was exported. It is expanded per
94
+ server, when that server starts.
95
+ - **A server update took GitHub down.** github-mcp-server 1.12.2 renamed the
96
+ tools the shipped policy read from, and the proxy refused to start. A rule
97
+ whose snapshot, inverse or verify tool has gone now falls back to being held,
98
+ and the proxy starts and says which rules it disabled. `check` still fails
99
+ hard. `github.yaml` is rewritten for 1.12.
100
+ - **`install` covers what it says.** It reads every Claude Code project, not
101
+ only the one it was run from (Claude Code's default scope is per project).
102
+ Running it again after `uninstall` covers the same server again instead of
103
+ duplicating it and then dropping it. Skip reasons are printed whole, where
104
+ "Please set SLACK_BOT_TOKEN" used to be cut off. It will not rewrite a Codex
105
+ table it could not fully read. `installed.json`, which holds the original
106
+ entries and their tokens, is now owner-only.
107
+ - **Bare `undo` and `show` skip empty sessions.** Most sessions record nothing;
108
+ bare `undo` refused on the empty one instead of finding the one with work.
109
+
110
+ ### Added: cover more of what people connect
111
+
112
+ - **Browsers.** Policies for Playwright MCP and Chrome DevTools MCP, written
113
+ from each server's real tool list. Reads are reads. Every interaction is
114
+ recorded, arguments included, and not held: nothing a browser does can be
115
+ undone, and holding every click made both unusable. Running agent-written
116
+ code and uploading files are still held.
117
+ - **A tool its server marks read-only is read as a read** when no rule mentions
118
+ it, instead of held. The MCP specification calls annotations untrusted unless
119
+ they come from a trusted server; the trust relied on is that you chose to
120
+ connect this one. It only ever loosens a hold on a read. It never applies on a
121
+ pinned server, `trust_annotations: false` turns it off, and `check` lists
122
+ every tool let through this way.
123
+ - **Hosted servers.** A server with a token in its headers is reached directly:
124
+ Streamable HTTP, falling back to SSE only where the server says it does not
125
+ speak the newer one, with redirects refused. `install` moves the header values
126
+ into the client entry's `env`, and the policy names the variables. A request
127
+ refused at the door, such as an expired token, is recorded as never sent. A
128
+ server that signs in through a browser is covered with `install --remote`,
129
+ through mcp-remote, which keeps the sign-in itself.
130
+ - **Four more clients:** Gemini CLI, GitHub Copilot CLI, Antigravity, and
131
+ Devin Desktop (what Windsurf became) plus the older Windsurf path. Every path
132
+ and variable syntax was checked against the vendor's own documentation.
133
+ - **Read-only policies** for fetch, Brave Search, Exa, Tavily and the AWS
134
+ documentation server. Each server was started and its tool list read first.
135
+
136
+ ### Added: ask less, tell the person
137
+
138
+ - **A desktop notification when a call is held**, on macOS and Linux. It names
139
+ the server and the tool, never the arguments. `synartesis notify --test`
140
+ checks it reaches you; `SYNARTESIS_NOTIFY=0` turns it off.
141
+ - **`synartesis allow <server.tool>`.** `--for 1h` stops holding that tool until
142
+ the time runs out, with no restart. `--always` edits the policy in place,
143
+ keeping its comments, and a tool that cannot be undone stays marked that way
144
+ (you type its name to confirm). `--stop` holds it again. `A` in `watch` and
145
+ the console, and a button on the desktop card, approve the call in front of
146
+ you and allow the tool for an hour.
147
+ - **A person's no reaches the agent.** A retry of a denied call is told who
148
+ said no, when and why, instead of opening a fresh hold. `approve` on the
149
+ denied call takes the denial back. Denials live in their own table, so a
150
+ spent approval, which is also stored as denied, is never reported as a refusal.
151
+ - **Why a call is held.** A tool with no rule says so, rather than "this action
152
+ cannot be undone".
153
+
154
+ ### Changed
155
+
156
+ - **The agent is never handed the command to approve its own call.** It used
157
+ to be in the refusal text and in the instructions every agent receives, and
158
+ an agent with a shell could run it, recorded under your name. `approve` and
159
+ `allow` now need a person at a terminal unless given `--unattended`, which is
160
+ recorded. This stops an agent following instructions or making a mistake. It
161
+ does not stop a process working to defeat it while running as you, which can
162
+ write the journal directly.
163
+ - **`show` and `undo` speak the same words as everything else.** No more
164
+ `reversible applied` or `result rolled_back` at the end of a dry run that
165
+ wrote nothing. A read-heavy session's reads fold into one line.
166
+ - **`install --print` starts nothing.** It promised to write nothing and then
167
+ started every server, which meant a download for each npx server and a
168
+ browser window for each sign-in. `install` also says which server it is
169
+ starting, instead of going quiet until the last one answers.
170
+ - `check` offers `pin` only once the policy has been used. The desktop window
171
+ points at `install`, not bare `init`. The `--http` server answers an ended
172
+ session with 404, as the MCP specification asks.
173
+ - A policy using `{{args.id}}` (loaded, then sent as those characters) or
174
+ `${args.id}` (failed with no line number) is refused with the line and the
175
+ spelling that works.
176
+
177
+ ### Docs
178
+
179
+ - The user guide's only complete policy example did not parse. It does now,
180
+ and a test loads every policy shown in the README, the guide and the install
181
+ page. The manual setup snippets give each server its own entry with
182
+ `--server`, and the Claude Code command has its `--`.
183
+
5
184
  ## 0.8.8 — 2026-09-22
6
185
 
7
186
  ### Fixed
package/README.md CHANGED
@@ -102,7 +102,8 @@ Then, from anywhere:
102
102
  synartesis install
103
103
  ```
104
104
 
105
- That finds what Claude Code, Claude Desktop, Cursor or Codex already list,
105
+ That finds what Claude Code, Claude Desktop, Cursor, Codex, Gemini CLI, Copilot
106
+ CLI, Antigravity or Devin Desktop (Windsurf) already list,
106
107
  writes one policy covering all of it, and points each entry at the proxy.
107
108
  Servers it recognises get the policy that ships for them and work immediately;
108
109
  the rest are drafted with every tool held until you say how to undo it. Your
@@ -219,12 +220,15 @@ Every tool gets one of four classifications, written down in a manifest:
219
220
  | `readonly` | Changes nothing | `get_customer` | Recorded, forwarded |
220
221
  | `reversible` | Prior state can be restored exactly | `update_customer` | State captured before the write; written back on undo |
221
222
  | `compensable` | Cannot be reversed, but can be offset | `create_charge` | A different call neutralises it |
222
- | `irreversible` | Neither | `send_email` | **Suspended until a human approves it** |
223
+ | `irreversible` | Neither | `send_email` | **Suspended until a human approves it**, or recorded without asking where the policy says `gate: never` |
223
224
 
224
225
  A tool your manifest does not mention is treated as `irreversible`. That is
225
226
  deliberate: silently forwarding an unknown destructive call is the one failure
226
- worth avoiding most. `synartesis check` names them, so you meet that decision
227
- before your agent does.
227
+ worth avoiding most. The one exception is a tool its own server marks
228
+ read-only: that is read as a read, and `check` lists every tool let through that
229
+ way, so you can see exactly what was trusted. (`trust_annotations: false` on a
230
+ server turns it off, and a pinned server never gets it.) `synartesis check`
231
+ names the rest, so you meet that decision before your agent does.
228
232
 
229
233
  A few calls are reversible only when nothing is in the way — moving a file onto
230
234
  a free path is undone by moving it back, moving it onto an existing file
@@ -235,6 +239,65 @@ than putting half of it back and calling that success.
235
239
 
236
240
  ---
237
241
 
242
+ ## Being asked less, and told when it matters
243
+
244
+ **You hear about a held call when it happens.** On macOS and Linux a desktop
245
+ notification names the server and the tool, never the arguments, since they can
246
+ carry secrets and notification history is kept. `synartesis notify --test`
247
+ checks it reaches you; `SYNARTESIS_NOTIFY=0` turns it off. Windows has none yet,
248
+ and `watch` shows every held call on every platform.
249
+
250
+ **Stop being asked, for a while or for good.**
251
+
252
+ ```bash
253
+ synartesis allow crm.send_email --for 1h # a row in the journal; next call on
254
+ synartesis allow crm.send_email --always # edits the policy; next client restart
255
+ synartesis allow crm.send_email --stop # held again from the next call
256
+ synartesis allow # what is let through right now
257
+ ```
258
+
259
+ `--for` goes up to a day and runs out by itself. `--always` changes only that
260
+ tool's gate, in place, keeping every comment in the file. A tool that cannot be
261
+ undone stays marked that way everywhere it shows up: it is recorded, just no
262
+ longer held. For those you type the tool's name to confirm. In `watch` and the
263
+ console, `A` approves the call in front of you and stops asking about that tool
264
+ for an hour. The desktop card has the same button.
265
+
266
+ **A no reaches the agent.** When you deny a call and the agent tries the same
267
+ call again, it is told who said no, when, and why. It does not see a fresh
268
+ "waiting for approval". `approve` on the denied call takes the denial back.
269
+
270
+ **The agent is never handed the way to approve itself.** Nothing it reads
271
+ contains an `approve` or `allow` command, and both refuse to run without a
272
+ person at a terminal unless given `--unattended`, which is recorded as such.
273
+
274
+ ---
275
+
276
+ ## Hosted servers and browsers
277
+
278
+ **Hosted servers.** A server your client reaches at a `url` with a token in its
279
+ headers is covered directly. `install` moves the header values into the
280
+ client entry's `env`, where the client already keeps secrets, and the policy
281
+ names the variables rather than holding the token. The proxy speaks Streamable
282
+ HTTP, falls back to SSE only where the server says it does not speak the newer
283
+ one, and refuses redirects. A request the server refuses at the door (an
284
+ expired token, say) is recorded as never sent, not as an outcome nobody knows.
285
+ A server that signs you in through a browser instead is covered with
286
+ `install --remote`, through [mcp-remote](https://www.npmjs.com/package/mcp-remote),
287
+ which does the sign-in and keeps the token itself.
288
+
289
+ **Browsers.** Nothing a browser does can be undone, and holding every click
290
+ made Playwright and Chrome DevTools unusable behind this. Their shipped policies
291
+ read reads as reads and record every interaction, arguments included, without
292
+ holding it. Running code the agent wrote, and uploading files from your disk,
293
+ are still held.
294
+
295
+ **Policies that ship:** filesystem, memory, git, github, Playwright, Chrome
296
+ DevTools, and a read-only set for fetch, Brave Search, Exa, Tavily and the AWS
297
+ documentation server. Each was written from the server's real tool list.
298
+
299
+ ---
300
+
238
301
  ## Writing a policy for your own server
239
302
 
240
303
  Four servers ship with a policy. For anything else — a database, a ticketing
@@ -407,6 +470,7 @@ get it if it is not installed.
407
470
  |---|---|
408
471
  | `synartesis` | The screen. Everything below can be done from it |
409
472
  | `install` / `uninstall` / `status` | Cover the clients on this machine, put them back, say what is covered |
473
+ | `install --remote` | Also cover hosted servers that sign in through a browser, via mcp-remote |
410
474
  | `init <server> -- <cmd>` | Introspect a server and draft a manifest |
411
475
  | `check` | Load a manifest and verify it against the servers it names |
412
476
  | `pin` | Print the `pins:` block for the servers you have now |
@@ -415,6 +479,8 @@ get it if it is not installed.
415
479
  | `show <id> --live` | The same, plus what has changed in the world since |
416
480
  | `show <id> --full` | Every argument, snapshot and inverse, nothing elided |
417
481
  | `gates` / `approve <id>` / `deny <id>` | What is waiting, and answering it |
482
+ | `allow <server.tool> --for 1h \| --always \| --stop` | Stop being asked about one tool, for a while or for good |
483
+ | `notify --test` | Send one notification, to see whether they reach you |
418
484
  | `resolve <id> --applied\|--failed` | Settle a call whose outcome nothing established |
419
485
  | `undo <id>` | Reverse a session, newest action first |
420
486
  | `undo <id> --dry-run` | Plan it and change nothing |
@@ -515,6 +581,14 @@ holding a call waiting on a person, or one whose undo halted on a conflict. A
515
581
  pruned session cannot be undone afterwards, which is the whole of the trade.
516
582
  Nothing prunes on a timer.
517
583
 
584
+ **What needing a terminal protects against.** `approve` and `allow` refuse to
585
+ run without a person at a terminal, and nothing an agent reads names them. That
586
+ stops an agent following its instructions, or making a mistake, into approving
587
+ its own calls. It does not stop a process working to defeat it while running as
588
+ you: anything with your permissions can write the journal directly. For a
589
+ boundary the agent cannot cross, deny it those commands in its own permission
590
+ settings.
591
+
518
592
  **Durability.** The journal runs `synchronous = NORMAL`. A crash of the process
519
593
  or of the CLI mid-undo loses nothing; only the machine losing power can cost the
520
594
  tail of the write-ahead log. `SYNARTESIS_SYNC=full` asks for an fsync per commit
@@ -535,6 +609,18 @@ pnpm check
535
609
  Every push runs that on Linux and macOS across Node 22 and 24, plus both demos,
536
610
  the installer, and a build of the desktop app.
537
611
 
612
+ ```bash
613
+ pnpm stress
614
+ ```
615
+
616
+ The same three attacks `check` runs lightly, at full strength: 500 random agent
617
+ sessions, each undone and checked byte for byte against where it started (also
618
+ undone to a random step, and with a person's edit made afterwards that must
619
+ survive); `synartesis undo` killed with SIGKILL at random points 25 times over
620
+ and run again; and eight agents racing one approval, ten times, which must send
621
+ exactly one email. A failure names its seed, and `SYNARTESIS_STRESS_SEED`
622
+ replays it. `pnpm coverage` reports what the suite reaches.
623
+
538
624
  **Windows is built and not tested.** The release attaches a Windows installer,
539
625
  and no CI job compiles or exercises it — the test matrix is Linux and macOS. It
540
626
  is expected to work, the code has no platform-specific paths outside