agent-comms 4.2.0 → 4.4.0

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.
Files changed (50) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +6 -1
  4. package/dist/bridges/user/web/dist/mesh-worker.js +1 -1
  5. package/dist/bridges/user/web/dist/sw.js +1 -1
  6. package/dist/core/bridge.d.ts +4 -0
  7. package/dist/core/bridge.d.ts.map +1 -1
  8. package/dist/core/bridge.js +24 -1
  9. package/dist/core/bridge.js.map +1 -1
  10. package/dist/core/dm-grant-actions.d.ts +20 -0
  11. package/dist/core/dm-grant-actions.d.ts.map +1 -0
  12. package/dist/core/dm-grant-actions.js +54 -0
  13. package/dist/core/dm-grant-actions.js.map +1 -0
  14. package/dist/core/identity-store.d.ts +16 -0
  15. package/dist/core/identity-store.d.ts.map +1 -1
  16. package/dist/core/identity-store.js +5 -3
  17. package/dist/core/identity-store.js.map +1 -1
  18. package/dist/core/mesh-store.d.ts +4 -0
  19. package/dist/core/mesh-store.d.ts.map +1 -1
  20. package/dist/core/mesh-store.js +11 -0
  21. package/dist/core/mesh-store.js.map +1 -1
  22. package/dist/core/room-lifecycle.d.ts +6 -0
  23. package/dist/core/room-lifecycle.d.ts.map +1 -1
  24. package/dist/core/room-lifecycle.js +52 -1
  25. package/dist/core/room-lifecycle.js.map +1 -1
  26. package/dist/core/token-text.d.ts +9 -0
  27. package/dist/core/token-text.d.ts.map +1 -0
  28. package/dist/core/token-text.js +24 -0
  29. package/dist/core/token-text.js.map +1 -0
  30. package/dist/core/tool.d.ts +9 -0
  31. package/dist/core/tool.d.ts.map +1 -1
  32. package/dist/core/tool.js +9 -0
  33. package/dist/core/tool.js.map +1 -1
  34. package/dist/core/types.d.ts +22 -0
  35. package/dist/core/types.d.ts.map +1 -1
  36. package/dist/core/types.js +12 -0
  37. package/dist/core/types.js.map +1 -1
  38. package/dist/test/dm-grant-actions.unit.test.d.ts +2 -0
  39. package/dist/test/dm-grant-actions.unit.test.d.ts.map +1 -0
  40. package/dist/test/dm-grant-actions.unit.test.js +78 -0
  41. package/dist/test/dm-grant-actions.unit.test.js.map +1 -0
  42. package/dist/test/dm-grant-tool.integration.test.d.ts +5 -0
  43. package/dist/test/dm-grant-tool.integration.test.d.ts.map +1 -0
  44. package/dist/test/dm-grant-tool.integration.test.js +257 -0
  45. package/dist/test/dm-grant-tool.integration.test.js.map +1 -0
  46. package/dist/test/token-text.unit.test.d.ts +2 -0
  47. package/dist/test/token-text.unit.test.d.ts.map +1 -0
  48. package/dist/test/token-text.unit.test.js +46 -0
  49. package/dist/test/token-text.unit.test.js.map +1 -0
  50. package/package.json +1 -1
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Integration test for pre-authorised DMs through the tool surface: the receiver admits the sender (dm_admit), the sender presents the grant (dm_use_grant), and the first DM is delivered with no decision at the receiving end. A grant minted for someone else, or revoked, is refused.
3
+ */
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { tmpdir } from "node:os";
7
+ import { test, expect } from "vitest";
8
+ import { MeshStore } from "../core/mesh-store.js";
9
+ import { CommsTool } from "../core/tool.js";
10
+ import { dmRoomPath } from "../core/room-path.js";
11
+ import { waitFor, wireTestTransport } from "./test-transport.js";
12
+ let nextPort = 25_450;
13
+ function freshPort() {
14
+ nextPort += 1;
15
+ return nextPort;
16
+ }
17
+ async function party(port, name) {
18
+ const store = new MeshStore({ coordinatorPort: port });
19
+ await wireTestTransport(store);
20
+ await store.init();
21
+ await store.registerAgent({
22
+ name,
23
+ harness: "test",
24
+ cwd: `/test/${name}`,
25
+ pid: process.pid,
26
+ visibility: "visible",
27
+ tags: [],
28
+ });
29
+ return {
30
+ store,
31
+ tool: new CommsTool(store),
32
+ ctx: {
33
+ agentId: store.peerId,
34
+ harness: "test",
35
+ cwd: `/test/${name}`,
36
+ pid: process.pid,
37
+ },
38
+ };
39
+ }
40
+ async function connectedPair() {
41
+ const port = freshPort();
42
+ const sender = await party(port, "sender");
43
+ const receiver = await party(port, "receiver");
44
+ await waitFor(() => sender.store.serialise().agents[receiver.store.peerId] !== undefined, "the sender sees the receiver");
45
+ return { sender, receiver };
46
+ }
47
+ /** The grant text in a dm_admit result: its last line. */
48
+ function grantFrom(content) {
49
+ const line = content.split("\n").at(-1);
50
+ if (line === undefined || line === "")
51
+ throw new Error("no grant in result");
52
+ return line;
53
+ }
54
+ test("a grant lets the sender's first DM through with no decision at the receiver", async () => {
55
+ const { sender, receiver } = await connectedPair();
56
+ try {
57
+ const admitted = await receiver.tool.handle(receiver.ctx, {
58
+ action: "dm_admit",
59
+ target: sender.store.peerId,
60
+ });
61
+ expect(admitted.isError).toBe(false);
62
+ expect(admitted.content).toContain(`dm_use_grant with target ${receiver.store.peerId}`);
63
+ const used = await sender.tool.handle(sender.ctx, {
64
+ action: "dm_use_grant",
65
+ target: receiver.store.peerId,
66
+ grant: grantFrom(admitted.content),
67
+ });
68
+ expect(used.isError).toBe(false);
69
+ expect(receiver.store.listPendingRoomJoins()).toEqual([]);
70
+ const sent = await sender.tool.handle(sender.ctx, {
71
+ action: "dm",
72
+ target: receiver.store.peerId,
73
+ content: "no decision needed",
74
+ });
75
+ expect(sent.isError).toBe(false);
76
+ const dmPath = dmRoomPath(sender.store.peerId, receiver.store.peerId);
77
+ await waitFor(() => (receiver.store.serialise().dms[dmPath] ?? []).some((m) => m.content === "no decision needed"), "the receiver gets the DM");
78
+ expect(receiver.store.listPendingRoomJoins()).toEqual([]);
79
+ }
80
+ finally {
81
+ await receiver.store.shutdown();
82
+ await sender.store.shutdown();
83
+ }
84
+ });
85
+ test("a grant minted for a different device is refused", async () => {
86
+ const { sender, receiver } = await connectedPair();
87
+ try {
88
+ const someoneElse = "c".repeat(sender.store.peerId.length);
89
+ const admitted = await receiver.tool.handle(receiver.ctx, {
90
+ action: "dm_admit",
91
+ target: someoneElse,
92
+ });
93
+ const used = await sender.tool.handle(sender.ctx, {
94
+ action: "dm_use_grant",
95
+ target: receiver.store.peerId,
96
+ grant: grantFrom(admitted.content),
97
+ });
98
+ expect(used.isError).toBe(true);
99
+ expect(used.content).toMatch(/not issued to this device or its user/);
100
+ }
101
+ finally {
102
+ await receiver.store.shutdown();
103
+ await sender.store.shutdown();
104
+ }
105
+ });
106
+ test("a revoked grant is refused", async () => {
107
+ const { sender, receiver } = await connectedPair();
108
+ try {
109
+ const admitted = await receiver.tool.handle(receiver.ctx, {
110
+ action: "dm_admit",
111
+ target: sender.store.peerId,
112
+ });
113
+ const revoked = await receiver.tool.handle(receiver.ctx, {
114
+ action: "dm_revoke",
115
+ target: sender.store.peerId,
116
+ });
117
+ expect(revoked.isError).toBe(false);
118
+ const used = await sender.tool.handle(sender.ctx, {
119
+ action: "dm_use_grant",
120
+ target: receiver.store.peerId,
121
+ grant: grantFrom(admitted.content),
122
+ });
123
+ expect(used.isError).toBe(true);
124
+ expect(used.content).toMatch(/refused/);
125
+ }
126
+ finally {
127
+ await receiver.store.shutdown();
128
+ await sender.store.shutdown();
129
+ }
130
+ });
131
+ test("dm_use_grant with text that is not a grant fails without contacting the receiver", async () => {
132
+ const { sender, receiver } = await connectedPair();
133
+ try {
134
+ const used = await sender.tool.handle(sender.ctx, {
135
+ action: "dm_use_grant",
136
+ target: receiver.store.peerId,
137
+ grant: "this is not a grant",
138
+ });
139
+ expect(used.isError).toBe(true);
140
+ expect(used.content).toMatch(/not a valid capability token/);
141
+ expect(receiver.store.listPendingRoomJoins()).toEqual([]);
142
+ }
143
+ finally {
144
+ await receiver.store.shutdown();
145
+ await sender.store.shutdown();
146
+ }
147
+ });
148
+ /** A party whose user principal is fixed by the caller, so two of them can be two devices of one person. */
149
+ async function partyOfUser(port, name, userIdentityDir) {
150
+ const store = new MeshStore({ coordinatorPort: port });
151
+ await wireTestTransport(store, {
152
+ userIdentityOptions: { dir: userIdentityDir },
153
+ });
154
+ await store.init();
155
+ await store.registerAgent({
156
+ name,
157
+ harness: "test",
158
+ cwd: `/test/${name}`,
159
+ pid: process.pid,
160
+ visibility: "visible",
161
+ tags: [],
162
+ });
163
+ return {
164
+ store,
165
+ tool: new CommsTool(store),
166
+ ctx: {
167
+ agentId: store.peerId,
168
+ harness: "test",
169
+ cwd: `/test/${name}`,
170
+ pid: process.pid,
171
+ },
172
+ };
173
+ }
174
+ /** The principal id in a whoami result. */
175
+ function principalFrom(content) {
176
+ const line = content.split("\n").find((l) => l.startsWith("Principal: "));
177
+ if (line === undefined)
178
+ throw new Error("whoami reported no principal");
179
+ return line.slice("Principal: ".length);
180
+ }
181
+ test("whoami reports the user principal, so a person can hand it to someone who wants to admit them", async () => {
182
+ const { sender } = await connectedPair();
183
+ try {
184
+ const whoami = await sender.tool.handle(sender.ctx, { action: "whoami" });
185
+ expect(whoami.content).toMatch(/^Principal: [0-9a-f]{64}$/m);
186
+ expect(whoami.content).not.toContain(`Principal: ${sender.store.peerId}`);
187
+ }
188
+ finally {
189
+ await sender.store.shutdown();
190
+ }
191
+ });
192
+ test("admitting a person's principal lets every one of their devices DM with no decision, from the same grant text", async () => {
193
+ const port = freshPort();
194
+ const receiver = await party(port, "receiver");
195
+ const dir = fs.mkdtempSync(path.join(tmpdir(), "dm-grant-user-"));
196
+ const first = await partyOfUser(port, "first-device", dir);
197
+ const second = await partyOfUser(port, "second-device", dir);
198
+ try {
199
+ const principal = principalFrom((await first.tool.handle(first.ctx, { action: "whoami" })).content);
200
+ expect(principalFrom((await second.tool.handle(second.ctx, { action: "whoami" })).content)).toBe(principal);
201
+ const admitted = await receiver.tool.handle(receiver.ctx, {
202
+ action: "dm_admit",
203
+ target: principal,
204
+ principal: true,
205
+ });
206
+ expect(admitted.isError).toBe(false);
207
+ const grant = grantFrom(admitted.content);
208
+ for (const device of [first, second]) {
209
+ const used = await device.tool.handle(device.ctx, {
210
+ action: "dm_use_grant",
211
+ target: receiver.store.peerId,
212
+ grant,
213
+ });
214
+ expect(used.isError, used.content).toBe(false);
215
+ expect(receiver.store.listPendingRoomJoins()).toEqual([]);
216
+ const sent = await device.tool.handle(device.ctx, {
217
+ action: "dm",
218
+ target: receiver.store.peerId,
219
+ content: `hello from ${device.ctx.cwd}`,
220
+ });
221
+ expect(sent.isError, sent.content).toBe(false);
222
+ }
223
+ }
224
+ finally {
225
+ await second.store.shutdown();
226
+ await first.store.shutdown();
227
+ await receiver.store.shutdown();
228
+ fs.rmSync(dir, { recursive: true, force: true });
229
+ }
230
+ });
231
+ test("a principal-level grant is refused when presented from a device of some other user", async () => {
232
+ const port = freshPort();
233
+ const receiver = await party(port, "receiver");
234
+ const owner = await party(port, "owner");
235
+ const stranger = await party(port, "stranger");
236
+ try {
237
+ const ownerPrincipal = principalFrom((await owner.tool.handle(owner.ctx, { action: "whoami" })).content);
238
+ const admitted = await receiver.tool.handle(receiver.ctx, {
239
+ action: "dm_admit",
240
+ target: ownerPrincipal,
241
+ principal: true,
242
+ });
243
+ const used = await stranger.tool.handle(stranger.ctx, {
244
+ action: "dm_use_grant",
245
+ target: receiver.store.peerId,
246
+ grant: grantFrom(admitted.content),
247
+ });
248
+ expect(used.isError).toBe(true);
249
+ expect(used.content).toMatch(/not issued to this device or its user/);
250
+ }
251
+ finally {
252
+ await stranger.store.shutdown();
253
+ await owner.store.shutdown();
254
+ await receiver.store.shutdown();
255
+ }
256
+ });
257
+ //# sourceMappingURL=dm-grant-tool.integration.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dm-grant-tool.integration.test.js","sourceRoot":"","sources":["../../src/test/dm-grant-tool.integration.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=token-text.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-text.unit.test.d.ts","sourceRoot":"","sources":["../../src/test/token-text.unit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Unit tests for the text form of a capability token, used to hand a dm:send grant from one person to another as a plain string.
3
+ */
4
+ import { describe, expect, it } from "vitest";
5
+ import { decodeTokenText, encodeTokenText } from "../core/token-text.js";
6
+ const bytes = (text) => Uint8Array.from(new TextEncoder().encode(text));
7
+ // A structurally valid stand-in: the codec only serialises a token's byte-string fields, it never verifies one.
8
+ const TOKEN = [
9
+ bytes("protected"),
10
+ { "4": bytes("key-id") },
11
+ bytes("payload"),
12
+ bytes("signature"),
13
+ ];
14
+ /** Long enough to be plausible text, short enough that it cannot be the whole token. */
15
+ const TRUNCATED_LENGTH = 10;
16
+ describe("token text", () => {
17
+ it("round-trips a token exactly", () => {
18
+ expect(decodeTokenText(encodeTokenText(TOKEN))).toEqual(TOKEN);
19
+ });
20
+ it("round-trips a token with no payload", () => {
21
+ const detached = [
22
+ bytes("protected"),
23
+ {},
24
+ null,
25
+ bytes("signature"),
26
+ ];
27
+ expect(decodeTokenText(encodeTokenText(detached))).toEqual(detached);
28
+ });
29
+ it("encodes to a single line with no whitespace, safe to paste into a message", () => {
30
+ expect(encodeTokenText(TOKEN)).toMatch(/^\S+$/);
31
+ });
32
+ it("tolerates whitespace around a pasted token", () => {
33
+ expect(decodeTokenText(` ${encodeTokenText(TOKEN)}\n`)).toEqual(TOKEN);
34
+ });
35
+ it("rejects text that is not a token, with a message naming what was wrong", () => {
36
+ for (const text of [
37
+ "",
38
+ "not a token",
39
+ "e30",
40
+ encodeTokenText(TOKEN).slice(0, TRUNCATED_LENGTH),
41
+ ]) {
42
+ expect(() => decodeTokenText(text)).toThrow(/not a valid capability token/);
43
+ }
44
+ });
45
+ });
46
+ //# sourceMappingURL=token-text.unit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-text.unit.test.js","sourceRoot":"","sources":["../../src/test/token-text.unit.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEzE,MAAM,KAAK,GAAG,CAAC,IAAY,EAA2B,EAAE,CACtD,UAAU,CAAC,IAAI,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AAElD,gHAAgH;AAChH,MAAM,KAAK,GAAoB;IAC7B,KAAK,CAAC,WAAW,CAAC;IAClB,EAAE,GAAG,EAAE,KAAK,CAAC,QAAQ,CAAC,EAAE;IACxB,KAAK,CAAC,SAAS,CAAC;IAChB,KAAK,CAAC,WAAW,CAAC;CACnB,CAAC;AAEF,wFAAwF;AACxF,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,eAAe,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,QAAQ,GAAoB;YAChC,KAAK,CAAC,WAAW,CAAC;YAClB,EAAE;YACF,IAAI;YACJ,KAAK,CAAC,WAAW,CAAC;SACnB,CAAC;QACF,MAAM,CAAC,eAAe,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2EAA2E,EAAE,GAAG,EAAE;QACnF,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,eAAe,CAAC,KAAK,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wEAAwE,EAAE,GAAG,EAAE;QAChF,KAAK,MAAM,IAAI,IAAI;YACjB,EAAE;YACF,aAAa;YACb,KAAK;YACL,eAAe,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC;SAClD,EAAE,CAAC;YACF,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CACzC,8BAA8B,CAC/B,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-comms",
3
3
  "description": "Cross-harness communication mesh for LLM agents — rooms, DMs, presence, and real-time push delivery over TCP",
4
- "version": "4.2.0",
4
+ "version": "4.4.0",
5
5
  "bin": {
6
6
  "agent-comms": "dist/cli.js"
7
7
  },