synartesis 0.9.0 → 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 +63 -0
- package/README.md +12 -0
- package/dist/{chunk-AEEKBR5D.js → chunk-H4SPMXQW.js} +52 -11
- package/dist/cli.js +71 -26
- package/dist/proxy.js +11 -8
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,69 @@
|
|
|
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
|
+
|
|
5
68
|
## 0.9.0 — 2026-09-24
|
|
6
69
|
|
|
7
70
|
0.8.4 to 0.8.8 made Synartesis correct once you were using it. This release is
|
package/README.md
CHANGED
|
@@ -609,6 +609,18 @@ pnpm check
|
|
|
609
609
|
Every push runs that on Linux and macOS across Node 22 and 24, plus both demos,
|
|
610
610
|
the installer, and a build of the desktop app.
|
|
611
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
|
+
|
|
612
624
|
**Windows is built and not tested.** The release attaches a Windows installer,
|
|
613
625
|
and no CI job compiles or exercises it — the test matrix is Linux and macOS. It
|
|
614
626
|
is expected to work, the code has no platform-specific paths outside
|
|
@@ -51,11 +51,10 @@ function launch(command, args) {
|
|
|
51
51
|
} catch {
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
function desktopNotifier(env = process.env) {
|
|
54
|
+
function desktopNotifier(env = process.env, os = platform()) {
|
|
55
55
|
if (env["SYNARTESIS_NOTIFY"] === "0") {
|
|
56
56
|
return SILENT;
|
|
57
57
|
}
|
|
58
|
-
const os = platform();
|
|
59
58
|
if (os === "darwin") {
|
|
60
59
|
return (notice) => {
|
|
61
60
|
const { title, body } = words(notice);
|
|
@@ -901,7 +900,7 @@ var SqliteJournal = class {
|
|
|
901
900
|
* resource is contested.
|
|
902
901
|
*/
|
|
903
902
|
markRollingBack(actionId, from = ["applied"]) {
|
|
904
|
-
return this.#run("markRollingBack", () => {
|
|
903
|
+
return this.#run("markRollingBack", () => this.#db.transaction(() => {
|
|
905
904
|
const slots = from.map(() => "?").join(",");
|
|
906
905
|
const result = this.#db.prepare(
|
|
907
906
|
`UPDATE actions SET status = 'rolling_back' WHERE id = ? AND status IN (${slots})`
|
|
@@ -915,7 +914,7 @@ var SqliteJournal = class {
|
|
|
915
914
|
pid = excluded.pid, claimed_at = excluded.claimed_at`
|
|
916
915
|
).run(actionId, hostname(), process.pid, (/* @__PURE__ */ new Date()).toISOString());
|
|
917
916
|
return true;
|
|
918
|
-
});
|
|
917
|
+
}).immediate());
|
|
919
918
|
}
|
|
920
919
|
leaseFor(actionId) {
|
|
921
920
|
return this.#run("leaseFor", () => {
|
|
@@ -1530,7 +1529,7 @@ function walk(current, parts, at, reference) {
|
|
|
1530
1529
|
return walk(current[segment.index], parts, at + 1, reference);
|
|
1531
1530
|
}
|
|
1532
1531
|
case "key": {
|
|
1533
|
-
if (typeof current !== "object" || !(segment.key
|
|
1532
|
+
if (typeof current !== "object" || !Object.hasOwn(current, segment.key)) {
|
|
1534
1533
|
throw new ManifestError(`${reference} is unresolvable: ${segment.key} is absent`);
|
|
1535
1534
|
}
|
|
1536
1535
|
const next = Object.getOwnPropertyDescriptor(current, segment.key)?.value;
|
|
@@ -2072,6 +2071,11 @@ function explainPins(server, faults) {
|
|
|
2072
2071
|
|
|
2073
2072
|
// src/manifest/verify.ts
|
|
2074
2073
|
import { z as z3 } from "zod";
|
|
2074
|
+
async function listAll(upstreams) {
|
|
2075
|
+
return new Map(
|
|
2076
|
+
await Promise.all(upstreams.map(async (upstream) => [upstream.name, await toolShapes(upstream)]))
|
|
2077
|
+
);
|
|
2078
|
+
}
|
|
2075
2079
|
var listSchema = z3.looseObject({
|
|
2076
2080
|
tools: z3.array(
|
|
2077
2081
|
z3.looseObject({
|
|
@@ -2103,11 +2107,11 @@ async function toolShapes(upstream) {
|
|
|
2103
2107
|
} while (cursor !== void 0);
|
|
2104
2108
|
return shapes;
|
|
2105
2109
|
}
|
|
2106
|
-
async function verifyAgainstServers(upstreams, manifest) {
|
|
2110
|
+
async function verifyAgainstServers(upstreams, manifest, listed) {
|
|
2107
2111
|
const shapes = /* @__PURE__ */ new Map();
|
|
2108
2112
|
const available = /* @__PURE__ */ new Map();
|
|
2109
2113
|
for (const upstream of upstreams) {
|
|
2110
|
-
const advertised = await toolShapes(upstream);
|
|
2114
|
+
const advertised = listed?.get(upstream.name) ?? await toolShapes(upstream);
|
|
2111
2115
|
shapes.set(upstream.name, advertised);
|
|
2112
2116
|
available.set(upstream.name, new Set(advertised.map((tool) => tool.name)));
|
|
2113
2117
|
}
|
|
@@ -2154,10 +2158,11 @@ async function verifyAgainstServers(upstreams, manifest) {
|
|
|
2154
2158
|
);
|
|
2155
2159
|
}
|
|
2156
2160
|
}
|
|
2157
|
-
async function withoutMissingTools(upstreams, manifest) {
|
|
2161
|
+
async function withoutMissingTools(upstreams, manifest, listed) {
|
|
2158
2162
|
const available = /* @__PURE__ */ new Map();
|
|
2159
2163
|
for (const upstream of upstreams) {
|
|
2160
|
-
|
|
2164
|
+
const advertised = listed?.get(upstream.name) ?? await toolShapes(upstream);
|
|
2165
|
+
available.set(upstream.name, new Set(advertised.map((tool) => tool.name)));
|
|
2161
2166
|
}
|
|
2162
2167
|
const missing = (qualified) => {
|
|
2163
2168
|
const target = splitQualified(qualified);
|
|
@@ -2537,6 +2542,31 @@ function refusal(error) {
|
|
|
2537
2542
|
}
|
|
2538
2543
|
return void 0;
|
|
2539
2544
|
}
|
|
2545
|
+
async function startTogether(items, start2) {
|
|
2546
|
+
const settled2 = await Promise.allSettled(
|
|
2547
|
+
items.map(async (item) => ({ item, upstream: await start2(item) }))
|
|
2548
|
+
);
|
|
2549
|
+
const started = [];
|
|
2550
|
+
const failed = [];
|
|
2551
|
+
items.forEach((item, index) => {
|
|
2552
|
+
const result = settled2[index];
|
|
2553
|
+
if (result?.status === "fulfilled") {
|
|
2554
|
+
started.push(result.value.upstream);
|
|
2555
|
+
} else if (result !== void 0) {
|
|
2556
|
+
failed.push({ item, error: result.reason });
|
|
2557
|
+
}
|
|
2558
|
+
});
|
|
2559
|
+
return { started, failed };
|
|
2560
|
+
}
|
|
2561
|
+
async function startAll(items, start2) {
|
|
2562
|
+
const { started, failed } = await startTogether(items, start2);
|
|
2563
|
+
const first = failed[0];
|
|
2564
|
+
if (first !== void 0) {
|
|
2565
|
+
await Promise.all(started.map((upstream) => upstream.close().catch(() => void 0)));
|
|
2566
|
+
throw first.error;
|
|
2567
|
+
}
|
|
2568
|
+
return started;
|
|
2569
|
+
}
|
|
2540
2570
|
|
|
2541
2571
|
// src/install/clients.ts
|
|
2542
2572
|
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync, renameSync, rmSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -2854,7 +2884,15 @@ function discover(cwd) {
|
|
|
2854
2884
|
sites.push({ client, label: LABELS[client], format: "json", path, scope, at: ["mcpServers"] });
|
|
2855
2885
|
}
|
|
2856
2886
|
}
|
|
2857
|
-
|
|
2887
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2888
|
+
return sites.filter((site) => {
|
|
2889
|
+
const key = `${resolve2(site.path)}\0${site.at.join("\0")}`;
|
|
2890
|
+
if (seen.has(key)) {
|
|
2891
|
+
return false;
|
|
2892
|
+
}
|
|
2893
|
+
seen.add(key);
|
|
2894
|
+
return true;
|
|
2895
|
+
});
|
|
2858
2896
|
}
|
|
2859
2897
|
function isRecord(value) {
|
|
2860
2898
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -3891,6 +3929,7 @@ export {
|
|
|
3891
3929
|
splitQualified,
|
|
3892
3930
|
fingerprint,
|
|
3893
3931
|
pinBlock,
|
|
3932
|
+
listAll,
|
|
3894
3933
|
toolShapes,
|
|
3895
3934
|
verifyAgainstServers,
|
|
3896
3935
|
withoutMissingTools,
|
|
@@ -3918,6 +3957,8 @@ export {
|
|
|
3918
3957
|
differing,
|
|
3919
3958
|
declaredNames,
|
|
3920
3959
|
connectUpstream,
|
|
3960
|
+
startTogether,
|
|
3961
|
+
startAll,
|
|
3921
3962
|
LOOKED_FOR,
|
|
3922
3963
|
CLIENT_IDS,
|
|
3923
3964
|
isClientId,
|
|
@@ -3932,4 +3973,4 @@ export {
|
|
|
3932
3973
|
applyUninstall,
|
|
3933
3974
|
clientEnvFor
|
|
3934
3975
|
};
|
|
3935
|
-
//# sourceMappingURL=chunk-
|
|
3976
|
+
//# sourceMappingURL=chunk-H4SPMXQW.js.map
|
package/dist/cli.js
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
isClientId,
|
|
34
34
|
isWrapped,
|
|
35
35
|
labelFor,
|
|
36
|
+
listAll,
|
|
36
37
|
loadManifest,
|
|
37
38
|
observeState,
|
|
38
39
|
openJournal,
|
|
@@ -48,6 +49,8 @@ import {
|
|
|
48
49
|
serversAt,
|
|
49
50
|
splitQualified,
|
|
50
51
|
standing,
|
|
52
|
+
startAll,
|
|
53
|
+
startTogether,
|
|
51
54
|
style,
|
|
52
55
|
toPayload,
|
|
53
56
|
toResolvedRead,
|
|
@@ -59,7 +62,7 @@ import {
|
|
|
59
62
|
verifyAgainstServers,
|
|
60
63
|
warnUntested,
|
|
61
64
|
wasRefused
|
|
62
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-H4SPMXQW.js";
|
|
63
66
|
import {
|
|
64
67
|
DriftConflict,
|
|
65
68
|
ManifestError,
|
|
@@ -372,7 +375,11 @@ ${seen}` : seen;
|
|
|
372
375
|
}
|
|
373
376
|
if (sameState(current, recordedPost.data)) {
|
|
374
377
|
verified = true;
|
|
375
|
-
} else if (sameState(current, intendedAfterInverse(action))
|
|
378
|
+
} else if (sameState(current, intendedAfterInverse(action)) || // An inverse this undo sent before it was killed, whose owner is gone:
|
|
379
|
+
// the resource having reached the state it produces is the inverse
|
|
380
|
+
// having landed. Found by SIGKILLing undo mid-compensation: the delete
|
|
381
|
+
// had gone through, and every later attempt called its absence drift.
|
|
382
|
+
action.status === "rolling_back" && journal.leaseFor(action.id)?.alive === false && looksUndone(current, action)) {
|
|
376
383
|
steps.push({
|
|
377
384
|
...describeStep(action),
|
|
378
385
|
kind: "already-reverted",
|
|
@@ -487,6 +494,19 @@ ${seen}` : seen;
|
|
|
487
494
|
journal.markRolledBack(action.id);
|
|
488
495
|
continue;
|
|
489
496
|
}
|
|
497
|
+
const landed = outcome.rejected && recordedPost.success && verifyRead.success ? await landedAnyway(router, toResolvedRead(verifyRead.data), recordedPost.data, action, signal) : void 0;
|
|
498
|
+
if (landed !== void 0) {
|
|
499
|
+
journal.markRolledBack(action.id);
|
|
500
|
+
steps[steps.length - 1] = {
|
|
501
|
+
...describeStep(action),
|
|
502
|
+
kind: "revert",
|
|
503
|
+
reason: landed,
|
|
504
|
+
verified,
|
|
505
|
+
plan,
|
|
506
|
+
note: `the server reported an error (${truncated(outcome.message)}), but the resource had changed as this inverse changes it`
|
|
507
|
+
};
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
490
510
|
const halt = new RollbackHalted(action.seq, outcome.message);
|
|
491
511
|
if (outcome.rejected) {
|
|
492
512
|
journal.markInverseRejected(action.id, halt.message);
|
|
@@ -544,6 +564,28 @@ function overwriteText(current, action) {
|
|
|
544
564
|
}
|
|
545
565
|
return changedLines(current, intended2);
|
|
546
566
|
}
|
|
567
|
+
async function landedAnyway(router, read, post, action, signal) {
|
|
568
|
+
let now;
|
|
569
|
+
try {
|
|
570
|
+
now = await observeState(router, read, signal);
|
|
571
|
+
} catch {
|
|
572
|
+
return void 0;
|
|
573
|
+
}
|
|
574
|
+
if (sameState(now, post)) {
|
|
575
|
+
return void 0;
|
|
576
|
+
}
|
|
577
|
+
return looksUndone(now, action) ? "done, despite the server reporting an error" : void 0;
|
|
578
|
+
}
|
|
579
|
+
function looksUndone(now, action) {
|
|
580
|
+
const intended2 = intendedAfterInverse(action);
|
|
581
|
+
if (intended2 !== void 0) {
|
|
582
|
+
return sameState(now, intended2);
|
|
583
|
+
}
|
|
584
|
+
return action.class === "compensable" && !now.present;
|
|
585
|
+
}
|
|
586
|
+
function truncated(text) {
|
|
587
|
+
return text.length > 160 ? `${text.slice(0, 157)}...` : text;
|
|
588
|
+
}
|
|
547
589
|
function intendedAfterInverse(action) {
|
|
548
590
|
return action.snapshot === void 0 ? void 0 : { present: true, value: action.snapshot };
|
|
549
591
|
}
|
|
@@ -1278,7 +1320,9 @@ function scan(journal, cwd) {
|
|
|
1278
1320
|
}
|
|
1279
1321
|
const connections = Object.entries(servers).map(([server, entry]) => {
|
|
1280
1322
|
const covered = isWrapped(entry);
|
|
1281
|
-
const
|
|
1323
|
+
const args = entry.args ?? [];
|
|
1324
|
+
const named = covered && args.includes("--server") ? args[args.indexOf("--server") + 1] : void 0;
|
|
1325
|
+
const lastSeen = seen.get(named ?? server);
|
|
1282
1326
|
return {
|
|
1283
1327
|
client: site.client,
|
|
1284
1328
|
scope: site.scope,
|
|
@@ -2254,14 +2298,16 @@ async function startAsTheClientWould(manifestPath, name, spec, session) {
|
|
|
2254
2298
|
async function runPin(argv) {
|
|
2255
2299
|
const path = findManifest(flag(argv, "--manifest"));
|
|
2256
2300
|
const manifest = loadManifest(path);
|
|
2257
|
-
|
|
2301
|
+
let shapes = /* @__PURE__ */ new Map();
|
|
2258
2302
|
const upstreams = [];
|
|
2259
2303
|
try {
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2304
|
+
upstreams.push(
|
|
2305
|
+
...await startAll(
|
|
2306
|
+
Object.entries(manifest.servers),
|
|
2307
|
+
([name, spec]) => startAsTheClientWould(path, name, spec)
|
|
2308
|
+
)
|
|
2309
|
+
);
|
|
2310
|
+
shapes = await listAll(upstreams);
|
|
2265
2311
|
} finally {
|
|
2266
2312
|
for (const upstream of upstreams) {
|
|
2267
2313
|
await upstream.close();
|
|
@@ -2321,13 +2367,17 @@ async function runCheck(argv) {
|
|
|
2321
2367
|
const offered = /* @__PURE__ */ new Map();
|
|
2322
2368
|
const trustedReads = /* @__PURE__ */ new Map();
|
|
2323
2369
|
try {
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2370
|
+
upstreams.push(
|
|
2371
|
+
...await startAll(
|
|
2372
|
+
Object.entries(manifest.servers),
|
|
2373
|
+
([name, spec]) => startAsTheClientWould(path, name, spec)
|
|
2374
|
+
)
|
|
2375
|
+
);
|
|
2376
|
+
const listed = await listAll(upstreams);
|
|
2377
|
+
await verifyAgainstServers(upstreams, manifest, listed);
|
|
2328
2378
|
const resolver = createPolicyResolver(manifest);
|
|
2329
2379
|
for (const upstream of upstreams) {
|
|
2330
|
-
const shapes =
|
|
2380
|
+
const shapes = listed.get(upstream.name) ?? [];
|
|
2331
2381
|
const trusted = shapes.filter(
|
|
2332
2382
|
(tool) => tool.readOnly === true && trustsMarks(manifest, upstream.name) && !resolver.resolve(`${upstream.name}.${tool.name}`).matched
|
|
2333
2383
|
).map((tool) => tool.name);
|
|
@@ -3663,19 +3713,14 @@ function report(result, alreadyForcing = false, as = "") {
|
|
|
3663
3713
|
}
|
|
3664
3714
|
async function withUpstreams(manifestPath, use, only, session) {
|
|
3665
3715
|
const manifest = loadManifest(manifestPath);
|
|
3666
|
-
const
|
|
3667
|
-
const
|
|
3716
|
+
const wanted = Object.entries(manifest.servers).filter(([name]) => only === void 0 || only.has(name));
|
|
3717
|
+
const { started, failed } = await startTogether(
|
|
3718
|
+
wanted,
|
|
3719
|
+
([name, spec]) => startAsTheClientWould(manifestPath, name, spec, session)
|
|
3720
|
+
);
|
|
3721
|
+
const upstreams = [...started];
|
|
3722
|
+
const missing = failed.map(({ item: [name], error }) => `${name}: ${describe(error)}`);
|
|
3668
3723
|
try {
|
|
3669
|
-
for (const [name, spec] of Object.entries(manifest.servers)) {
|
|
3670
|
-
if (only !== void 0 && !only.has(name)) {
|
|
3671
|
-
continue;
|
|
3672
|
-
}
|
|
3673
|
-
try {
|
|
3674
|
-
upstreams.push(await startAsTheClientWould(manifestPath, name, spec, session));
|
|
3675
|
-
} catch (error) {
|
|
3676
|
-
missing.push(`${name}: ${describe(error)}`);
|
|
3677
|
-
}
|
|
3678
|
-
}
|
|
3679
3724
|
if (upstreams.length === 0 && missing.length > 0) {
|
|
3680
3725
|
throw new ManifestError(`no server could be started. ${missing.join("; ")}`);
|
|
3681
3726
|
}
|
package/dist/proxy.js
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
findManifest,
|
|
15
15
|
fingerprint2 as fingerprint,
|
|
16
16
|
isDisconnected,
|
|
17
|
+
listAll,
|
|
17
18
|
loadManifest,
|
|
18
19
|
mark,
|
|
19
20
|
observeState,
|
|
@@ -23,8 +24,8 @@ import {
|
|
|
23
24
|
qualify,
|
|
24
25
|
refusal,
|
|
25
26
|
runRead,
|
|
27
|
+
startAll,
|
|
26
28
|
toPayload,
|
|
27
|
-
toolShapes,
|
|
28
29
|
trustsMarks,
|
|
29
30
|
ungoverned,
|
|
30
31
|
untested,
|
|
@@ -33,7 +34,7 @@ import {
|
|
|
33
34
|
warnUntested,
|
|
34
35
|
withIdempotencyKey,
|
|
35
36
|
withoutMissingTools
|
|
36
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-H4SPMXQW.js";
|
|
37
38
|
import {
|
|
38
39
|
SnapshotError,
|
|
39
40
|
UpstreamError,
|
|
@@ -1303,8 +1304,8 @@ async function main() {
|
|
|
1303
1304
|
const upstreams = [];
|
|
1304
1305
|
const key = journal.fingerprintKey();
|
|
1305
1306
|
const startedWith = /* @__PURE__ */ new Map();
|
|
1307
|
+
upstreams.push(...await startAll([...wanted], ([name, spec]) => connectUpstream(name, spec, { env: source })));
|
|
1306
1308
|
for (const [name, spec] of wanted) {
|
|
1307
|
-
upstreams.push(await connectUpstream(name, spec, { env: source }));
|
|
1308
1309
|
let client;
|
|
1309
1310
|
try {
|
|
1310
1311
|
client = source.kind === "inherit" ? clientEnvFor(argv.manifest, name)?.env : void 0;
|
|
@@ -1316,7 +1317,8 @@ async function main() {
|
|
|
1316
1317
|
fingerprints: fingerprint(key, upstreamEnv(name, spec, source), declaredNames(spec, client))
|
|
1317
1318
|
});
|
|
1318
1319
|
}
|
|
1319
|
-
const
|
|
1320
|
+
const listed = await listAll(upstreams);
|
|
1321
|
+
const { manifest: served, disabled } = await withoutMissingTools(upstreams, manifest, listed);
|
|
1320
1322
|
for (const line of disabled) {
|
|
1321
1323
|
log.warn(line);
|
|
1322
1324
|
}
|
|
@@ -1325,14 +1327,15 @@ async function main() {
|
|
|
1325
1327
|
argv.server === void 0 ? served : {
|
|
1326
1328
|
...served,
|
|
1327
1329
|
tools: served.tools.filter((rule) => rule.match.startsWith(`${String(argv.server)}.`))
|
|
1328
|
-
}
|
|
1330
|
+
},
|
|
1331
|
+
listed
|
|
1329
1332
|
);
|
|
1330
1333
|
const uncovered = ungoverned(
|
|
1331
1334
|
served,
|
|
1332
|
-
new Map(
|
|
1335
|
+
new Map(upstreams.map((upstream) => [
|
|
1333
1336
|
upstream.name,
|
|
1334
|
-
(
|
|
1335
|
-
]))
|
|
1337
|
+
(listed.get(upstream.name) ?? []).filter((tool) => !(tool.readOnly === true && trustsMarks(served, upstream.name))).map((tool) => tool.name)
|
|
1338
|
+
]))
|
|
1336
1339
|
);
|
|
1337
1340
|
for (const entry of uncovered) {
|
|
1338
1341
|
log.warn(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "synartesis",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "An undo layer for AI agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -58,6 +58,8 @@
|
|
|
58
58
|
"demo": "tsup --silent && ./demo/filesystem-demo.sh",
|
|
59
59
|
"demo:memory": "tsup --silent && ./demo/memory-demo.sh",
|
|
60
60
|
"check": "pnpm build && pnpm typecheck && pnpm lint && pnpm test",
|
|
61
|
+
"stress": "tsup --silent && SYNARTESIS_STRESS_SESSIONS=500 SYNARTESIS_KILL_ROUNDS=25 SYNARTESIS_RACE_ROUNDS=10 vitest run tests/stress-undo.test.ts tests/kill-undo.test.ts tests/race-approval.test.ts",
|
|
62
|
+
"coverage": "tsup --silent && vitest run --testTimeout=240000 --coverage --coverage.provider=v8 --coverage.include='src/**' --coverage.reportOnFailure",
|
|
61
63
|
"prepublishOnly": "pnpm check"
|
|
62
64
|
},
|
|
63
65
|
"dependencies": {
|
|
@@ -93,6 +95,7 @@
|
|
|
93
95
|
"@types/react": "^19.3.0",
|
|
94
96
|
"@types/react-dom": "^19.3.0",
|
|
95
97
|
"@vitejs/plugin-react": "^6.1.1",
|
|
98
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
96
99
|
"electron": "^44.3.0",
|
|
97
100
|
"electron-builder": "^26.15.3",
|
|
98
101
|
"eslint": "^10.8.1",
|