a2a-exposed 0.8.17 → 0.8.18

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.
@@ -0,0 +1,161 @@
1
+ // The inbox lists and deletes the push configs stored on a task, for the calling peer only.
2
+ // Run: npm test
3
+ import { test } from "node:test";
4
+ import assert from "node:assert/strict";
5
+ import worker from "../src/index.ts";
6
+ import { sha256 } from "../src/a2a.ts";
7
+ import { d1 } from "./d1.ts";
8
+
9
+ const BASE = "https://agent.example.com";
10
+
11
+ function setup() {
12
+ const DB = d1(new URL("../migrations/", import.meta.url));
13
+ const env: any = {
14
+ DB, PUBLIC_URL: BASE, OWNER_TOKEN: "owner-secret", AGENT_NAME: "Test Inbox", WAKE_PRESET: "generic",
15
+ };
16
+ const logs: string[] = [];
17
+ const secrets: string[] = [];
18
+ const orig = console.log;
19
+ console.log = (...args: unknown[]) => {
20
+ const line = args.map((a) => String(a)).join(" ");
21
+ logs.push(line);
22
+ if (secrets.some((secret) => secret && line.includes(secret))) return;
23
+ orig(...args);
24
+ };
25
+ const call = async (token: string, method: string, params: unknown, headers: Record<string, string> = {}) => {
26
+ const res = await worker.fetch(new Request(BASE + "/", {
27
+ method: "POST",
28
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json", ...headers },
29
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
30
+ }) as any, env, { waitUntil() {}, passThroughOnException() {} } as any);
31
+ const text = await res.text();
32
+ let data: any = text;
33
+ try { data = text ? JSON.parse(text) : null; } catch { /* not json */ }
34
+ return { status: res.status, data, text };
35
+ };
36
+ const owner = async (method: string, path: string, json?: unknown) => {
37
+ const res = await worker.fetch(new Request(BASE + path, {
38
+ method, headers: { authorization: "Bearer owner-secret", "content-type": "application/json" },
39
+ body: json === undefined ? undefined : JSON.stringify(json),
40
+ }) as any, env, { waitUntil() {}, passThroughOnException() {} } as any);
41
+ return res.json() as Promise<any>;
42
+ };
43
+ return {
44
+ DB, call, owner, logs, secrets,
45
+ restore: () => { console.log = orig; },
46
+ };
47
+ }
48
+
49
+ function hides(body: string, secret: string) {
50
+ assert.equal(body.includes(secret), false);
51
+ }
52
+
53
+ test("list and delete return the caller's push config and hide it from other peers", async (t) => {
54
+ const s = setup();
55
+ t.after(s.restore);
56
+ const ada = (await s.owner("POST", "/owner/peers", { label: "ada" })).token as string;
57
+ const bob = (await s.owner("POST", "/owner/peers", { label: "bob" })).token as string;
58
+ const token = `pt_${crypto.randomUUID()}`;
59
+ s.secrets.push(token);
60
+ const tokenHash = await sha256(token);
61
+ const sent = await s.call(ada, "message/send", {
62
+ message: { kind: "message", role: "user", messageId: crypto.randomUUID(), parts: [{ kind: "text", text: "hello" }] },
63
+ });
64
+ assert.equal(sent.status, 200);
65
+ const taskId = sent.data.result.id as string;
66
+ const set = await s.call(ada, "tasks/pushNotificationConfig/set", {
67
+ taskId, pushNotificationConfig: { id: "cfg-1", url: "https://hooks.example.net/ada", token },
68
+ });
69
+ assert.equal(set.status, 200);
70
+ assert.equal(set.data.result.taskId, taskId);
71
+ assert.equal(await sha256(String(set.data.result.pushNotificationConfig.token)), tokenHash);
72
+
73
+ const listed = await s.call(ada, "ListTaskPushNotificationConfig", { taskId });
74
+ assert.equal(listed.status, 200);
75
+ assert.equal(listed.data.result.nextPageToken, "");
76
+ assert.equal(listed.data.result.configs.length, 1);
77
+ assert.equal(listed.data.result.configs[0].id, "cfg-1");
78
+ assert.equal(listed.data.result.configs[0].taskId, taskId);
79
+ assert.equal(await sha256(String(listed.data.result.configs[0].token)), tokenHash);
80
+ const plural = await s.call(ada, "ListTaskPushNotificationConfigs", { taskId });
81
+ assert.equal(await sha256(String(plural.data.result.configs[0].token)), tokenHash);
82
+
83
+ const as03 = await s.call(ada, "tasks/pushNotificationConfig/list", { id: taskId });
84
+ assert.equal(Array.isArray(as03.data.result), true);
85
+ assert.equal(as03.data.result.length, 1);
86
+ assert.equal(as03.data.result[0].pushNotificationConfig.id, "cfg-1");
87
+ assert.equal(await sha256(String(as03.data.result[0].pushNotificationConfig.token)), tokenHash);
88
+
89
+ const missing = await s.call(bob, "ListTaskPushNotificationConfig", { taskId: "missing-task-id" });
90
+ const foreign = await s.call(bob, "ListTaskPushNotificationConfig", { taskId });
91
+ assert.deepEqual(foreign.data.error, { code: -32001, message: "Task not found" });
92
+ assert.deepEqual(missing.data.error, foreign.data.error);
93
+ hides(foreign.text, token);
94
+ hides(missing.text, token);
95
+ const foreignDel = await s.call(bob, "DeleteTaskPushNotificationConfig", { taskId, id: "cfg-1" });
96
+ assert.deepEqual(foreignDel.data.error, { code: -32001, message: "Task not found" });
97
+ hides(foreignDel.text, token);
98
+ const foreign03 = await s.call(bob, "tasks/pushNotificationConfig/list", { id: taskId });
99
+ assert.deepEqual(foreign03.data.error, { code: -32001, message: "Task not found" });
100
+ hides(foreign03.text, token);
101
+
102
+ const gone = await s.call(ada, "DeleteTaskPushNotificationConfig", { taskId, id: "cfg-1" });
103
+ assert.deepEqual(gone.data.result, {});
104
+ const again = await s.call(ada, "DeleteTaskPushNotificationConfig", { taskId, id: "cfg-1" });
105
+ assert.deepEqual(again.data.result, {});
106
+ const after = await s.call(ada, "ListTaskPushNotificationConfig", { taskId });
107
+ assert.deepEqual(after.data.result, { configs: [], nextPageToken: "" });
108
+
109
+ const noId = await s.call(ada, "DeleteTaskPushNotificationConfig", { taskId });
110
+ assert.equal(noId.data.error?.code, -32602);
111
+ assert.equal(noId.data.error?.message, "Invalid params: push notification config id");
112
+
113
+ const bad = "page-token-secret-value";
114
+ const rejected = await s.call(ada, "ListTaskPushNotificationConfig", { taskId, pageToken: bad });
115
+ assert.equal(rejected.data.error?.message, "Invalid params: pageToken");
116
+ hides(rejected.text, bad);
117
+ hides(rejected.text, token);
118
+
119
+ assert.equal(s.logs.some((line) => line.includes(token)), false);
120
+ });
121
+
122
+ test("1.0 list pages configs and 0.3 delete returns null", async (t) => {
123
+ const s = setup();
124
+ t.after(s.restore);
125
+ const ada = (await s.owner("POST", "/owner/peers", { label: "ada" })).token as string;
126
+ const token = `pt_${crypto.randomUUID()}`;
127
+ s.secrets.push(token);
128
+ const sent = await s.call(ada, "SendMessage", {
129
+ message: { role: "ROLE_USER", messageId: crypto.randomUUID(), parts: [{ text: "hello" }] },
130
+ });
131
+ const taskId = sent.data.result.task.id as string;
132
+ for (const id of ["c-1", "c-2", "c-3"]) {
133
+ const cfgToken = id === "c-2" ? token : `other-${id}`;
134
+ s.secrets.push(cfgToken);
135
+ const set = await s.call(ada, "CreateTaskPushNotificationConfig", {
136
+ taskId, pushNotificationConfig: { id, url: `https://hooks.example.net/${id}`, token: cfgToken },
137
+ });
138
+ assert.equal(set.data.error, undefined);
139
+ }
140
+ const seen: string[] = [];
141
+ let pageToken = "";
142
+ do {
143
+ const page = await s.call(ada, "ListTaskPushNotificationConfigs", { taskId, pageSize: 1, ...(pageToken ? { pageToken } : {}) });
144
+ assert.equal(page.data.result.configs.length, 1);
145
+ seen.push(page.data.result.configs[0].id);
146
+ if (page.data.result.configs[0].id === "c-2") assert.equal(await sha256(String(page.data.result.configs[0].token)), await sha256(token));
147
+ pageToken = page.data.result.nextPageToken;
148
+ if (!pageToken) assert.equal(pageToken, "");
149
+ } while (pageToken);
150
+ assert.deepEqual(seen, ["c-1", "c-2", "c-3"]);
151
+
152
+ const del = await s.call(ada, "tasks/pushNotificationConfig/delete", { id: taskId, pushNotificationConfigId: "c-2" });
153
+ assert.equal(del.data.result, null);
154
+ const delAgain = await s.call(ada, "tasks/pushNotificationConfig/delete", { id: taskId, pushNotificationConfigId: "c-2" });
155
+ assert.equal(delAgain.data.result, null);
156
+ const left = await s.call(ada, "tasks/pushNotificationConfig/list", { id: taskId });
157
+ assert.deepEqual(left.data.result.map((row: { pushNotificationConfig: { id: string } }) => row.pushNotificationConfig.id), ["c-1", "c-3"]);
158
+ hides(left.text, token);
159
+ hides(del.text, token);
160
+ assert.equal(s.logs.some((line) => line.includes(token)), false);
161
+ });
@@ -19,7 +19,7 @@ test("grok-bot: bearer + JSON payload", async () => {
19
19
  const b = JSON.parse(r.body);
20
20
  assert.deepEqual([b.contextId, b.taskId, b.from, b.kind], ["ctx-1", "t-2", "peer-a", "inbound"]);
21
21
  assert.deepEqual(b.taskIds, ["t-1", "t-2"]);
22
- assert.match(b.hint, /inbox --context ctx-1/);
22
+ assert.match(b.hint, /inbox --context ctx-1 --from peer-a/);
23
23
  });
24
24
 
25
25
  test("claude-code: anthropic-version + text body", async () => {
@@ -106,7 +106,7 @@ test("redact masks credentials", async () => {
106
106
  });
107
107
 
108
108
  test("summary for outbound updates points at history", () => {
109
- assert.match(wakeSummary({ ...ev, kind: "outbound_update" }, "a2a"), /a2a history ctx-1/);
109
+ assert.match(wakeSummary({ ...ev, kind: "outbound_update" }, "a2a"), /a2a history ctx-1 --from peer-a/);
110
110
  });
111
111
 
112
112
  test("generic custom key header is rendered with prefix and redacted in previews", async () => {