a2a-exposed 0.8.2 → 0.8.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2a-exposed",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "Give any AI agent a public A2A (Agent2Agent) endpoint: Cloudflare Worker inbox + wake webhook or scheduled inbox checks",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1312,7 +1312,7 @@ async function tokenEndpoint(req: Request, ctx: TenantContext): Promise<Response
1312
1312
  if (now >= r.expires_ms) return oauthError("expired_token", "the device_code expired; start again");
1313
1313
  if (r.status === "denied") {
1314
1314
  await ctx.DB.prepare("DELETE FROM device_requests WHERE device_hash = ?").bind(hash).run();
1315
- return oauthError("access_denied", "the inbox owner denied this request");
1315
+ return oauthError("access_denied", "the agent owner denied this request");
1316
1316
  }
1317
1317
  if (r.status === "pending") {
1318
1318
  // RFC 8628 §3.5: polling faster than the interval -> slow_down, and the interval grows by 5 s for good
@@ -1321,7 +1321,7 @@ async function tokenEndpoint(req: Request, ctx: TenantContext): Promise<Response
1321
1321
  return oauthError("slow_down", `polling too fast; wait at least ${r.interval_s + 5} seconds between requests`);
1322
1322
  }
1323
1323
  await ctx.DB.prepare("UPDATE device_requests SET last_poll_ms = ? WHERE device_hash = ?").bind(now, hash).run();
1324
- return oauthError("authorization_pending", "waiting for the inbox owner to approve");
1324
+ return oauthError("authorization_pending", "waiting for the agent owner to approve");
1325
1325
  }
1326
1326
  // approved: single use. Only the request that flips the row gets the token; the row is then deleted.
1327
1327
  const claimed = await ctx.DB.prepare("UPDATE device_requests SET status = 'redeemed' WHERE device_hash = ? AND status = 'approved'").bind(hash).run();
@@ -2029,7 +2029,7 @@ async function authorizePage(req: Request, ctx: TenantContext, url: URL, head =
2029
2029
  return show(403, { kind: "error", text: "This form expired or came from another site. Start connecting again from your MCP client." });
2030
2030
  if (form.decision === "deny") {
2031
2031
  log("mcp_authorization_denied", { ip, clientName: c.client!.client_name });
2032
- return redirectWith(ctx, c.redirect!, { error: "access_denied", error_description: "the inbox owner denied access", state: c.params.state });
2032
+ return redirectWith(ctx, c.redirect!, { error: "access_denied", error_description: "the agent owner denied access", state: c.params.state });
2033
2033
  }
2034
2034
  if (form.decision === "oidc") {
2035
2035
  const started = await O.startOidc(ctx, {
@@ -1,2 +1,2 @@
1
1
  /** Inbox build. Keep this equal to `version` in cli/package.json. */
2
- export const VERSION = "0.8.2";
2
+ export const VERSION = "0.8.3";
@@ -231,7 +231,7 @@ test("authorization: consent page, CSP allows the redirect origin, password requ
231
231
  c = await s.consent(q, "", "deny");
232
232
  assert.equal(c.post.status, 302);
233
233
  const den = new URL(c.post.headers.get("location")!);
234
- assert.deepEqual([den.origin + den.pathname, den.searchParams.get("error"), den.searchParams.get("state"), den.searchParams.get("iss")], [CLAUDE_CB, "access_denied", "xyz", BASE]);
234
+ assert.deepEqual([den.origin + den.pathname, den.searchParams.get("error"), den.searchParams.get("error_description"), den.searchParams.get("state"), den.searchParams.get("iss")], [CLAUDE_CB, "access_denied", "the agent owner denied access", "xyz", BASE]);
235
235
  // CSRF: a POST without the cookie is refused
236
236
  const forged = await s.call("POST", "/oauth/authorize", { form: { ...q, csrf: "x".repeat(24), password: PASSWORD, decision: "approve" }, headers: { origin: BASE } });
237
237
  assert.equal(forged.status, 403);
@@ -271,7 +271,7 @@ test("human approval: pending, slow_down, page approve with the password, single
271
271
  assert.match(w.pairing.instructions, /never approve on your own[\s\S]*give them this link/);
272
272
 
273
273
  let p = await s.poll(d.device_code);
274
- assert.deepEqual([p.status, p.data.error], [400, "authorization_pending"]);
274
+ assert.deepEqual([p.status, p.data.error, p.data.error_description], [400, "authorization_pending", "waiting for the agent owner to approve"]);
275
275
  p = await s.poll(d.device_code);
276
276
  assert.deepEqual([p.status, p.data.error], [400, "slow_down"], "polled again at once");
277
277
  assert.match(p.data.error_description, /at least 10 seconds/);
@@ -390,7 +390,8 @@ test("human mode: the owner token approves and denies; the device poll returns t
390
390
  assert.equal(denied.data.decision, "denied");
391
391
  const denyRow = s.DB.db.prepare("SELECT decided_by FROM device_requests WHERE user_code = ?").get(deny.user_code.replace("-", "")) as { decided_by: string };
392
392
  assert.equal(denyRow.decided_by, "owner");
393
- assert.equal((await s.poll(deny.device_code)).data.error, "access_denied");
393
+ const deniedPoll = await s.poll(deny.device_code);
394
+ assert.deepEqual([deniedPoll.data.error, deniedPoll.data.error_description], ["access_denied", "the agent owner denied this request"]);
394
395
  });
395
396
 
396
397
  test("a pairing label comes from the client name, a second one is suffixed, and inbox from uses it", async (t) => {