@openparachute/agent 0.2.0 → 0.2.3-rc.2
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/.parachute/module.json +3 -3
- package/package.json +5 -1
- package/src/agent-defs.test.ts +11 -11
- package/src/agents.test.ts +2 -2
- package/src/backends/programmatic.test.ts +5 -5
- package/src/daemon-agent-defs-api.test.ts +6 -6
- package/src/daemon-agent-env-api.test.ts +2 -2
- package/src/daemon-config-api.test.ts +2 -2
- package/src/daemon-jobs-api.test.ts +2 -2
- package/src/daemon.test.ts +5 -5
- package/src/jobs.test.ts +2 -2
- package/src/mint-token.test.ts +2 -2
- package/src/module-manifest.test.ts +2 -2
- package/src/programmatic-wiring.test.ts +2 -2
- package/src/spawn-agent-cli.test.ts +2 -2
- package/src/spawn-agent.test.ts +4 -4
- package/src/transports/vault.test.ts +65 -64
- package/src/transports/vault.ts +21 -21
- package/web/ui/dist/assets/index-5KEwEhfi.js +60 -0
- package/web/ui/dist/assets/index-C-iWdFFV.css +1 -0
- package/web/ui/dist/index.html +15 -0
package/.parachute/module.json
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"module": "vault",
|
|
53
53
|
"event": "note.created",
|
|
54
54
|
"filter": {
|
|
55
|
-
"tags": ["
|
|
55
|
+
"tags": ["agent/message/inbound"],
|
|
56
56
|
"has_metadata": ["channel"],
|
|
57
57
|
"missing_metadata": ["channel_inbound_rendered_at"]
|
|
58
58
|
}
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"module": "vault",
|
|
86
86
|
"event": "note.created",
|
|
87
87
|
"filter": {
|
|
88
|
-
"tags": ["
|
|
88
|
+
"tags": ["agent/definition"]
|
|
89
89
|
}
|
|
90
90
|
},
|
|
91
91
|
"sink": {
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
"module": "vault",
|
|
111
111
|
"event": "note.updated",
|
|
112
112
|
"filter": {
|
|
113
|
-
"tags": ["
|
|
113
|
+
"tags": ["agent/definition"]
|
|
114
114
|
}
|
|
115
115
|
},
|
|
116
116
|
"sink": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openparachute/agent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3-rc.2",
|
|
4
4
|
"description": "Vault-native agents for Claude Code — a #agent/definition note + an inbound message becomes a sandboxed claude turn; the reply is written back as a note. Messaging gateway on :1941.",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"type": "module",
|
|
@@ -40,5 +40,9 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/bun": "latest"
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/ParachuteComputer/parachute-agent.git"
|
|
43
47
|
}
|
|
44
48
|
}
|
package/src/agent-defs.test.ts
CHANGED
|
@@ -375,8 +375,8 @@ describe("DefVaultClient", () => {
|
|
|
375
375
|
const client = new DefVaultClient(binding);
|
|
376
376
|
const notes = await client.listDefNotes();
|
|
377
377
|
expect(urls).toHaveLength(1);
|
|
378
|
-
//
|
|
379
|
-
expect(urls[0]).toContain("tag
|
|
378
|
+
// `agent/definition` → `agent%2Fdefinition` (the `/` percent-encoded).
|
|
379
|
+
expect(urls[0]).toContain("tag=agent%2Fdefinition");
|
|
380
380
|
expect(urls[0]).toContain("include_content=true");
|
|
381
381
|
expect(auth).toBe("Bearer write-token");
|
|
382
382
|
expect(notes.map((n) => n.id)).toEqual(["Agents/uni-dev", "Agents/researcher"]);
|
|
@@ -444,7 +444,7 @@ describe("DefVaultClient", () => {
|
|
|
444
444
|
expect(created.id).toBe("Agents/newbot");
|
|
445
445
|
expect(captured!.method).toBe("POST");
|
|
446
446
|
expect(captured!.url).toContain("/vault/default/api/notes");
|
|
447
|
-
expect(captured!.body.tags).toEqual(["
|
|
447
|
+
expect(captured!.body.tags).toEqual(["agent/definition"]);
|
|
448
448
|
expect(captured!.body.content).toBe("P");
|
|
449
449
|
expect((captured!.body.metadata as Record<string, string>).name).toBe("newbot");
|
|
450
450
|
expect(captured!.body.path).toBe("Agents/newbot");
|
|
@@ -538,7 +538,7 @@ function vaultFetch(opts: {
|
|
|
538
538
|
opts.patches?.push({ id, status: meta.status, pending: meta.pending });
|
|
539
539
|
return new Response(null, { status: 200 });
|
|
540
540
|
}
|
|
541
|
-
if (u.includes("/api/notes?") && u.includes("tag
|
|
541
|
+
if (u.includes("/api/notes?") && u.includes("tag=agent%2Fdefinition")) {
|
|
542
542
|
return new Response(JSON.stringify(opts.defs ?? []), {
|
|
543
543
|
status: 200,
|
|
544
544
|
headers: { "content-type": "application/json" },
|
|
@@ -678,7 +678,7 @@ describe("AgentDefRegistry — lifecycle", () => {
|
|
|
678
678
|
const u = String(url);
|
|
679
679
|
const method = init?.method ?? "GET";
|
|
680
680
|
if (method === "PATCH") return new Response(null, { status: 200 });
|
|
681
|
-
if (u.includes("/api/notes?") && u.includes("tag
|
|
681
|
+
if (u.includes("/api/notes?") && u.includes("tag=agent%2Fdefinition")) {
|
|
682
682
|
return new Response(JSON.stringify(present), { status: 200, headers: { "content-type": "application/json" } });
|
|
683
683
|
}
|
|
684
684
|
return new Response("[]", { status: 200 });
|
|
@@ -714,7 +714,7 @@ describe("AgentDefRegistry — lifecycle", () => {
|
|
|
714
714
|
const u = String(url);
|
|
715
715
|
const method = init?.method ?? "GET";
|
|
716
716
|
if (method === "PATCH") return new Response(null, { status: 200 });
|
|
717
|
-
if (u.includes("/api/notes?") && u.includes("tag
|
|
717
|
+
if (u.includes("/api/notes?") && u.includes("tag=agent%2Fdefinition")) {
|
|
718
718
|
return new Response(JSON.stringify(current), { status: 200, headers: { "content-type": "application/json" } });
|
|
719
719
|
}
|
|
720
720
|
return new Response("[]", { status: 200 });
|
|
@@ -752,7 +752,7 @@ describe("AgentDefRegistry — lifecycle", () => {
|
|
|
752
752
|
const fetchFn = (async (url: string | URL | Request) => {
|
|
753
753
|
const u = String(url);
|
|
754
754
|
if (u.includes("/vault/default/")) return new Response("boom", { status: 500 });
|
|
755
|
-
if (u.includes("/vault/research/") && u.includes("tag
|
|
755
|
+
if (u.includes("/vault/research/") && u.includes("tag=agent%2Fdefinition")) {
|
|
756
756
|
return new Response(JSON.stringify([{ id: "r1", content: "role", metadata: { name: "r" } }]), {
|
|
757
757
|
status: 200,
|
|
758
758
|
});
|
|
@@ -1217,7 +1217,7 @@ describe("AgentDefRegistry — grant-GC reconcile (#96)", () => {
|
|
|
1217
1217
|
);
|
|
1218
1218
|
}
|
|
1219
1219
|
}
|
|
1220
|
-
if (u.includes("/api/notes?") && u.includes("tag
|
|
1220
|
+
if (u.includes("/api/notes?") && u.includes("tag=agent%2Fdefinition")) {
|
|
1221
1221
|
return new Response(JSON.stringify(present), { status: 200, headers: { "content-type": "application/json" } });
|
|
1222
1222
|
}
|
|
1223
1223
|
return new Response("[]", { status: 200 });
|
|
@@ -1309,7 +1309,7 @@ describe("AgentDefRegistry — grant-GC reconcile (#96)", () => {
|
|
|
1309
1309
|
reconciled.push({ agent: body.agent, liveConnections: body.liveConnections });
|
|
1310
1310
|
return new Response(JSON.stringify({ pruned: 0 }), { status: 200 });
|
|
1311
1311
|
}
|
|
1312
|
-
if (u.includes("/api/notes?") && u.includes("tag
|
|
1312
|
+
if (u.includes("/api/notes?") && u.includes("tag=agent%2Fdefinition")) {
|
|
1313
1313
|
return new Response(JSON.stringify(present), { status: 200, headers: { "content-type": "application/json" } });
|
|
1314
1314
|
}
|
|
1315
1315
|
return new Response("[]", { status: 200 });
|
|
@@ -1339,7 +1339,7 @@ describe("AgentDefRegistry — grant-GC reconcile (#96)", () => {
|
|
|
1339
1339
|
reconciled.push({ agent: body.agent, liveConnections: body.liveConnections });
|
|
1340
1340
|
return new Response(JSON.stringify({ pruned: 0 }), { status: 200 });
|
|
1341
1341
|
}
|
|
1342
|
-
if (u.includes("/api/notes?") && u.includes("tag
|
|
1342
|
+
if (u.includes("/api/notes?") && u.includes("tag=agent%2Fdefinition")) {
|
|
1343
1343
|
if (listShouldFail) return new Response("boom", { status: 500 });
|
|
1344
1344
|
return new Response(JSON.stringify(present), { status: 200, headers: { "content-type": "application/json" } });
|
|
1345
1345
|
}
|
|
@@ -1408,7 +1408,7 @@ function vaultFetchWithDelete(opts: {
|
|
|
1408
1408
|
return new Response(status >= 400 ? "delete failed" : null, { status });
|
|
1409
1409
|
}
|
|
1410
1410
|
if (method === "PATCH") return new Response(null, { status: 200 });
|
|
1411
|
-
if (u.includes("/api/notes?") && u.includes("tag
|
|
1411
|
+
if (u.includes("/api/notes?") && u.includes("tag=agent%2Fdefinition")) {
|
|
1412
1412
|
return new Response(JSON.stringify(opts.defs), {
|
|
1413
1413
|
status: 200,
|
|
1414
1414
|
headers: { "content-type": "application/json" },
|
package/src/agents.test.ts
CHANGED
|
@@ -77,9 +77,9 @@ describe("buildSpecFromBody", () => {
|
|
|
77
77
|
const spec = buildSpecFromBody({
|
|
78
78
|
name: "a",
|
|
79
79
|
channels: ["c"],
|
|
80
|
-
vault: { name: "default", access: "write", tags: ["
|
|
80
|
+
vault: { name: "default", access: "write", tags: ["agent/message"] },
|
|
81
81
|
});
|
|
82
|
-
expect(spec.vault).toEqual({ name: "default", access: "write", tags: ["
|
|
82
|
+
expect(spec.vault).toEqual({ name: "default", access: "write", tags: ["agent/message"] });
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
test("rejects a bad filesystem / network value", () => {
|
|
@@ -210,7 +210,7 @@ function specWithVault(name = "eng"): AgentSpec {
|
|
|
210
210
|
return {
|
|
211
211
|
name,
|
|
212
212
|
channels: [name],
|
|
213
|
-
vault: { name: "default", access: "read", tags: ["
|
|
213
|
+
vault: { name: "default", access: "read", tags: ["agent/message"] },
|
|
214
214
|
};
|
|
215
215
|
}
|
|
216
216
|
|
|
@@ -222,7 +222,7 @@ function specWithSystemPrompt(
|
|
|
222
222
|
return {
|
|
223
223
|
name,
|
|
224
224
|
channels: [name],
|
|
225
|
-
vault: { name: "default", access: "read", tags: ["
|
|
225
|
+
vault: { name: "default", access: "read", tags: ["agent/message"] },
|
|
226
226
|
systemPrompt: prompt,
|
|
227
227
|
...(mode ? { systemPromptMode: mode } : {}),
|
|
228
228
|
};
|
|
@@ -234,7 +234,7 @@ function specMultiThreaded(name = "eng"): AgentSpec {
|
|
|
234
234
|
name,
|
|
235
235
|
channels: [name],
|
|
236
236
|
mode: "multi-threaded",
|
|
237
|
-
vault: { name: "default", access: "read", tags: ["
|
|
237
|
+
vault: { name: "default", access: "read", tags: ["agent/message"] },
|
|
238
238
|
};
|
|
239
239
|
}
|
|
240
240
|
|
|
@@ -735,7 +735,7 @@ function specWithWorkspace(workspace: string, name = "eng"): AgentSpec {
|
|
|
735
735
|
return {
|
|
736
736
|
name,
|
|
737
737
|
channels: [name],
|
|
738
|
-
vault: { name: "default", access: "read", tags: ["
|
|
738
|
+
vault: { name: "default", access: "read", tags: ["agent/message"] },
|
|
739
739
|
workspace,
|
|
740
740
|
};
|
|
741
741
|
}
|
|
@@ -773,7 +773,7 @@ describe("ProgrammaticBackend.deliver — workspace seam: cwd = workspace, secre
|
|
|
773
773
|
const spec: AgentSpec = {
|
|
774
774
|
name: "eng",
|
|
775
775
|
channels: ["eng"],
|
|
776
|
-
vault: { name: "default", access: "read", tags: ["
|
|
776
|
+
vault: { name: "default", access: "read", tags: ["agent/message"] },
|
|
777
777
|
workspace: workspaceDir,
|
|
778
778
|
systemPrompt: "Work in the repo.",
|
|
779
779
|
};
|
|
@@ -309,7 +309,7 @@ describe("GET /api/agent-defs", () => {
|
|
|
309
309
|
fake.seed({
|
|
310
310
|
id: "Agents/uni-dev",
|
|
311
311
|
content: "a".repeat(500), // long prompt → preview truncates to 200.
|
|
312
|
-
tags: ["
|
|
312
|
+
tags: ["agent/definition"],
|
|
313
313
|
metadata: { name: "uni-dev", backend: "attached", mode: "multi-threaded" },
|
|
314
314
|
});
|
|
315
315
|
const { reg } = registryWithFakeVault({ fake });
|
|
@@ -351,7 +351,7 @@ describe("GET /api/agent-defs/:noteId (full def)", () => {
|
|
|
351
351
|
fake.seed({
|
|
352
352
|
id: "Agents/uni-dev",
|
|
353
353
|
content: "F".repeat(500), // long body → list preview truncates; full returns all.
|
|
354
|
-
tags: ["
|
|
354
|
+
tags: ["agent/definition"],
|
|
355
355
|
metadata: { name: "uni-dev", backend: "attached", mode: "multi-threaded", wants: "vault:research:read" },
|
|
356
356
|
});
|
|
357
357
|
const { reg } = registryWithFakeVault({ fake });
|
|
@@ -459,7 +459,7 @@ describe("POST /api/agent-defs", () => {
|
|
|
459
459
|
// The note was written to the fake vault, tagged the def tag, body = prompt.
|
|
460
460
|
const written = [...fake.notes.values()].find((n) => n.metadata.name === "newbot");
|
|
461
461
|
expect(written).toBeDefined();
|
|
462
|
-
expect(written!.tags).toContain("
|
|
462
|
+
expect(written!.tags).toContain("agent/definition");
|
|
463
463
|
expect(written!.content).toBe("You are newbot.");
|
|
464
464
|
expect(written!.metadata.backend).toBe("programmatic");
|
|
465
465
|
// It instantiated LIVE (the per-note reload ran ensureChannel + register) — not
|
|
@@ -555,7 +555,7 @@ describe("POST /api/agent-defs", () => {
|
|
|
555
555
|
fake.seed({
|
|
556
556
|
id: "Agents/uni-dev",
|
|
557
557
|
content: "p",
|
|
558
|
-
tags: ["
|
|
558
|
+
tags: ["agent/definition"],
|
|
559
559
|
metadata: { name: "uni-dev", backend: "programmatic" },
|
|
560
560
|
});
|
|
561
561
|
const { reg } = registryWithFakeVault({ fake });
|
|
@@ -586,7 +586,7 @@ describe("PATCH /api/agent-defs/:noteId", () => {
|
|
|
586
586
|
fake.seed({
|
|
587
587
|
id: "Agents/uni-dev",
|
|
588
588
|
content: "old prompt",
|
|
589
|
-
tags: ["
|
|
589
|
+
tags: ["agent/definition"],
|
|
590
590
|
metadata: { name: "uni-dev", backend: "programmatic" },
|
|
591
591
|
});
|
|
592
592
|
const { reg, calls } = registryWithFakeVault({ fake });
|
|
@@ -657,7 +657,7 @@ describe("DELETE /api/agent-defs/:noteId", () => {
|
|
|
657
657
|
fake.seed({
|
|
658
658
|
id: "Agents/uni-dev",
|
|
659
659
|
content: "p",
|
|
660
|
-
tags: ["
|
|
660
|
+
tags: ["agent/definition"],
|
|
661
661
|
metadata: { name: "uni-dev", backend: "programmatic" },
|
|
662
662
|
});
|
|
663
663
|
const { reg, calls } = registryWithFakeVault({ fake });
|
|
@@ -226,7 +226,7 @@ describe("GET /api/agents/<name>/env — composes all three sources + precedence
|
|
|
226
226
|
fake.seed({
|
|
227
227
|
id: "Agents/uni-dev",
|
|
228
228
|
content: "You are uni-dev.",
|
|
229
|
-
tags: ["
|
|
229
|
+
tags: ["agent/definition"],
|
|
230
230
|
metadata: { name: "uni-dev", backend: "programmatic", mode: "single-threaded", wants: "env:github" },
|
|
231
231
|
});
|
|
232
232
|
const grants = grantsClientWith({ github: githubGrantStatus });
|
|
@@ -308,7 +308,7 @@ describe("GET /api/agents/<name>/env — resilient when grants/hub unreachable",
|
|
|
308
308
|
fake.seed({
|
|
309
309
|
id: "Agents/uni-dev",
|
|
310
310
|
content: "You are uni-dev.",
|
|
311
|
-
tags: ["
|
|
311
|
+
tags: ["agent/definition"],
|
|
312
312
|
metadata: { name: "uni-dev", backend: "programmatic", mode: "single-threaded", wants: "env:github" },
|
|
313
313
|
});
|
|
314
314
|
// A grants client whose hub fetch always throws — registerGrant fails at instantiate,
|
|
@@ -130,7 +130,7 @@ function inboundBody(noteId: string, channel = "eng") {
|
|
|
130
130
|
id: noteId,
|
|
131
131
|
path: `channel/${channel}/${noteId}`,
|
|
132
132
|
content: "wake up session",
|
|
133
|
-
tags: ["
|
|
133
|
+
tags: ["agent/message", "agent/message/inbound"],
|
|
134
134
|
metadata: { channel, direction: "inbound", sender: "aaron" },
|
|
135
135
|
},
|
|
136
136
|
});
|
|
@@ -596,7 +596,7 @@ describe("C — AGENT_VAULT_TRIGGER_TEMPLATE exposed via /.parachute/config", ()
|
|
|
596
596
|
// re-registration updates the existing trigger in place. The TAG is
|
|
597
597
|
// #agent/message and the webhook is on the /agent mount.
|
|
598
598
|
expect(body.triggerTemplate.name).toBe("channel_inbound_<channel>");
|
|
599
|
-
expect(body.triggerTemplate.when.tags).toEqual(["
|
|
599
|
+
expect(body.triggerTemplate.when.tags).toEqual(["agent/message/inbound"]);
|
|
600
600
|
// CONTRACT: the predicate keys on the `agent` routing key (was `channel`).
|
|
601
601
|
expect(body.triggerTemplate.when.has_metadata).toEqual(["agent"]);
|
|
602
602
|
// The rendered-at marker NAME is unchanged (cosmetic/internal).
|
|
@@ -194,7 +194,7 @@ describe("POST /api/jobs — create + validation", () => {
|
|
|
194
194
|
expect(body.job.noteId).toBe("Channels/eng/jobs/x"); // vault note id for addressing
|
|
195
195
|
const post = calls.find((c) => c.url.endsWith("/api/notes") && c.init.method === "POST")!;
|
|
196
196
|
const sent = JSON.parse(String(post.init.body));
|
|
197
|
-
expect(sent.tags).toEqual(["
|
|
197
|
+
expect(sent.tags).toEqual(["agent/job"]);
|
|
198
198
|
expect(sent.content).toBe("do it"); // trimmed
|
|
199
199
|
expect(sent.metadata.enabled).toBe("true");
|
|
200
200
|
expect(sent.metadata.jobId).toBe("x");
|
|
@@ -224,7 +224,7 @@ describe("POST /api/jobs/:id/run — fire now", () => {
|
|
|
224
224
|
// The injected note is INBOUND with the #agent/message tags.
|
|
225
225
|
const inject = calls.find((c) => c.url.endsWith("/api/notes") && c.init.method === "POST")!;
|
|
226
226
|
const sent = JSON.parse(String(inject.init.body));
|
|
227
|
-
expect(sent.tags).toEqual(["
|
|
227
|
+
expect(sent.tags).toEqual(["agent/message", "agent/message/inbound"]);
|
|
228
228
|
expect(sent.metadata.sender).toBe("runner:Channels/eng/jobs/x");
|
|
229
229
|
} finally { srv.stop(true); }
|
|
230
230
|
});
|
package/src/daemon.test.ts
CHANGED
|
@@ -413,7 +413,7 @@ describe("Vault inbound webhook — POST /api/vault/inbound", () => {
|
|
|
413
413
|
function body(
|
|
414
414
|
noteId: string,
|
|
415
415
|
extraMeta: Record<string, unknown> = {},
|
|
416
|
-
tags: string[] = ["
|
|
416
|
+
tags: string[] = ["agent/message", "agent/message/inbound"],
|
|
417
417
|
) {
|
|
418
418
|
return JSON.stringify({
|
|
419
419
|
trigger: "channel-inbound",
|
|
@@ -546,7 +546,7 @@ describe("Vault inbound webhook — POST /api/vault/inbound", () => {
|
|
|
546
546
|
note: {
|
|
547
547
|
id: "agent-only-1",
|
|
548
548
|
content: "wake up via agent key",
|
|
549
|
-
tags: ["
|
|
549
|
+
tags: ["agent/message", "agent/message/inbound"],
|
|
550
550
|
// NO `channel` field — the routing key is carried ONLY under the new `agent` alias.
|
|
551
551
|
metadata: { agent: "eng", direction: "inbound", sender: "aaron" },
|
|
552
552
|
},
|
|
@@ -571,7 +571,7 @@ describe("Vault inbound webhook — POST /api/vault/inbound", () => {
|
|
|
571
571
|
note: {
|
|
572
572
|
id: "channel-only-1",
|
|
573
573
|
content: "wake up via legacy channel key",
|
|
574
|
-
tags: ["
|
|
574
|
+
tags: ["agent/message", "agent/message/inbound"],
|
|
575
575
|
// ONLY the legacy `channel` field — a pre-expand writer; must still route.
|
|
576
576
|
metadata: { channel: "eng", direction: "inbound", sender: "aaron" },
|
|
577
577
|
},
|
|
@@ -618,13 +618,13 @@ describe("Vault inbound webhook — POST /api/vault/inbound", () => {
|
|
|
618
618
|
}
|
|
619
619
|
});
|
|
620
620
|
|
|
621
|
-
test("
|
|
621
|
+
test("agent/message/outbound-tagged note is ack'd 200 but NOT emitted (belt-and-suspenders)", async () => {
|
|
622
622
|
const { srv, base, emitted } = buildVaultServer();
|
|
623
623
|
try {
|
|
624
624
|
const res = await fetch(`${base}/api/vault/inbound?secret=${SECRET}`, {
|
|
625
625
|
method: "POST",
|
|
626
626
|
headers: { "content-type": "application/json" },
|
|
627
|
-
body: body("ob-1", { direction: "outbound" }, ["
|
|
627
|
+
body: body("ob-1", { direction: "outbound" }, ["agent/message", "agent/message/outbound"]),
|
|
628
628
|
});
|
|
629
629
|
expect(res.status).toBe(200);
|
|
630
630
|
expect(emitted).toHaveLength(0);
|
package/src/jobs.test.ts
CHANGED
|
@@ -126,7 +126,7 @@ describe("VaultJobStore — vault-native CRUD", () => {
|
|
|
126
126
|
const jobs = await store.listAll();
|
|
127
127
|
// Only ONE vault transport in the map → queried exactly once (telegram is skipped).
|
|
128
128
|
expect(urls.filter((u) => u.includes("/api/notes")).length).toBe(1);
|
|
129
|
-
expect(urls[0]).toContain("tag
|
|
129
|
+
expect(urls[0]).toContain("tag=agent%2Fjob");
|
|
130
130
|
expect(jobs).toHaveLength(2);
|
|
131
131
|
expect(jobs[0]).toMatchObject({
|
|
132
132
|
id: "note-1",
|
|
@@ -190,7 +190,7 @@ describe("VaultJobStore — vault-native CRUD", () => {
|
|
|
190
190
|
expect(saved.noteId).toBe("Channels/uni-dev/jobs/morning");
|
|
191
191
|
expect(calls).toHaveLength(1);
|
|
192
192
|
const body = JSON.parse(String(calls[0]!.init.body));
|
|
193
|
-
expect(body.tags).toEqual(["
|
|
193
|
+
expect(body.tags).toEqual(["agent/job"]);
|
|
194
194
|
expect(body.path).toBe("Channels/uni-dev/jobs/morning");
|
|
195
195
|
expect(body.metadata.jobId).toBe("morning"); // slug persisted for stable display
|
|
196
196
|
expect(body.content).toBe("Run the morning weave");
|
package/src/mint-token.test.ts
CHANGED
|
@@ -68,12 +68,12 @@ describe("mintScopedToken — happy path", () => {
|
|
|
68
68
|
{
|
|
69
69
|
scope: "vault:default:read",
|
|
70
70
|
audience: "vault.default",
|
|
71
|
-
permissions: { scoped_tags: ["
|
|
71
|
+
permissions: { scoped_tags: ["agent/message"] },
|
|
72
72
|
},
|
|
73
73
|
depsWith(hub.fetchFn),
|
|
74
74
|
);
|
|
75
75
|
expect(hub.calls[0]!.body.audience).toBe("vault.default");
|
|
76
|
-
expect(hub.calls[0]!.body.permissions).toEqual({ scoped_tags: ["
|
|
76
|
+
expect(hub.calls[0]!.body.permissions).toEqual({ scoped_tags: ["agent/message"] });
|
|
77
77
|
});
|
|
78
78
|
});
|
|
79
79
|
|
|
@@ -74,7 +74,7 @@ describe("module.json — modular-UI declaration", () => {
|
|
|
74
74
|
expect(tmpl?.requestedBy).toBe("agent");
|
|
75
75
|
expect(tmpl?.source?.module).toBe("vault");
|
|
76
76
|
expect(tmpl?.source?.event).toBe("note.created");
|
|
77
|
-
expect(tmpl?.source?.filter?.tags).toContain("
|
|
77
|
+
expect(tmpl?.source?.filter?.tags).toContain("agent/message/inbound");
|
|
78
78
|
expect(tmpl?.sink?.module).toBe("agent");
|
|
79
79
|
expect(tmpl?.sink?.action).toBe("message.deliver");
|
|
80
80
|
// It's PARAMETERIZED — the operator picks the vault + names the channel.
|
|
@@ -128,7 +128,7 @@ describe("module.json — modular-UI declaration", () => {
|
|
|
128
128
|
expect(tmpl?.requestedBy).toBe("agent");
|
|
129
129
|
expect(tmpl?.source?.module).toBe("vault");
|
|
130
130
|
// Filters on the def tag — and ONLY that tag (no inbound-message keys).
|
|
131
|
-
expect(tmpl?.source?.filter?.tags).toEqual(["
|
|
131
|
+
expect(tmpl?.source?.filter?.tags).toEqual(["agent/definition"]);
|
|
132
132
|
expect(tmpl?.sink?.module).toBe("agent");
|
|
133
133
|
expect(tmpl?.sink?.action).toBe("definition.reload");
|
|
134
134
|
// Parameterized: the operator picks which def-vault. No channel param
|
|
@@ -264,7 +264,7 @@ describe("inbound for a programmatic channel → deliver → outbound note", ()
|
|
|
264
264
|
note: {
|
|
265
265
|
id: noteId,
|
|
266
266
|
content,
|
|
267
|
-
tags: ["
|
|
267
|
+
tags: ["agent/message", "agent/message/inbound"],
|
|
268
268
|
metadata: { channel, direction: "inbound", sender: "aaron", ts: "2026-06-16T00:00:01Z" },
|
|
269
269
|
},
|
|
270
270
|
}),
|
|
@@ -356,7 +356,7 @@ describe("inbound for an EXPECTED-but-not-yet-registered channel → queued pend
|
|
|
356
356
|
note: {
|
|
357
357
|
id: noteId,
|
|
358
358
|
content,
|
|
359
|
-
tags: ["
|
|
359
|
+
tags: ["agent/message", "agent/message/inbound"],
|
|
360
360
|
metadata: { channel, direction: "inbound", sender: "aaron", ts: "2026-06-16T00:00:01Z" },
|
|
361
361
|
},
|
|
362
362
|
}),
|
|
@@ -16,7 +16,7 @@ describe("parseArgs — name + channels + vault + egress + mounts → the right
|
|
|
16
16
|
"--channel",
|
|
17
17
|
"ops:read",
|
|
18
18
|
"--vault",
|
|
19
|
-
"default:read
|
|
19
|
+
"default:read:agent/message,#decision",
|
|
20
20
|
"--egress",
|
|
21
21
|
"registry.npmjs.org,github.com",
|
|
22
22
|
"--mount",
|
|
@@ -38,7 +38,7 @@ describe("parseArgs — name + channels + vault + egress + mounts → the right
|
|
|
38
38
|
expect(spec!.vault).toEqual({
|
|
39
39
|
name: "default",
|
|
40
40
|
access: "read",
|
|
41
|
-
tags: ["
|
|
41
|
+
tags: ["agent/message", "#decision"],
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
// Egress: additive host list, comma-split.
|
package/src/spawn-agent.test.ts
CHANGED
|
@@ -448,7 +448,7 @@ describe("spawnAgent — full wiring with stubs (no real token)", () => {
|
|
|
448
448
|
const spec: AgentSpec = {
|
|
449
449
|
name: "aaron-dev",
|
|
450
450
|
channels: ["aaron-dev"],
|
|
451
|
-
vault: { name: "default", access: "read", tags: ["
|
|
451
|
+
vault: { name: "default", access: "read", tags: ["agent/message"] },
|
|
452
452
|
network: "restricted", // exercise the egress floor; scoped reads are the default (step 6)
|
|
453
453
|
};
|
|
454
454
|
const res = await spawnAgent(spec, baseDeps({ tmux, sandboxEngine: engine }));
|
|
@@ -570,13 +570,13 @@ describe("spawnAgent — full wiring with stubs (no real token)", () => {
|
|
|
570
570
|
const spec: AgentSpec = {
|
|
571
571
|
name: "weaver",
|
|
572
572
|
channels: ["c"],
|
|
573
|
-
vault: { name: "default", access: "read", tags: ["
|
|
573
|
+
vault: { name: "default", access: "read", tags: ["agent/message"] },
|
|
574
574
|
};
|
|
575
575
|
await spawnAgent(spec, baseDeps({ fetchFn }));
|
|
576
576
|
const vaultCall = calls.find((c) => String(c.scope).startsWith("vault:"));
|
|
577
577
|
expect(vaultCall).toBeDefined();
|
|
578
578
|
expect(vaultCall!.scope).toBe("vault:default:read");
|
|
579
|
-
expect(vaultCall!.permissions).toEqual({ scoped_tags: ["
|
|
579
|
+
expect(vaultCall!.permissions).toEqual({ scoped_tags: ["agent/message"] });
|
|
580
580
|
});
|
|
581
581
|
|
|
582
582
|
test("idempotent: an already-running session is a no-op", async () => {
|
|
@@ -674,7 +674,7 @@ describe("spawnAgent — full wiring with stubs (no real token)", () => {
|
|
|
674
674
|
const spec: AgentSpec = {
|
|
675
675
|
name: "weaver",
|
|
676
676
|
channels: [{ name: "weave", access: "read" }],
|
|
677
|
-
vault: { name: "default", access: "read", tags: ["
|
|
677
|
+
vault: { name: "default", access: "read", tags: ["agent/message"] },
|
|
678
678
|
network: "restricted",
|
|
679
679
|
egress: ["registry.npmjs.org"],
|
|
680
680
|
};
|