a2a-exposed 0.8.10 → 0.8.11

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/lib/commands.mjs CHANGED
@@ -253,13 +253,24 @@ export function peers(sub, args, o) {
253
253
  }
254
254
  }
255
255
 
256
+ const ownerConfigured = () => !!(baseUrl() && C.get("A2A_OWNER_TOKEN"));
257
+
258
+ // Outbound delivery works with just a peer token unless --push is requested.
259
+ // Persistence failures must not hide successful delivery (and invite a duplicate send).
260
+ async function persistOutbound(method, path, body, action) {
261
+ if (!ownerConfigured()) return false;
262
+ try { await owner(method, path, body); return true; }
263
+ catch { console.error(`warning: could not ${action} to the worker`); return false; }
264
+ }
265
+
256
266
  async function logHistory(ctx, entry) {
257
- try { await owner("POST", "/owner/history", { contextId: ctx, ...entry }); }
258
- catch { console.error("warning: could not log history to the worker"); }
267
+ await persistOutbound("POST", "/owner/history", { contextId: ctx, ...entry }, "log history");
259
268
  }
260
269
 
261
270
  export async function send(o) {
262
271
  if (!o.to) die("--to required");
272
+ // The callback authenticates against the token saved through the owner API.
273
+ if (o.push && !ownerConfigured()) die("--push needs A2A_BASE_URL and A2A_OWNER_TOKEN (your Worker); configure both or omit --push and poll instead");
263
274
  const [alias, base, tok] = resolvePeer(o.to);
264
275
  const card = await fetchCard(base);
265
276
  const [url, version] = pickEndpoint(base, card, o.proto);
@@ -272,7 +283,6 @@ export async function send(o) {
272
283
  const conf = v1 ? { returnImmediately: true } : { blocking: false };
273
284
  let pushToken = null;
274
285
  if (o.push) {
275
- if (!baseUrl()) die("--push needs A2A_BASE_URL (your Worker)");
276
286
  pushToken = randomToken(24);
277
287
  conf[v1 ? "taskPushNotificationConfig" : "pushNotificationConfig"] = { url: baseUrl() + "/push", token: pushToken };
278
288
  }
@@ -282,10 +292,13 @@ export async function send(o) {
282
292
  const isTask = obj && typeof obj === "object" && "status" in obj;
283
293
  const ctx = (obj && obj.contextId) || o.context || newId();
284
294
  const tid = isTask ? obj.id : null;
295
+ out(obj);
285
296
  await logHistory(ctx, { dir: "out", peer: alias, taskId: tid, role: "user", text, data: { messageId: msg.messageId, endpoint: url, protocol: version } });
286
- if (tid) await owner("POST", "/owner/outbound", { taskId: tid, contextId: ctx, peer: alias, endpoint: url, protocol: version, pushToken, task: obj });
297
+ if (tid) {
298
+ const saved = await persistOutbound("POST", "/owner/outbound", { taskId: tid, contextId: ctx, peer: alias, endpoint: url, protocol: version, pushToken, task: obj }, "save outbound task");
299
+ if (o.push && !saved) console.error(`warning: push updates cannot authenticate because the outbound task token could not be saved; use ${CLI} poll --to ${alias} ${tid} instead; the message was delivered, do not resend`);
300
+ }
287
301
  else if (obj && obj.parts) await logHistory(ctx, { dir: "in", peer: alias, role: "agent", event: "direct_message", text: textOf(obj) });
288
- out(obj);
289
302
  if (tid) console.error(`# task ${tid}: ${plainState(obj)} (check: ${CLI} poll --to ${alias} ${tid})`);
290
303
  }
291
304
 
@@ -294,17 +307,19 @@ export async function poll(taskId, o) {
294
307
  const [alias, base, tok] = resolvePeer(o.to);
295
308
  const tid = checkId(taskId, "taskId");
296
309
  let rec = {};
297
- try { rec = await owner("GET", `/owner/outbound/${q(tid)}`); } catch { rec = {}; }
310
+ if (ownerConfigured()) {
311
+ try { rec = await owner("GET", `/owner/outbound/${q(tid)}`); } catch { rec = {}; }
312
+ }
298
313
  const [url, version] = rec.endpoint ? [rec.endpoint, rec.protocol || "0.3"] : pickEndpoint(base, await fetchCard(base), o.proto);
299
314
  const res = await rpc(url, version, tok, "tasks/get", "GetTask", { id: tid }, { alias: C.loadPeers()[alias] ? alias : null, base });
315
+ out(res);
300
316
  if (rec.endpoint) {
301
- await owner("PUT", `/owner/outbound/${q(tid)}`, { task: res });
317
+ await persistOutbound("PUT", `/owner/outbound/${q(tid)}`, { task: res }, "update outbound task");
302
318
  let txt = textOf((res.status || {}).message || {});
303
319
  const arts = (res.artifacts || []).map((x) => textOf(x)).join("\n");
304
320
  if (arts && arts !== txt) txt = (txt ? txt + "\n" : "") + arts;
305
321
  await logHistory(rec.contextId, { dir: "in", peer: alias, taskId: tid, event: "poll", state: plainState(res), text: txt });
306
322
  }
307
- out(res);
308
323
  console.error(`# task ${tid}: ${plainState(res)}`);
309
324
  }
310
325
 
package/lib/main.mjs CHANGED
@@ -228,6 +228,8 @@ Outbound (agents you call)
228
228
  differs between the two, connect/send/poll/peers/status warn (values never shown)
229
229
  send --to <alias|url> [--text T | stdin] [--context C] [--task T] [--push] [--proto 0.3|1.0]
230
230
  poll --to <alias|url> <taskId> [--proto 0.3|1.0]
231
+ send/poll need only peer config; owner history/task storage is optional.
232
+ Storage failures warn on stderr; the peer response still prints and exits 0
231
233
  outbound <taskId> stored state of a task you sent (incl. pushed updates)
232
234
 
233
235
  Owner
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2a-exposed",
3
- "version": "0.8.10",
3
+ "version": "0.8.11",
4
4
  "description": "Give any AI agent a public A2A (Agent2Agent) endpoint: a Cloudflare Worker, a wake webhook, or scheduled checks",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,7 +15,8 @@
15
15
  ],
16
16
  "scripts": {
17
17
  "prepack": "node scripts/sync-worker.mjs && node -e \"require('fs').copyFileSync('../LICENSE','LICENSE')\"",
18
- "test": "node --test test/*.test.mjs"
18
+ "test": "node --test test/*.test.mjs",
19
+ "test:coverage": "node --experimental-test-coverage --test-coverage-include=lib/** --test-reporter=spec --test-reporter=lcov --test-reporter-destination=stdout --test-reporter-destination=../coverage/cli.lcov --test test/*.test.mjs"
19
20
  },
20
21
  "engines": {
21
22
  "node": ">=22.18"
@@ -16,7 +16,7 @@ A2A_AGENT_DESCRIPTION=An AI agent reachable over A2A. Replies asynchronously.
16
16
  WAKE_PRESET=generic
17
17
  # WAKE_AGENT_ID=main
18
18
  # WAKE_KEY_HEADER=authorization
19
- # WAKE_KEY_PREFIX=Bearer
19
+ # WAKE_KEY_PREFIX="Bearer "
20
20
  # WAKE_BODY_TEMPLATE={"text":"{{summary}}"}
21
21
  # Command shown in wake hints (CLI: --cli-command; default "npx -y a2a-exposed@latest"). For a source checkout:
22
22
  # WAKE_CLI_COMMAND=node /path/to/a2a-exposed/cli/bin/a2a-exposed.mjs
@@ -9,6 +9,7 @@
9
9
  "deploy": "cf deploy",
10
10
  "pretest": "node scripts/stamp-version.mjs",
11
11
  "test": "node --import ./test/register-hooks.mjs --test test/*.test.ts",
12
+ "test:coverage": "node scripts/stamp-version.mjs && node --import ./test/register-hooks.mjs --experimental-test-coverage --test-coverage-include=src/** --test-reporter=spec --test-reporter=lcov --test-reporter-destination=stdout --test-reporter-destination=../coverage/worker.lcov --test test/*.test.ts",
12
13
  "typecheck": "cf workers types && tsc"
13
14
  },
14
15
  "engines": {
@@ -16,11 +17,11 @@
16
17
  },
17
18
  "devDependencies": {
18
19
  "@cloudflare/vite-plugin": "beta",
19
- "cf": "^1.0.0-beta.13",
20
- "esbuild": "^0.28.1",
20
+ "cf": "^1.0.0-beta.18",
21
+ "esbuild": "^0.28.2",
21
22
  "miniflare": "^5.20261006.0-alpha",
22
23
  "typescript": "^7.0.2",
23
- "vite": "^8.3.0"
24
+ "vite": "^8.3.4"
24
25
  },
25
26
  "dependencies": {
26
27
  "tunnelfetch": "1.13.0"
@@ -1,2 +1,2 @@
1
1
  /** Inbox build. Keep this equal to `version` in cli/package.json. */
2
- export const VERSION = "0.8.10";
2
+ export const VERSION = "0.8.11";
@@ -111,4 +111,4 @@ test("a token-shaped claim is blanked and the rest of the list is returned", ()
111
111
  assert.equal(JSON.stringify(listed).includes(token), false);
112
112
  assert.equal(JSON.stringify(listed).includes("t=" + "c".repeat(16)), false);
113
113
  assert.equal(JSON.stringify(listed).includes("user:secret"), false);
114
- });
114
+ });
@@ -19,4 +19,4 @@
19
19
  "cloudflare.config.ts",
20
20
  ".cloudflare/types"
21
21
  ]
22
- }
22
+ }