a2a-exposed 0.8.40 → 0.8.42

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.
@@ -127,3 +127,45 @@ test("unsigned OpenBot owner test/preview and inbound wake never contact the web
127
127
  assert.equal(message.body.error, undefined, "stored inbox message succeeds without a wake");
128
128
  assert.equal(calls, 0);
129
129
  });
130
+
131
+
132
+ test("MausBot retries keep their key, later messages and verification tests get new keys", async (t) => {
133
+ const env: any = { DB: d1(new URL("../migrations/", import.meta.url)), PUBLIC_URL: BASE, OWNER_TOKEN: "owner-secret",
134
+ WAKE_PRESET: "mausbot", WAKE_WEBHOOK_URL: "https://hooks.example.com/hooks/wh_test", WAKE_WEBHOOK_KEY: "dummy-secret", WAKE_DEBOUNCE_SECONDS: "0", WAKE_BUSY_SECONDS: "0" };
135
+ const sent: any[] = [];
136
+ let answer = 429;
137
+ const original = globalThis.fetch;
138
+ globalThis.fetch = (async (_url: any, init: any) => { sent.push(init); return new Response("{}", { status: answer, headers: { "retry-after": "30" } }); }) as typeof fetch;
139
+ t.after(() => { globalThis.fetch = original; });
140
+ const call = async (method: string, path: string, body?: any, auth = "Bearer owner-secret") => {
141
+ const pending: Promise<unknown>[] = [];
142
+ const response = await worker.fetch(new Request(BASE + path, { method, headers: { authorization: auth, "content-type": "application/json" }, body: body ? JSON.stringify(body) : undefined }) as any,
143
+ env, { waitUntil: (p: Promise<unknown>) => pending.push(p), passThroughOnException() {} } as any);
144
+ await Promise.allSettled(pending);
145
+ return { status: response.status, body: await response.json() as any };
146
+ };
147
+ const preview = await call("GET", "/owner/wake/preview");
148
+ assert.equal(preview.status, 200);
149
+ assert.equal(preview.body.request.headers.authorization, "Bearer ••••");
150
+ assert.ok(!JSON.stringify(preview.body).includes(env.WAKE_WEBHOOK_KEY));
151
+ const peer = await call("POST", "/owner/peers", { label: "maus-test" });
152
+ const msg = (id: string, taskId?: string) => call("POST", "/", { jsonrpc: "2.0", id: 1, method: "message/send", params: { message: { kind: "message", role: "user", messageId: id, contextId: "ctx-maus", ...(taskId ? { taskId } : {}), parts: [{ kind: "text", text: "same text" }] } } }, `Bearer ${peer.body.token}`);
153
+ const first = await msg("m1");
154
+ assert.equal(sent.length, 1);
155
+ answer = 202;
156
+ // Advance only the saved retry deadline, without changing the global clock.
157
+ await env.DB.prepare("UPDATE wakes SET last_sent_ms = ? WHERE context_id = ?").bind(Date.now() - 1000, "ctx-maus").run();
158
+ await call("GET", "/health");
159
+ assert.equal(sent.length, 2);
160
+ assert.equal(sent[0].headers["idempotency-key"], sent[1].headers["idempotency-key"]);
161
+ await msg("m2", first.body.result.id);
162
+ assert.equal(sent.length, 3);
163
+ assert.notEqual(sent[2].headers["idempotency-key"], sent[1].headers["idempotency-key"]);
164
+ await call("POST", "/owner/wake/test");
165
+ await call("POST", "/owner/wake/test");
166
+ assert.notEqual(sent[3].headers["idempotency-key"], sent[4].headers["idempotency-key"]);
167
+ delete env.WAKE_WEBHOOK_KEY;
168
+ assert.equal((await call("GET", "/owner/wake/preview")).status, 400);
169
+ assert.equal((await call("POST", "/owner/wake/test")).body.status, null);
170
+ assert.equal(sent.length, 5, "no unsigned request leaves the Worker");
171
+ });
@@ -106,7 +106,7 @@ test("generic: invalid template throws", () => {
106
106
 
107
107
  test("every preset renders valid JSON", async () => {
108
108
  for (const p of PRESETS) {
109
- const r = (await renderWake({ preset: p, url: "https://x/y", key: "k", hmacSecret: "s" }, ev))!;
109
+ const r = (await renderWake({ preset: p, url: "https://x/hooks/wh_test", key: "k", hmacSecret: "s" }, ev))!;
110
110
  JSON.parse(r.body);
111
111
  }
112
112
  });
@@ -151,7 +151,7 @@ const ACCESS = { accessClientId: "abc123.access", accessClientSecret: "s3cr3t-va
151
151
 
152
152
  test("Access service-token headers are added for every preset, next to the preset's own auth", async () => {
153
153
  for (const preset of PRESETS) {
154
- const r = (await renderWake({ preset, url: "https://wake-x.example.com/hooks/wake", key: "k1", hmacSecret: "h1", ...ACCESS }, ev))!;
154
+ const r = (await renderWake({ preset, url: "https://wake-x.example.com/hooks/wh_test", key: "k1", hmacSecret: "h1", ...ACCESS }, ev))!;
155
155
  assert.equal(r.headers["CF-Access-Client-Id"], "abc123.access", preset);
156
156
  assert.equal(r.headers["CF-Access-Client-Secret"], "s3cr3t-value-0123456789", preset);
157
157
  if (preset === "hermes") assert.ok(r.headers["X-Webhook-Signature-V2"], "hermes keeps its HMAC");
@@ -227,3 +227,36 @@ test("OpenBot assigns a fresh delivery ID to every rendered attempt", async () =
227
227
  const second = (await renderWake(cfg, ev))!;
228
228
  assert.notEqual(first.headers["x-openbot-delivery-id"], second.headers["x-openbot-delivery-id"]);
229
229
  });
230
+
231
+
232
+ test("mausbot: Bearer JSON, bounded peer preview, stable delivery identity and masked credentials", async () => {
233
+ const cfg = { preset: "mausbot", url: "https://hooks.example.com/hooks/wh_example", key: "dummy-mausbot-secret" };
234
+ const event = { ...ev, preview: '雪 "quoted"\n'.repeat(100), deliveryId: "wake-1" };
235
+ const first = (await renderWake(cfg, event, { requestId: "attempt-1" }))!;
236
+ const retry = (await renderWake(cfg, event, { requestId: "attempt-2" }))!;
237
+ assert.equal(first.headers.authorization, "Bearer dummy-mausbot-secret");
238
+ assert.equal(first.headers["content-type"], "application/json");
239
+ assert.equal(first.headers["idempotency-key"], "t-2:wake-1");
240
+ assert.equal(retry.headers["idempotency-key"], first.headers["idempotency-key"]);
241
+ assert.deepEqual(JSON.parse(first.body), wakePayload(event, "npx -y a2a-exposed@latest"));
242
+ assert.equal(JSON.parse(first.body).preview.length, 300);
243
+ assert.equal(JSON.parse(first.body).deliveryId, undefined);
244
+ assert.equal(redact(first).headers.authorization, "Bearer ••••");
245
+ assert.ok(!JSON.stringify(redact(first)).includes(cfg.key));
246
+ const update = (await renderWake(cfg, { ...event, kind: "outbound_update", deliveryId: "wake-2" }))!;
247
+ assert.notEqual(update.headers["idempotency-key"], first.headers["idempotency-key"]);
248
+ assert.match(JSON.parse(update.body).hint, /history ctx-1 --from peer-a/);
249
+ const test1 = (await renderWake(cfg, { ...ev, kind: "test", taskId: "none" }))!;
250
+ const test2 = (await renderWake(cfg, { ...ev, kind: "test", taskId: "none" }))!;
251
+ assert.notEqual(test1.headers["idempotency-key"], test2.headers["idempotency-key"], "verification test must not suppress the enabled test");
252
+ });
253
+
254
+ test("mausbot: rejects unsafe URLs, capability credentials and missing Bearer auth without echoing input", async () => {
255
+ for (const url of ["http://example.com/hooks/wh_test", "https://user:secret@example.com/hooks/wh_test", "https://example.com/hooks/wh_test/capability-secret", "https://example.com/hooks/wh_test?secret=dummy", "https://example.com/hooks/wh_test#dummy", "not-a-url", "https://example.com/api"]) {
256
+ await assert.rejects(renderWake({ preset: "mausbot", url, key: "dummy" }, ev), error => error instanceof Error && /MausBot requires an HTTPS/.test(error.message) && !error.message.includes(url));
257
+ }
258
+ for (const key of [undefined, "", " ", "line\nbreak"]) {
259
+ await assert.rejects(renderWake({ preset: "mausbot", url: "https://example.com/hooks/wh_test", key, hmacSecret: "cannot-replace-bearer" }, ev), /MausBot requires WAKE_WEBHOOK_KEY/);
260
+ }
261
+ assert.equal(await renderWake({ preset: "mausbot" }, ev), null);
262
+ });