synartesis 0.6.12 → 0.6.15
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 +308 -0
- package/README.md +103 -2
- package/dist/{chunk-FUVEHRJP.js → chunk-OODDXM36.js} +376 -66
- package/dist/cli.js +511 -88
- package/dist/proxy.js +39 -9
- package/manifests/filesystem.yaml +8 -0
- package/manifests/git.yaml +8 -0
- package/manifests/github.yaml +10 -0
- package/manifests/memory.yaml +18 -0
- package/package.json +3 -2
package/dist/proxy.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
PROXY_FLAGS,
|
|
3
4
|
canonical,
|
|
4
5
|
cliCommandFrom,
|
|
5
6
|
connectStdioUpstream,
|
|
@@ -18,8 +19,11 @@ import {
|
|
|
18
19
|
refusal,
|
|
19
20
|
runRead,
|
|
20
21
|
toPayload,
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
untested,
|
|
23
|
+
verifyAgainstServers,
|
|
24
|
+
warnUntested,
|
|
25
|
+
withIdempotencyKey
|
|
26
|
+
} from "./chunk-OODDXM36.js";
|
|
23
27
|
import {
|
|
24
28
|
SnapshotError,
|
|
25
29
|
UpstreamError,
|
|
@@ -582,14 +586,21 @@ function createProxyServer(options) {
|
|
|
582
586
|
enter();
|
|
583
587
|
try {
|
|
584
588
|
const wantsGate = policy.gate === "always" || policy.gate === "on_write" && shouldGateOnWrite(args);
|
|
585
|
-
const
|
|
589
|
+
const unresolved = policy.class === "readonly" ? void 0 : journal.findPending({
|
|
590
|
+
runId: activeRun,
|
|
591
|
+
server: route.upstream.name,
|
|
592
|
+
tool: route.tool,
|
|
593
|
+
args
|
|
594
|
+
});
|
|
595
|
+
const mustAsk = wantsGate || unresolved !== void 0;
|
|
596
|
+
const granted = mustAsk ? journal.findApproval({
|
|
586
597
|
server: route.upstream.name,
|
|
587
598
|
tool: route.tool,
|
|
588
599
|
args,
|
|
589
600
|
notBefore: new Date(Date.now() - APPROVAL_WINDOW_MS).toISOString()
|
|
590
601
|
}) : void 0;
|
|
591
602
|
const inherited = granted !== void 0 && granted.runId !== activeRun ? granted : void 0;
|
|
592
|
-
const waiting = granted === void 0 &&
|
|
603
|
+
const waiting = granted === void 0 && mustAsk ? journal.findGated({
|
|
593
604
|
runId: activeRun,
|
|
594
605
|
server: route.upstream.name,
|
|
595
606
|
tool: route.tool,
|
|
@@ -673,9 +684,13 @@ function createProxyServer(options) {
|
|
|
673
684
|
);
|
|
674
685
|
}
|
|
675
686
|
};
|
|
676
|
-
const askedAlready =
|
|
677
|
-
if (
|
|
678
|
-
|
|
687
|
+
const askedAlready = mustAsk;
|
|
688
|
+
if (mustAsk && (granted === void 0 || !spent)) {
|
|
689
|
+
const because = [
|
|
690
|
+
unresolved === void 0 ? void 0 : `an earlier attempt at this exact call, action ${String(unresolved.seq)}, went out and its outcome could never be established, so sending it again may apply the change a second time`,
|
|
691
|
+
wantsGate ? "this action cannot be undone" : void 0
|
|
692
|
+
].filter((one) => one !== void 0);
|
|
693
|
+
await decide(because.join(", and "));
|
|
679
694
|
}
|
|
680
695
|
let snapshot;
|
|
681
696
|
let verify;
|
|
@@ -730,7 +745,11 @@ function createProxyServer(options) {
|
|
|
730
745
|
}
|
|
731
746
|
const forwarded = {
|
|
732
747
|
method: "tools/call",
|
|
733
|
-
params: {
|
|
748
|
+
params: {
|
|
749
|
+
...request.params,
|
|
750
|
+
name: route.tool,
|
|
751
|
+
_meta: withIdempotencyKey(request.params._meta, pending.idempotencyKey)
|
|
752
|
+
}
|
|
734
753
|
};
|
|
735
754
|
try {
|
|
736
755
|
const result = await route.upstream.client.request(forwarded, PassthroughResult, {
|
|
@@ -775,6 +794,13 @@ function createProxyServer(options) {
|
|
|
775
794
|
warnings.push(`inverse could not be resolved: ${describe(error)}`);
|
|
776
795
|
}
|
|
777
796
|
}
|
|
797
|
+
if (verify === void 0 && policy.verify !== void 0) {
|
|
798
|
+
try {
|
|
799
|
+
verify = planRead(policy.verify, context);
|
|
800
|
+
} catch (error) {
|
|
801
|
+
warnings.push(`the drift check could not be planned: ${describe(error)}`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
778
804
|
let postSnapshot;
|
|
779
805
|
if (verify !== void 0) {
|
|
780
806
|
try {
|
|
@@ -1048,7 +1074,7 @@ function parseArgv(argv) {
|
|
|
1048
1074
|
const at = argv.indexOf(flag);
|
|
1049
1075
|
return at === -1 ? void 0 : argv[at + 1];
|
|
1050
1076
|
};
|
|
1051
|
-
const known =
|
|
1077
|
+
const known = PROXY_FLAGS;
|
|
1052
1078
|
const unknown = argv.find((token) => token.startsWith("--") && !known.includes(token));
|
|
1053
1079
|
if (unknown !== void 0) {
|
|
1054
1080
|
throw new Error(`unknown flag ${unknown}; expected one of ${known.join(", ")}`);
|
|
@@ -1109,6 +1135,10 @@ async function main() {
|
|
|
1109
1135
|
process.stderr.write(mark());
|
|
1110
1136
|
}
|
|
1111
1137
|
const manifest = loadManifest(argv.manifest);
|
|
1138
|
+
const unproven = untested(manifest);
|
|
1139
|
+
if (unproven.length > 0) {
|
|
1140
|
+
log.warn({ servers: unproven }, warnUntested(unproven));
|
|
1141
|
+
}
|
|
1112
1142
|
const journal = openJournal(argv.journal);
|
|
1113
1143
|
const declared = Object.entries(manifest.servers);
|
|
1114
1144
|
const wanted = argv.server === void 0 ? declared : declared.filter(([name]) => name === argv.server);
|
|
@@ -14,6 +14,7 @@ servers:
|
|
|
14
14
|
fs:
|
|
15
15
|
command: node
|
|
16
16
|
args: ["node_modules/@modelcontextprotocol/server-filesystem/dist/index.js", "."]
|
|
17
|
+
provenance: live
|
|
17
18
|
|
|
18
19
|
tools:
|
|
19
20
|
# --- reads -------------------------------------------------------------
|
|
@@ -105,3 +106,10 @@ tools:
|
|
|
105
106
|
- match: "fs.create_directory"
|
|
106
107
|
class: irreversible
|
|
107
108
|
gate: always
|
|
109
|
+
|
|
110
|
+
# This policy is written for the server version named at the top. A later
|
|
111
|
+
# version can keep a tool's name and change what it takes, which would leave
|
|
112
|
+
# the policy above describing something else -- and undo would still produce an
|
|
113
|
+
# answer. `synartesis pin` prints a `pins:` block for the server you actually
|
|
114
|
+
# have; with it pasted in, a tool whose shape has moved stops the proxy at
|
|
115
|
+
# startup instead of being quietly trusted.
|
package/manifests/git.yaml
CHANGED
|
@@ -15,6 +15,7 @@ servers:
|
|
|
15
15
|
git:
|
|
16
16
|
command: uvx
|
|
17
17
|
args: ["--from", "mcp-server-git", "mcp-server-git"]
|
|
18
|
+
provenance: live
|
|
18
19
|
|
|
19
20
|
tools:
|
|
20
21
|
# --- reads -------------------------------------------------------------
|
|
@@ -75,3 +76,10 @@ tools:
|
|
|
75
76
|
- match: "git.git_checkout"
|
|
76
77
|
class: irreversible
|
|
77
78
|
gate: never
|
|
79
|
+
|
|
80
|
+
# This policy is written for the server version named at the top. A later
|
|
81
|
+
# version can keep a tool's name and change what it takes, which would leave
|
|
82
|
+
# the policy above describing something else -- and undo would still produce an
|
|
83
|
+
# answer. `synartesis pin` prints a `pins:` block for the server you actually
|
|
84
|
+
# have; with it pasted in, a tool whose shape has moved stops the proxy at
|
|
85
|
+
# startup instead of being quietly trusted.
|
package/manifests/github.yaml
CHANGED
|
@@ -24,6 +24,9 @@ servers:
|
|
|
24
24
|
# Read from your shell; never written into this file.
|
|
25
25
|
env:
|
|
26
26
|
GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_PERSONAL_ACCESS_TOKEN}"
|
|
27
|
+
# Nothing here has met a real github; every shape below comes from
|
|
28
|
+
# documentation. That is a weaker claim than the others make.
|
|
29
|
+
provenance: documented
|
|
27
30
|
|
|
28
31
|
tools:
|
|
29
32
|
# --- reads -------------------------------------------------------------
|
|
@@ -188,3 +191,10 @@ tools:
|
|
|
188
191
|
- match: "github.create_release"
|
|
189
192
|
class: irreversible
|
|
190
193
|
gate: always
|
|
194
|
+
|
|
195
|
+
# This policy is written for the server version named at the top. A later
|
|
196
|
+
# version can keep a tool's name and change what it takes, which would leave
|
|
197
|
+
# the policy above describing something else -- and undo would still produce an
|
|
198
|
+
# answer. `synartesis pin` prints a `pins:` block for the server you actually
|
|
199
|
+
# have; with it pasted in, a tool whose shape has moved stops the proxy at
|
|
200
|
+
# startup instead of being quietly trusted.
|
package/manifests/memory.yaml
CHANGED
|
@@ -18,6 +18,7 @@ servers:
|
|
|
18
18
|
# or the agent will remember into a different file than it reads from.
|
|
19
19
|
env:
|
|
20
20
|
MEMORY_FILE_PATH: "${MEMORY_FILE_PATH}"
|
|
21
|
+
provenance: live
|
|
21
22
|
|
|
22
23
|
tools:
|
|
23
24
|
# --- reads -------------------------------------------------------------
|
|
@@ -54,6 +55,16 @@ tools:
|
|
|
54
55
|
tool: "memory.delete_entities"
|
|
55
56
|
args:
|
|
56
57
|
entityNames: "$result.entities[].name"
|
|
58
|
+
# Deleting an entity takes its observations and its relations with it. So
|
|
59
|
+
# the compensating delete is only safe while the entity is still the thing
|
|
60
|
+
# the agent made: if anybody has added to it since, undoing destroys work
|
|
61
|
+
# nobody recorded. There is no pre-read to compare against -- the entity
|
|
62
|
+
# did not exist before the call -- so this reads it back afterwards, and
|
|
63
|
+
# undo halts rather than deleting over somebody.
|
|
64
|
+
verify:
|
|
65
|
+
tool: "memory.open_nodes"
|
|
66
|
+
args:
|
|
67
|
+
names: "$result.entities[].name"
|
|
57
68
|
|
|
58
69
|
- match: "memory.create_relations"
|
|
59
70
|
class: compensable
|
|
@@ -94,3 +105,10 @@ tools:
|
|
|
94
105
|
- match: "memory.delete_observations"
|
|
95
106
|
class: irreversible
|
|
96
107
|
gate: always
|
|
108
|
+
|
|
109
|
+
# This policy is written for the server version named at the top. A later
|
|
110
|
+
# version can keep a tool's name and change what it takes, which would leave
|
|
111
|
+
# the policy above describing something else -- and undo would still produce an
|
|
112
|
+
# answer. `synartesis pin` prints a `pins:` block for the server you actually
|
|
113
|
+
# have; with it pasted in, a tool whose shape has moved stops the proxy at
|
|
114
|
+
# startup instead of being quietly trusted.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "synartesis",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.15",
|
|
4
4
|
"description": "An undo layer for AI agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -74,7 +74,8 @@
|
|
|
74
74
|
"overrides": {
|
|
75
75
|
"fast-uri": "^3.1.7",
|
|
76
76
|
"hono": "^4.13.7",
|
|
77
|
-
"qs": "^6.16.0"
|
|
77
|
+
"qs": "^6.16.0",
|
|
78
|
+
"esbuild": "^0.28.1"
|
|
78
79
|
}
|
|
79
80
|
},
|
|
80
81
|
"devDependencies": {
|