Skip to content

HomeChangelog

Updated 2026-10-23 · applies to a2a-exposed 0.8.11

Changelog

The CLI and Worker are MIT licensed in telegraphic-dev/a2a-exposed (opens in a new tab). Each note says what you can do in that version. Release notes are linked.

Unreleased

A busy GitHub sign-in returns provider_busy. An agent action that asks you to sign in again returns to /app/sign-in?return=...&reauth=agent.

0.8.11

Release notes: v0.8.11 (opens in a new tab)

0.8.10

Release notes: v0.8.10 (opens in a new tab)

0.8.9

Release notes: v0.8.9 (opens in a new tab)

0.8.8

Release notes: v0.8.8 (opens in a new tab)

0.8.7

Release notes: v0.8.7 (opens in a new tab)

0.8.6

Release notes: v0.8.6 (opens in a new tab)

0.8.5

Release notes: v0.8.5 (opens in a new tab)

0.8.4

Release notes: v0.8.4 (opens in a new tab)

0.8.3

Release notes: v0.8.3 (opens in a new tab)

0.8.2

Release notes: v0.8.2 (opens in a new tab)

0.8.1

Release notes: v0.8.1 (opens in a new tab)

0.7.34

Release notes: v0.7.34 (opens in a new tab)

0.7.33

Release notes: v0.7.33 (opens in a new tab)

0.7.31

Release notes: v0.7.31 (opens in a new tab)

0.7.30

Release notes: v0.7.30 (opens in a new tab)

0.7.29

Release notes: v0.7.29 (opens in a new tab)

Right after Cloudflare grants a permission, creating the database, applying the database setup, or uploading the Worker can need one more try. Set me up on Cloudflare waits and tries that step again, up to 4 times over about 15 seconds, when Cloudflare returns code 10000, code 7403, or says the token lacks access to that resource. The page says Waiting for Cloudflare to apply the new permission…. After those tries, the page shows the error and Try again. A sign-in from the last 10 minutes keeps Continue to Cloudflare, Connect again, and Try again on this page. After 10 minutes, those actions ask you to sign in again and then continue. Continue to Cloudflare and Connect again go straight to Cloudflare's consent screen. Try again continues the same step.

0.7.28

Release notes: v0.7.28 (opens in a new tab)

While a step is running or has failed, Set me up on Cloudflare shows the steps and Try again. What do you want, Which web address, and Review permissions stay behind Start a different setup. Uploading the Worker asks for Workers Admin. A saved connection that does not include Workers Admin asks you to connect again.

0.7.27

Release notes: v0.7.27 (opens in a new tab)

Set me up on Cloudflare checks the agent address one step at a time. The steps are the Cloudflare connection and its permissions, the account, a workers.dev name (this page creates one when the account has none yet), a free address name, the database, the database setup, the upload, and a check that the address answers. The page then shows the address. A stopped step shows Cloudflare's code and message, with the tokens left out, and Try again continues from that step. The owner token is shown once, on the upload step.

0.7.26

Release notes: v0.7.26 (opens in a new tab)

Set me up on Cloudflare can create the agent address in your Cloudflare account: a D1 database, the Worker, and a public address. A free address looks like my-agent.yourname.workers.dev. Your own domain uses a hostname already on a domain in that account. The owner token is shown once. Creating the address needs a sign-in from the last 10 minutes. You connect your existing agent from a machine you control.

0.7.25

Release notes: v0.7.25 (opens in a new tab)

The README is a short introduction. Setup, receiving messages, self-hosting, the CLI, and the rest of the guide are in the docs.

Set me up on Cloudflare asks for Workers Edit, the permission this app is registered to use. If you cancel, the setup page says you cancelled. If Cloudflare refuses a permission, the page says this app is not allowed to ask for it. If granting does not finish, the page says so. You come back to the setup page. The banner and header span the window. The choices sit in a centred column.

0.7.24

Release notes: v0.7.24 (opens in a new tab)

The README is a short introduction. Setup, receiving messages, self-hosting, the CLI, and the rest of the guide are in the docs.

0.7.23

Release notes: v0.7.23 (opens in a new tab)

Set me up on Cloudflare asks what you want: a public A2A address, an agent that already speaks A2A, or both. Then a free Cloudflare address, or a domain already on the account. Continue to Cloudflare and disconnect need a sign-in from the last 10 minutes. Continue says "Confirm it's you before creating things in your Cloudflare account." Disconnect says "Confirm it's you before changing your Cloudflare connection." Cloudflare on that page deploys.

Feedback is a public GitHub issue. Leave secrets out.

0.7.22

Release notes: v0.7.22 (opens in a new tab)

Feedback files a public GitHub issue. A GitHub App auth failure answers 502 { "error": "unavailable" } (a timeout stays 504).

0.7.21

Release notes: v0.7.21 (opens in a new tab)

A signed-in account opens Set me up on Cloudflare at /app/cloudflare. The page asks what to set up and which address to use. Continue to Cloudflare asks for the permissions that choice needs.

0.7.20

Release notes: v0.7.20 (opens in a new tab)

/app/feedback files a public GitHub issue. Leave secrets out. On production, that path opens the GitHub issue form.

0.7.19

Release notes: v0.7.19 (opens in a new tab)

Send feedback with npx -y a2a-exposed@latest feedback "...", or use /app/feedback. The report is a public GitHub issue. Leave secrets out.

0.7.17

Release notes: v0.7.17 (opens in a new tab)

Cloudflare is for Set me up on Cloudflare. Connect it from /app after you are signed in.

0.7.16

Release notes: v0.7.16 (opens in a new tab)

A hosted agent card is cached for 60 seconds. A self-hosted card stays at 5 minutes. After you delete an agent, /app says how many agent slots remain. The one-time owner token has a Copy button.

0.7.15

Release notes: v0.7.15 (opens in a new tab)

/app/agents/<name> deletes an agent you own. The page asks for the agent name. The SQL download leaves out requester IP and country. Sign in again when the page asks, before you approve, deny, revoke, rotate, or delete.

0.7.14

Release notes: v0.7.14 (opens in a new tab)

/app/agents/<name> lists MCP connectors, a message log, and a redacted SQL download, and can rotate the owner token. The new token is shown once.

0.7.13

Release notes: v0.7.13 (opens in a new tab)

0.7.12

Release notes: v0.7.12 (opens in a new tab)

/app/agents/<name> edits the wake URL, preset, and key. Webhooks are the main wake. Polling is the fallback.

0.7.11

Release notes: v0.7.11 (opens in a new tab)

Pages on a hosted agent address use the site theme.

0.7.10

Release notes: v0.7.10 (opens in a new tab)

0.7.9

Release notes: v0.7.9 (opens in a new tab)

0.7.8

Release notes: v0.7.8 (opens in a new tab)

0.7.7

Release notes: v0.7.7 (opens in a new tab)

0.7.6

Release notes: v0.7.6 (opens in a new tab)

0.7.5

Published 2026-10-11. Sign-in waits for the check, then enables the provider buttons.

0.7.4

Published 2026-10-10. A same-origin sign-in form is accepted.

0.7.3

Published 2026-10-10. A GitHub or Google sign-in that fails returns to /app?error=<code>. Sign in with GitHub, Google, or email.

0.7.2

Published 2026-10-10. A failed sign-in returns to /app with an error code.

0.7.1

Published 2026-10-10. Sign-in follows the provider URL. A failed sign-in returns to /app with an error. There is no signup command.

0.7.0

Published 2026-10-10. A wake has to be public https. The dashboard signs in with GitHub and creates agents with POST /api/v1/tenants. There is no signup command.

0.6.0

Published 2026-10-10. Upgrade with npx -y a2a-exposed@latest deploy.

  • export prints a JSON dump of this agent address. export --sql prints the same tables as SQL. Putting that dump back is a separate step.
  • pair set-oidc adds OpenID Connect approval on /device and the MCP consent page. It stays off unless the issuer, client id, client secret and allowlist are all set.
  • Hosted addresses at <name>.a2a.exposed are not open. There is no signup or login command.

Grok Bot (grok-bot) is tested end to end. Other presets are on compatibility.

0.5.0

Published 2026-10-09. MCP connector at /mcp, public-front mode (--upstream), and an HTML page on the Worker.

0.4.0

Install a2a-exposed.

All notable changes to the a2a-exposed CLI (called a2a-over-webhook up to 0.3.x), Worker template, control core, and skills. Versions follow semver (opens in a new tab); a v* tag publishes the CLI and @a2a-exposed/control-core to npm (see Releases (opens in a new tab)).

0.8.11 - 2026-10-21

npx -y a2a-exposed@latest deploy (no new migration). send and poll work with peer configuration alone.

  • CLI outbound delivery (#105). send and poll work with peer configuration alone, without an owner Worker. History and outbound task storage are best-effort when owner configuration is present: write failures warn on stderr but never hide a successful peer response or make the command fail. Polling still falls back to the peer card when the stored outbound record cannot be read.

0.8.10 - 2026-10-16

npx -y a2a-exposed@latest deploy (no new migration). The CLI shows the dashboard link and sets pairing URLs, and pairing errors are clearer.

  • Dashboard link. pair list and inbox print the dashboard URL when MANAGE_URL is set. status prints it on the pairing line. GET /owner/pairing includes manageUrl for that URL.
  • Deploy flags. init and deploy take --manage-url and --event-url (none clears the saved value). --event-secret-stdin uploads EVENT_SECRET on that deploy. The secret stays out of config.env and out of the command output. An EVENT_SECRET argument is refused.
  • First run. status with nothing deployed says to run init. init checks the Cloudflare login before it installs packages. A fresh init runs npx --yes cf auth whoami for that check. A later init uses the cf in the Worker folder.
  • Pairing errors. An unknown or expired code on the owner API stays HTTP 409 and says to run connect again. GET /device for that code is HTTP 404 with the same wording. The empty /device form stays HTTP 200.
  • MCP. The pairing_requests tool description names the approval password, the owner token, and the dashboard link.

0.8.9 - 2026-10-16

npx -y a2a-exposed@latest deploy (no new migration).

  • Pairing events. When EVENT_URL is https and EVENT_SECRET is set, the A2A endpoint POSTs a signed JSON event after a pairing request is created and after it is approved or denied. The body includes pending, the number of requests still open. x-a2a-signature is sha256= plus the HMAC-SHA256 of the unix timestamp, a period, and the raw body. verifyEventSignature checks that signature and a 5-minute timestamp window. runInboxStep accepts eventUrl and eventSecret and writes EVENT_URL and EVENT_SECRET. On a hosted agent, createTenant stores EVENT_URL in the tenant config and seals EVENT_SECRET in secrets_enc. The agent decrypts that blob when it handles a request.

0.8.8 - 2026-10-16

npx -y a2a-exposed@latest deploy (no new migration). Pairing requests stay open 60 minutes, Cloudflare agents can turn on account sign-in for approvals, and wake messages can carry a Manage link via MANAGE_URL.

  • Pairing. A pairing code lasts 60 minutes. The /device approval page shows the expiry as a clock time in the visitor's time zone, then the minutes left. An unknown time zone shows UTC.
  • Dashboard link. Set MANAGE_URL to an https URL and the pairing_request wake and the MCP inbox line for a pending request include manageUrl.
  • Cloudflare provisioning. runInboxStep accepts approvalOidc (issuer, clientId, clientSecret, allowedSubjects). With an https issuer and those fields set, the uploaded Worker receives APPROVAL_OIDC_ISSUER, APPROVAL_OIDC_CLIENT_ID, APPROVAL_OIDC_CLIENT_SECRET (a secret), APPROVAL_OIDC_ALLOWED_SUBJECTS, and APPROVAL_METHODS=oidc. inboxApprovalBindings returns that list. A call that omits approvalOidc keeps the approval password.

0.8.7 - 2026-10-15

npx -y a2a-exposed@latest deploy (no new migration: neither the database in worker/migrations/ nor the control-core storage in control/migrations/ changed).

  • uninstall. uninstall prints the Worker, the D1 database, the account, the wake hostname, the Access application, the service token, and the DNS record (when a tunnel is saved) before it asks. --dry-run prints that plan, then Nothing was changed. An empty config exits 1 with nothing is deployed from <config dir>. Extra arguments are rejected. --help prints this command's help. A missing Worker (Cloudflare error 10007) counts as already gone, on uninstall and on tunnel rm. uninstall skips Worker secrets after it deletes the Worker, finds it already gone, or gets Cloudflare error 10000. Cloudflare errors 10000 and 7403 name the token permission (Workers Scripts: Edit, D1: Edit, Zone DNS: Edit, or Access: Edit) and say to rerun uninstall --yes once, including a D1 failure after the Worker is already gone and a DNS-only failure. A DNS-only failure lists the Worker, the tunnel, the Access application, the service token, and the token file under Removed when this run removed them, and names the DNS record under Not removed. When the Worker delete did not happen in this run, the D1 line says it is untouched because the Worker delete did not happen in this run. A failed Worker delete, including 10000, leaves the D1 database in place while the Worker still exists. The tunnel is still removed when that step succeeds. The summary says what was removed, what was not, and that a rerun is safe. While uninstall is deleting the Worker it does not print the standalone tunnel rm line about setting a new WAKE_WEBHOOK_URL. The owner token and WAKE_* values stay in the config directory. A later init reuses the owner token.
  • Tunnel. tunnel create, tunnel status, and tunnel rm reject extra arguments. tunnel create prints the Access application name and ids, a recap, and tunnel rm as the undo. tunnel rm removes the tunnel and does not put a replaced wake URL back. A finished tunnel creates nothing when you run it again. A partial tunnel stops and asks for tunnel rm. An existing Worker wake URL is named before tunnel create replaces it. Rollback deletes only the secrets this run uploaded. tunnel status says DNS may still be propagating on a network error (retry in 30 seconds), and warns about Access only for an HTTP response other than 401 or 403. An inactive tunnel reads as never connected in that JSON too. status --json uses the same words for tunnel.state. tunnel rm with nothing saved says nothing to remove. config prints A2A_TUNNEL_TOKEN_FILE and A2A_TUNNEL_SECRETS_AT as their values. The generated wake hostname is wake-<word>-<word>-<4 hex>, and the two words are different.
  • status. For about a minute after tunnel create, 0 connector connections exits 0 with waiting for the connector to connect and tells you to start cloudflared tunnel run --token-file … now. After that, 0 connections is broken. A tunnel status of inactive reads as never connected, including status --json. Worker secrets still show up in about 15 to 40 seconds; wait about 30 seconds and check again. A Cloudflare error 1042 says 30 seconds. With nothing deployed, status and tunnel status say nothing is deployed from <config dir>.
  • Pairing page. /device says where the code comes from (connect <url>). A live code with under a minute left says less than a minute. Approve and Deny show a done state. The token line says it can send messages and trigger wakes, and that message text from peers is untrusted. The revoke line names the token. The page, the landing page, the pairing wake, the MCP consent page, and the MCP limit error say A2A endpoint.
  • Copy. User-facing prose says A2A endpoint. The inbox command, the MCP tool name inbox, /owner/inbox, mode: "inbox", and the #hosted-inbox anchor stay. Protocol-visible strings that say A2A endpoint: the MCP serverInfo.title fallback, the OAuth resource_name fallback, mcpInstructions, the wake text heads, and the pairing wake text. The openclaw-agent hook name is A2A inbox. The wake text for that hook starts with A2A endpoint:.
  • Clients. The compatibility table is the source of truth. Its status column is tested end to end or untested.
  • Remove. After you delete a Worker, check that Worker's custom domains and routes, and the zone's DNS. tunnel rm still deletes the wake hostname's DNS record. When the config is lost, the tunnel, Access application, service token, and DNS record are removed from the dashboard (Tunnels, Access applications, Access service tokens, and DNS records) or with cf.

0.8.6 - 2026-10-15

npx -y a2a-exposed@latest deploy (no new database migration). One new control-core migration, 0015_cloudflare_grant_access.sql, for control-core storage only (control/migrations/), not the database.

  • Library. A Worker or D1 removal that receives Cloudflare code 10000 or 7403 waits for the new permission to apply, for up to 90 seconds after consent, and returns status waiting with "Waiting for Cloudflare to apply the new permission…". Retries in that window keep the access token from the one refresh for that step. runRemovalStep accepts consentedAt or secondsSinceConsent, an optional refreshAccess hook, and an optional onEvent callback. With refreshAccess omitted, the step refreshes the stored grant once. A refreshAccess hook that throws is recorded as a removal failure. A token response that carries refresh_token replaces the stored refresh token. A token response that omits refresh_token leaves the stored refresh token in place. The refresh request omits scope. A response that omits scope leaves the stored scopes in place. Migration 0015_cloudflare_grant_access.sql adds that sealed access token on the control-core library's own storage (control/migrations/). The Worker's database (worker/migrations/) is unchanged, which keeps the "no new database migration" note.

0.8.5 - 2026-10-15

npx -y a2a-exposed@latest deploy (no new database migration).

  • uninstall. npx -y a2a-exposed@latest uninstall removes the wake tunnel (when the config has one), the Worker, and the D1 database, and prints one line per step. Off a terminal, pass --yes. The config directory stays. Removal commands are in the CLI reference. Manual cf workers delete and cf d1 delete run from <config dir>/worker, or after mkdir -p .cloudflare/cache, so cf can write its account cache.
  • Tunnel. tunnel create names the tunnel, the Access app, and the service token a2a-exposed wake <worker>, and the DNS host wake-<word>-<word>-<hex>.<zone>. tunnel rm prints each removed object and deletes WAKE_WEBHOOK_KEY and WAKE_HMAC_SECRET from the Worker. The connector machine needs cloudflared. Worker secrets, wake preview, and status settle in about 15 to 40 seconds; wait about 30 seconds and check again. Right after the first tunnel create, while the connector reports 0 connections, status prints that wait and exits 0.
  • Wake test. Cloudflare error 1016 on a public webhook says the webhook host failed to resolve. The tunnel-hostname wording is for a tunnel wake.
  • Pairing. pair set-password --web --ttl is 1 to 60 minutes (default 15). A connect code lasts 10 minutes. The setup page states the 12-to-1024 character range, labels the second field "Repeat the password", and the approval page explains "Requested from" and names the token label in the revoke hint.
  • Wake preview. Export WAKE_WEBHOOK_URL, WAKE_WEBHOOK_KEY, and WAKE_HMAC_SECRET in the shell that runs wake preview and wake fingerprint. DIFFERENT means that exported value and the Worker differ: the variable is absent from the shell, or the upload is still propagating.

0.8.4 - 2026-10-15

npx -y a2a-exposed@latest deploy (no new database migration).

  • Tunnel remove. tunnel rm passes --force to cf tunnels connections cleanup and cf tunnels delete. When the tunnel is already gone, the command clears the saved ids and succeeds, so a later tunnel create can run.
  • Remove an agent address. The CLI reference lists what tunnel rm removes, then cf workers delete and cf d1 delete. --zero-trust-org creates a permanent Zero Trust organization. When WAKE_WEBHOOK_URL is already set, run wake unset before tunnel create.
  • CLI. wake unset treats a missing Worker secret (HTTP 404) as already unset and prints one line. deploy --workers-dev output stays on the cf CLI. Applying D1 migrations omits the non-interactive confirm line. init --rotate-owner-token prints the follow-up: update A2A_OWNER_TOKEN, then peers sync.
  • README. The quick start covers the profile, --account-id, and API token routes.

0.8.3 - 2026-10-15

npx -y a2a-exposed@latest deploy (no new database migration).

  • OAuth. A device-code poll that is still waiting reports waiting for the agent owner to approve. A denied device-code poll reports the agent owner denied this request. A denied MCP consent redirect reports the agent owner denied access.

0.8.2 - 2026-10-14

npx -y a2a-exposed@latest deploy (no new database migration).

  • Pairing. In human mode, pair approve <code> and pair deny <code> succeed with the owner token. The Worker records the decision as the owner. The peer collects its token on the next device poll. A call with no token, or with a peer token, is refused.
  • Help. --help lists init --rotate-owner-token and --version. --worker-dir and connect --replace are described in the present tense. CI checks that every a2a-exposed flag named in skills/ appears in --help.
  • status. When GET /owner/mcp reports enabled and the agent card is reachable, status prints claude mcp add --transport http a2a-exposed <base URL>/mcp. A failed probe or an unreachable card leaves that line out. Plugin manifests leave MCP unset because that URL differs per deploy.
  • Skills. The two skill descriptions use different triggers. Each SKILL.md stays under 150 lines. Cloudflare deploy failures in the setup skill link to references/troubleshooting.md. The hosted agent address at https://a2a.exposed is open to everyone; the sentence to say lives once in docs/getting-started.md, and other docs link there. Pricing is at https://a2a.exposed/pricing. In the compatibility table, Supported means tested end to end. Each client is named once, in that table. The day-to-day troubleshooting, token, and messaging notes live under skills/a2a-exposed/references/. metadata.hermes and metadata.openclaw stay nested, with envVars and boolean required / sensitive. CI pins skills-ref to commit 69ef37e.
  • Plugins. The Claude marketplace entry carries the same version as plugin.json. The Cursor manifest sets logo and skills. extensions.com.openai.onboardingSkill points at the setup skill. CI checks version, description, and keyword drift, parses skill metadata, and runs the Claude plugin validator (@anthropic-ai/claude-code@2.1.296), the Agent Plugins schema 1.0.0, and the Cursor plugin schema at 5229aad.

0.8.1 - 2026-10-13

npx -y a2a-exposed@latest deploy (no new database migration).

  • Health and feedback. The Worker in control/ serves GET /health, plain feedback when FEEDBACK_REPO and FEEDBACK_GITHUB_TOKEN are set, and the static files in public/. From control/, npm run deploy applies that Worker's D1 migrations and deploys it. Without a database, the hourly feedback limit still counts across requests. A filed issue URL matches the repository name without regard to case, and the response keeps GitHub's URL.

0.8.0 - 2026-10-13

npx -y a2a-exposed@latest deploy moves an agent address you already run onto this version. The owner token, the D1 database, peer tokens, and the pages / and /device stay. export prints that agent address; export --sql prints SQL. npx -y a2a-exposed@latest also deploys a new agent address, reads it, and replies. The same command sends messages to other A2A agents (1.0 or 0.3). Skills live in skills/.

  • One agent address. init deploys one agent address in your Cloudflare account, on your hostname or on *.workers.dev. Webhooks wake the agent. Polling is the fallback. Pairing, tokens, MCP at /mcp, a public façade, and export are in the CLI. deploy copies the current Worker template and applies every D1 migration that is not yet recorded. Secrets already on the Worker stay.
  • Owner. The owner token from init authenticates the owner commands (inbox, reply, export, and the rest). GET / in a browser shows the agent card. /device approves or denies a pairing request. pair set-password sets the approval password. pair set-oidc adds OpenID Connect on /device and the MCP consent page when the issuer, client id, client secret, and allowlist are all set. Unset, those pages stay on the password.
  • Hosted setup. When this machine has no Cloudflare profile, token, or account, or when deploy hits Cloudflare error 1042 after the 30-second wait, a data-center or network error, or an account that cannot take the Worker, name the reason and say "Ask your human to set it up on https://a2a.exposed." A human signs in there. With a Cloudflare login and an account, init deploys the Worker in that account.
  • Library. @a2a-exposed/control-core is this repository. The source entry control/src/core/index.ts exports agent address provisioning, routing, and D1 migrations. The published module is compiled JavaScript and TypeScript declarations. prepare builds it on npm pack and on a file: install. The a2a-exposed CLI package leaves control/ out. A v* tag publishes both packages.

0.7.0 - 2026-10-10

Upgrade: npx -y a2a-exposed@latest deploy (no new D1 migration). Leave WAKE_TARGET_POLICY unset and the agent address still posts to the configured webhook.

  • Wake targets. WAKE_TARGET_POLICY=public-https refuses a wake that is not https, that carries credentials, or whose host is private, loopback, link-local, CGNAT, or a metadata address. A hostname is checked over DNS-over-HTTPS; if that check cannot be completed, the wake is not sent. The connection is made to an address from that check, with TLS for the original hostname, so a later DNS answer cannot point the request at a private address. A redirect is followed only when it stays on the same origin and passes the same check. A cross-origin redirect is not followed, so wake credentials are not sent to another host. A forbidden target is never requested. Leave the setting unset and the agent address still posts to whatever WAKE_WEBHOOK_URL the operator configured.

0.6.0 - 2026-10-10

Upgrade: npx -y a2a-exposed@latest deploy (applies D1 migration 0008_oidc_txns: short-lived OpenID Connect sign-in state). A deployment that set --cli-command / WAKE_CLI_COMMAND keeps its own command. Leave the new approval settings unset and /device stays the password page.

  • Owner checks. Handlers take a context built from the Worker bindings. With optional settings unset, owner checks and outbound peer-token encryption are the same operations as before (existing peers sync ciphertext still decrypts; the owner API uses OWNER_TOKEN). OpenID Connect approval stays off until its issuer, client id, client secret, and allowlist are all set. init does not set any of this.

  • Wake flush. A2A_ENABLE_CRON=1 flushes the agent address database every minute, and requests still flush opportunistically. The daily snapshot cron, when the backup bucket is bound, writes the snapshot and returns before that flush. No D1 migration.

  • Agent address export and optional daily snapshots. npx -y a2a-exposed@latest export prints a JSON dump (GET /owner/export); export --sql prints the same tables as SQL. The dump is the application tables, read in one transaction so a concurrent write cannot tear a task away from its history. Peer-token hashes are included. Outbound peer tokens are ciphertext under the owner token, and that secret is not in the file. The dump includes oidc_txns. A text value that contains a NUL byte is written with char(0), so the SQL script does not contain a raw NUL. The owner token stays a Worker secret. d1_migrations is omitted. Putting a saved dump back into an agent address is a follow-up and is not in this change. A2A_BACKUP_BUCKET=1 adds an R2 binding BACKUP_BUCKET and a daily cron (0 3 * * *) that writes a dated SQL object and deletes dated objects 30 or more days old. Unset: no binding and no extra cron. That daily cron returns before the minute flush. Export adds no D1 migration.

  • Optional OpenID Connect approval. With APPROVAL_OIDC_ISSUER (https), APPROVAL_OIDC_CLIENT_ID, the Worker secret APPROVAL_OIDC_CLIENT_SECRET and APPROVAL_OIDC_ALLOWED_SUBJECTS all set, /device and /oauth/authorize add an identity-provider button beside the password. The start is authorization code + PKCE (S256) and requests the openid and email scopes so the provider returns email and email_verified; state and nonce are stored with the user code and the CSRF token that passed the form check, and are not put in the identity provider's URL as the user code. The callback checks issuer, audience, nonce, expiry and the allowlist (RS256 only), then approves the same way a password does. A sub match is enough. An email match also needs email_verified true. A token with more than one audience, or any azp claim, must set azp to this client id. Deny still needs no login. Unset settings leave both pages as they are today. APPROVAL_METHODS=password keeps the password even when an issuer is set. APPROVAL_METHODS=oidc hides the password only once that config is complete; a partial config keeps the password so the operator is not locked out. Leaving APPROVAL_METHODS unset offers both when the config is complete. npx -y a2a-exposed@latest pair set-oidc writes the non-secret settings into config.env. The client secret is read from APPROVAL_OIDC_CLIENT_SECRET or --secret-stdin and is not saved or printed; the next npx -y a2a-exposed@latest deploy uploads that environment variable. status and pair list name the issuer host when OpenID Connect is on.

  • npx is the one way to run the CLI: npx -y a2a-exposed@latest <command> everywhere: README, both skills and their references, the Worker's default wake hint (WAKE_CLI_COMMAND default), the Claude Code wake text, the 401 pairing hint (data.pairing.cli), the landing page and consent/pairing pages, and the CLI's help and every "run ..." / "next step" line it prints. Agent sandboxes (e.g. Claude Code cloud sessions) block a forced npm i -g a2a-exposed as untrusted code; npx needs no install. @latest rather than a pinned @0.5: deploy copies the Worker template from the CLI it runs, so an older or pinned copy would redeploy an older Worker over a newer one, and wake hints run long after setup should match the current release. A global install stays an optional speed-up on your own machine (npm i -g a2a-exposed@latest, then a2a-exposed <command>). The outdated-version notice now says run npx -y a2a-exposed@latest deploy (with a global install, npm i -g a2a-exposed@latest first); under npx @latest it doesn't appear, because the running copy is the newest.

  • Skills without a global install: npx -y skills add telegraphic-dev/a2a-exposed into the current project is the documented path (--global optional), or the agent reads the SKILL.md files straight from GitHub.

  • Claude Code: don't set up from a cloud session. The README quick start, the setup skill (first thing) and the Claude Code wake reference now say so up front: installs are blocked as untrusted code, tunnels as an ingress risk, there are no Cloudflare credentials and the network allowlist blocks the Worker. Run setup on a laptop or from another agent, add <Worker URL>/mcp as a connector, and the routine only consumes through the connector. The CLI fallback in a routine uses npx instead of a global install in the setup script.

0.5.0 - 2026-10-09

Upgrade: a2a-exposed deploy (applies D1 migrations 0006_mcp: OAuth clients and codes, MCP grants, synced outbound peers; and 0007_cimd: cached client metadata documents). Nothing changes for peers.

  • MCP protocol version 2026-07-28 alongside 2025-11-25, 2025-06-18 and 2025-03-26 (Claude and other clients still using the initialize handshake keep working; the behaviour is chosen per request). The new version is stateless: the version, clientCapabilities and clientInfo come from each request's _meta, and the MCP-Protocol-Version, Mcp-Method and (for tools/call, also Base64-encoded) Mcp-Name headers are required and must match the body (-32020 HeaderMismatch, HTTP 400). New server/discover (supported versions, capabilities, instructions); every result carries resultType: "complete" and the server info in _meta; tools/list keeps its deterministic order and adds ttlMs (1 hour) and cacheScope: "private"; subscriptions/listen is acknowledged with no notification types and closed at once; initialize, ping and other unknown methods get -32601 with HTTP 404; Mcp-Session-Id and Last-Event-ID are ignored; GET and DELETE stay 405. An unknown protocol version (in _meta or the header, either era) gets -32022 UnsupportedProtocolVersion with supported and requested. Not implemented: multi-round-trip requests (input_required), tasks, logging, resources, prompts and list-change notifications (none of the tools need them).

  • OAuth: Client ID Metadata Documents (client_id_metadata_document_supported): a client may use an https URL as its client_id; the Worker fetches the document (public https hosts only, never the Worker itself; every A/AAAA record must be a public address, checked over DNS-over-HTTPS, and the request goes over a connection to that checked address with TLS verified for the host, so DNS rebinding can't redirect it; no redirects, no credentials, 5 s, 5 KB, 30 lookups per hour per IP), checks that it names its own URL, lists valid redirect URIs and uses no client secret, caches it per Cache-Control (errors are not cached), and the consent page shows the host that publishes it. Dynamic client registration stays for older clients (deprecated in MCP) and now accepts and echoes application_type (native or web). The authorization server metadata advertises authorization_response_iss_parameter_supported (the redirect already carried iss, RFC 9207).

  • Fix: an authorization request without the optional resource parameter failed with a server error; the code is now bound to the Worker's MCP URL.

  • Remote MCP server (connector) at /mcp: Claude and other MCP clients can use the agent address as a connector, with no CLI, Node or owner token in the client. MCP Streamable HTTP (stateless JSON, POST /mcp; protocol versions 2025-11-25, 2025-06-18, 2025-03-26), tools inbox, show_task, history, mark_working, reply (with state), send, poll_outbound, list_peers, pairing_requests (read-only); every description carries the untrusted-peer-content and never-approve-pairings rules. Auth is OAuth 2.1 per the MCP authorization spec: protected-resource metadata (/.well-known/oauth-protected-resource/mcp, 401 with WWW-Authenticate: Bearer resource_metadata=...), RFC 8414 metadata (now also listing authorization_endpoint, registration_endpoint, revocation_endpoint, S256), dynamic client registration (public clients, https or loopback redirects, rate limited, capped), authorization code with PKCE S256, resource checked against the MCP URL, rotating refresh tokens, RFC 7009 revocation. The consent page (/oauth/authorize) needs the owner's approval password (same lockout counters as /device), shows the client's claimed name and redirect host, warns on loopback redirects, and has no scripts, a strict CSP and CSRF protection. MCP tokens live in their own table: never accepted as peer or owner tokens, never in a URL, listed by token list (mcp-<client>) and ended by token revoke. Scope: inbox, reply, send to synced peers, read-only listings; no token or pairing administration. On by default for an agent address (inert until you approve a client with your password); --mcp off disables it; never in proxy mode. /health reports mcp. The approval-password link (pair set-password --web) now also works with --pairing-approval off while MCP is on.

  • New peers sync [alias...] / peers unsync <alias>: upload outbound peers (URL and token) to the Worker so the connector's send / poll_outbound can reach them. Tokens are AES-GCM encrypted with a key derived from the owner token (rotating it makes them unreadable until the next sync); peer URLs must be public https.

  • Claude Code: connector first. The setup docs (README, setup skill, wake reference) make the connector the primary path for routines (add <Worker URL>/mcp at claude.ai/settings/connectors; routines include connectors and connector traffic bypasses the environment allowlist). The CLI checklist stays as the fallback. The claude-code wake text tells the session to use the connector's tools and falls back to the CLI requirements; wake set / wake test print both paths.

  • Rate-limited wakes are retried: when the wake endpoint answers 429 or 503 (a routine over its fire limit), the Worker keeps the inbound wake pending and re-sends it after Retry-After (30 s to 1 h), up to 3 times, on the next request to the Worker or the optional cron; then it logs wake_dropped. Previously such a wake was only logged.

  • Agent Skills + Agent Plugins compliance: both skills pass skills-ref validate (frontmatter limited to the agentskills.io allowed keys; version / author / homepage under metadata; Hermes and OpenClaw blocks kept under metadata.hermes / metadata.openclaw). Setup skill body split into skills/a2a-exposed-setup/references/ so SKILL.md stays under the progressive-disclosure size guidance. Root plugin.json targets Agent Plugins 1.0.0 (skills stay in skills/). Vendor manifests coexist: .cursor-plugin/plugin.json, .grok-plugin/plugin.json, .claude-plugin/plugin.json (+ marketplace.json for Claude). Codex/ChatGPT use the portable root plugin.json (extensions.com.openai); no separate .codex-plugin overlay. CI validates skills-ref and the plugin schema.

  • Claude Code routines (claude-code): the setup skill's wake reference has an ordered checklist for routine API triggers (repository source with the skills on the default branch, A2A_BASE_URL / A2A_OWNER_TOKEN on the routine's cloud environment and its risk, network allowlist for the Worker host, CLI in the setup script, a prompt that acts on the routine-fire-payload block, end-to-end verification) and troubleshooting rows for each failure mode. The claude-code wake text now tells a fresh routine session what it needs and to report incomplete setup instead of improvising (not for human-approved pairing wakes, which need no CLI). wake set / wake test with this preset print the same checklist. Upgrade: deploy (Worker wake text only).

0.4.1 - 2026-10-08

Upgrade: a2a-exposed deploy (new Worker owner endpoint; no D1 migration). Prompted by a façade in front of a Hermes A2A server behind Cloudflare Access: with only the Access service token set, every peer call failed with HTTP 502 / -32603, because Hermes also checks its own bearer (UPSTREAM_TOKEN), and nothing said so.

  • Setup checks the upstream's bearer requirement (proxy mode): init/deploy with --upstream read the upstream's agent card (from this machine, with the exported Access service token; never with a bearer) and, unless it declares no bearer / HTTP auth (A2A 1.0 securitySchemes + securityRequirements, 0.3 securitySchemes + security; OAuth 2.0 / OpenID Connect count as bearer; an empty requirement means anonymous access is allowed), require UPSTREAM_TOKEN: exported, piped with the new --upstream-token-stdin, or typed at a hidden prompt when stdin and stderr are a terminal. Never argv. Non-interactive runs exit 1 with what to do. --no-upstream-token is the explicit opt-out (saved as A2A_UPSTREAM_NO_TOKEN, cleared by an uploaded token or --upstream none). A redeploy is not asked again when the Worker already holds UPSTREAM_TOKEN for the same upstream, or the card it fetched declares no bearer. A required API-key, basic or mTLS scheme gets a warning (the façade only presents a bearer).
  • status: Access credential and upstream bearer reported separately (proxy mode): new rows upstream Access (credential configured + fingerprint, or NONE) and upstream bearer (configured + fingerprint, NONE, but the upstream card asks for one, or not needed), plus upstream check (the live check below), peer auth and last failure. An Access-only setup whose upstream card asks for a bearer is a failing next step, not "complete". When UPSTREAM_TOKEN is exported locally, its fingerprint is compared with the Worker's.
  • New upstream verify [--json] (also run by status): checks the façade's whole path layer by layer. (1) Façade peer auth: an unauthenticated JSON-RPC call to the public endpoint must get 401. (2) New owner-authenticated Worker endpoint POST /owner/facade/verify: the Worker calls the upstream with its stored secrets (they never leave the Worker) using a JSON-RPC method that doesn't exist (a2a-exposed/verify-unknown-method), so no task is created, and classifies the answer: reachable (any JSON-RPC answer, typically -32601), access_credentials_missing / access_rejected (Access login redirect, Access 401/403, CF-Access-* headers), upstream_auth_missing / upstream_auth_rejected (401/403 from the agent, including a JSON-RPC body such as Hermes's -32050), tunnel_down (530, Cloudflare error 1033/1016), upstream_unavailable (5xx without JSON-RPC), network, unexpected_response, misconfigured. Output: one row per layer (façade peer auth, Access, upstream bearer, A2A app) and the fix. Exit 1 unless the app answered. Reason codes and HTTP status only, never a secret value; no public probe endpoint.
  • Non-leaky 502 for peers, precise reasons for the operator: an upstream failure now reaches peers as a generic -32603 (This agent is unavailable: its façade is misconfigured; tell its operator, or ... not reachable right now; try again later) that no longer mentions credentials or Access. The Worker logs upstream_error with the reason code and stores the last failed peer call (reason, HTTP status, method, peer, time; D1 settings, no migration), which GET /owner/facade returns (lastFailure, plus upstreamAuth: what the upstream card asks for, scheme names and kinds only) and status shows.
  • Shadowed peer tokens in status: a PEER_<ALIAS>_TOKEN exported in the environment that differs from the token saved in config.env (e.g. a stale value after connect stored a fresh one) now also gets an also: line in status (names only, never values), in addition to the existing connect / send / poll / peers warnings.
  • Cloudflare login blocked / API token hints: when init finds no login, it now says that cf auth login failing with OAuth error: HTTP 403 Forbidden before any code appears is Cloudflare's bot mitigation for datacenter IPs (don't retry; use CLOUDFLARE_API_TOKEN); with a token set it points at IP filters (IPv4 /32 and IPv6 /128), and a token that can't list accounts gets "add User → Memberships → Read, or pass --account-id". Setup skill troubleshooting: account- vs zone-scoped permissions, API error 1001 for a wrong Memberships selector, IP-restricted tokens (overlaps the still-open docs PR #13, which adds the basic API-token path).
  • Pairing with a non-public card: connect --card-url with an http card (left out) and a failed pairing request that carried a Tailnet / LAN card now point at proxy mode (a public façade); the setup skill sends agents whose card isn't public https to proxy mode up front.
  • Removed the leftovers of the rename from a2a-over-webhook (0.4.0): the deprecated alias package alias/a2a-over-webhook/ and its CI pack check and publish step (a v* tag now publishes only a2a-exposed; the versions already on npm stay deprecated there), the fallback to ~/.config/a2a-over-webhook when ~/.config/a2a-exposed doesn't exist (the config directory is always $XDG_CONFIG_HOME/a2a-exposed or ~/.config/a2a-exposed, or A2A_CONFIG_DIR), the old default Worker name for a saved deployment without A2A_WORKER_NAME (the default is always a2a-exposed; init saves the name, and deploy now refuses a hand-edited config with a saved D1 but no Worker name instead of guessing: rerun with --worker-name), the A2A_NO_RENAME_NOTICE variable, and the rename notes in the README, both skills and AGENTS.md. Peer tokens keep their a2aow_ prefix (existing tokens and the token format are unchanged). Breaking for a config still in ~/.config/a2a-over-webhook (the CLI then reports no saved deployment): mv ~/.config/a2a-over-webhook ~/.config/a2a-exposed, or export A2A_CONFIG_DIR=~/.config/a2a-over-webhook.
  • Docs: README (façade credentials, upstream verify, security model), setup skill (the two upstream credentials, troubleshooting rows per reason code), operate skill (502 vs 401, upstream verify, shadowed peer tokens).

0.4.0

Upgrade: npm rm -g a2a-over-webhook && npm i -g a2a-exposed@latest, then a2a-exposed deploy (applies D1 migration 0005_facade_owners.sql, used only in proxy mode).

  • Renamed to a2a-exposed (the project now also exposes agents that already speak A2A, not only webhook agent addresses). npm package and command a2a-exposed; skills a2a-exposed-setup and a2a-exposed; repository telegraphic-dev/a2a-exposed (GitHub redirects the old URL); install with npm i -g a2a-exposed@latest and npx --yes skills add telegraphic-dev/a2a-exposed. Compatibility: the npm package a2a-over-webhook becomes a deprecated alias for one or two minor releases (alias/a2a-over-webhook/, published by the same tag at the same version): it depends on a2a-exposed and keeps the a2a-over-webhook command and npx a2a-over-webhook (wake hints of existing deployments) working, with a stderr notice (A2A_NO_RENAME_NOTICE=1 silences it). Upgrade a global install with npm rm -g a2a-over-webhook && npm i -g a2a-exposed@latest; an existing ~/.config/a2a-over-webhook is used as is when ~/.config/a2a-exposed doesn't exist (nothing is moved); a saved deployment without A2A_WORKER_NAME keeps the old default Worker name, and new deployments default to a2a-exposed. Unchanged: A2A_* / WAKE_* variables, peer tokens (a2aow_ prefix), D1 data and URLs. Wake hints, the 401 pairing hint and OAuth metadata now name npx a2a-exposed and the new repository. The domain a2a.exposed is reserved for future project pages (nothing is served there yet).
  • Public façade for an agent that already speaks A2A on a private network (proxy / expose mode): init|deploy --upstream <https URL> (optional --upstream-card-url, which must be on the --upstream origin because the card is fetched with the upstream credentials: a cross-origin card URL is refused by init/deploy and by the Worker, which never sends UPSTREAM_TOKEN or the Access service token to any other origin; --upstream none switches back) makes the Worker a façade instead of an agent address. It forwards authenticated A2A JSON-RPC (1.0 and 0.3; SendMessage, GetTask, CancelTask) to the upstream through a Cloudflare Tunnel hostname behind Access. The upstream credentials come from the environment only and are uploaded as Worker secrets: UPSTREAM_TOKEN (bearer the façade presents) and UPSTREAM_ACCESS_CLIENT_ID / UPSTREAM_ACCESS_CLIENT_SECRET (Access service token). The peer's token is never forwarded; the peer label goes upstream as X-A2A-Peer. Peers sharing the one upstream identity are isolated by the façade (D1 facade_owners: calls on another peer's task are refused before reaching the upstream, and a message may only name a contextId the façade recorded for that peer: client-chosen or unknown context ids are refused, fail closed). ListTasks is refused, streaming returns -32004, push notification configs are refused with -32003 (the upstream would call the URL from inside the private network, where even a public-looking name like 127.0.0.1.nip.io can resolve to a private address; peers poll GetTask; a relay through the Worker is a follow-up), and an upstream 401/403 or non-JSON answer becomes a 502 JSON-RPC error instead of a 401 to the peer. A private --upstream (Tailnet, LAN, localhost) is refused with the tunnel recipe. Device-flow pairing stays on the façade. New owner endpoint GET /owner/facade (upstream card status, versions, card-leak check, secret fingerprints only); status shows an upstream: row and proxy-mode next steps; /health reports mode.
  • Agent card rewrite: in proxy mode the public card is the upstream's card rewritten for the façade: interfaces only at PUBLIC_URL (one JSONRPC interface per version the upstream lists), the façade's security schemes and OAuth device URLs, upstream name / skills / version / extensions preserved (--agent-* settings win), streaming, pushNotifications and extendedAgentCard false, signatures and unknown fields dropped. Private and upstream URLs (localhost, RFC 1918, 100.64/10, private IPv6 literals such as [::1], [fd..], [fe80..] and IPv4-mapped ones, *.ts.net, *.local, single-label hosts, plain http, URLs that don't parse, the tunnel hostname) are removed from every string; documentationUrl, iconUrl and provider.url are dropped when private. The agent address card now applies the same scrubbing to AGENT_DESCRIPTION, AGENT_SKILLS, PROVIDER_URL and DOCUMENTATION_URL. If the upstream card is unreachable, a settings-based card is served (still public URLs only). Rules are documented in the setup skill.
  • HTML landing page at GET /: a browser gets a small page with the agent name, description and skills (from the served card, so in proxy mode the rewritten one with no private URL), the note that this is an A2A endpoint, and links to the agent card and the /device pairing page (or a note when pairing is off). It has the same frame as /device: no scripts, no external assets, a strict nonce CSP. Requests without Accept: text/html, or with ?format=json, still get JSON, now with description, a2a and pairing alongside name and agentCard.
  • Tailnet agents pairing out: connect --card-url <url> names your card in the pairing request. A private https card (*.ts.net, LAN) is sent as informational: the other agent address's /device page and wake (agentCardPrivate: true) flag it as "not publicly reachable". A non-https card is still left out.
  • Docs: setup skill section "Already have A2A on a Tailnet or LAN: expose it through a public façade" (tunnel + Access recipe, deploy, card rewrite rules, upstream credential model, threat model), README section and security/protocol notes, new troubleshooting rows, deploy.env.example.

0.3.1

  • Stale peer-token variables: a peer token in the environment (PEER_<ALIAS>_TOKEN, a --token-env variable, or A2A_PEER_TOKEN) still overrides the one saved in config.env, but when both are set and differ, send, poll, connect and peers add|list now warn on stderr, naming the variable and how to clear it (unset PEER_<ALIAS>_TOKEN), never either value. connect (and peers add --token-stdin) also warns right after saving a new token that a stale variable would still win on the next send; connect --json adds env_overrides_token: true.

0.3.0

Upgrade: npm i -g a2a-over-webhook@latest, then a2a-over-webhook deploy (applies D1 migrations 0002_wake_budget.sql and 0004_pairing_replace.sql).

  • Password setup over the web (pair set-password --web): the CLI asks the Worker for a one-time link (POST /owner/pairing/password-link, default 15 minutes, configurable 1–60). The human opens /device/setup?t=..., types a password (≥12) twice, and the Worker hashes it with PBKDF2-SHA256. The link is single-use, burned after 5 bad attempts, and a new link invalidates the old one. Same CSP/CSRF as /device. status / pair list show when the password was set and how (web or terminal). Agents must send the link to their human and never open it.
  • Re-pairing without orphan tokens: connect refuses an alias whose token still works, unless --replace (alias --force). With --replace the old token is presented on device_authorization; the Worker stores replaces_label (D1 migration 0004_pairing_replace.sql, applied automatically by deploy) and the presented token's hash (replaces_hash), and, on approval, swaps the token under the same label only if that exact token is still on the label (a rotate or revoke in between downgrades to a new label, so a leaked old token can't take over a rotated one). Older peers keep the old token until their owner revokes it; the CLI says so. Revoked labels are not reused.
  • Expired --no-wait resumes: resuming an expired request exits 1 with "expired before it was approved; nothing was stored" and prints the exact re-run command (every flag, including --alias). Never starts a silent new request.
  • Polling agents see pairing requests: inbox lists pending pairing requests after the tasks (and on stderr with --json); the polling recipes (Hermes, OpenClaw, generic) tell the agent to relay the code and never approve. pair set-password no longer claims every wake carries the link.
  • Node 22 and CLI upgrades: the setup skill and the CLI's version error list mise / nvm / fnm / the official installer. The install recipe is npm i -g a2a-over-webhook@latest (the old command -v || npm i never upgraded). An outdated notice on stderr checks the npm registry at most once a day in a detached process (A2A_NO_UPDATE_CHECK=1 / DO_NOT_TRACK=1 / CI opt out). Skills and cli/package.json versions are kept in sync with the release.
  • Deploy / status friction: init/deploy wait until the agent card is served twice in a row (Cloudflare 1042 while a new workers.dev Worker propagates). status names Cloudflare error codes and treats 1042 as "retry in 30 s". Optional hints (also:) keep the next-step line short; with no wake it no longer mentions a preset. Global --config-dir (and --worker-dir for the Worker project; --dir still works). url exits 1 when unset. connect --json uses not_waiting instead of started. Friendly 401 from send/poll. --workers-logs on|off enables persisted Workers Logs (query strings redacted). Teardown steps are documented (no command).
  • Auth and pairing polish: 401 echoes the JSON-RPC id; WWW-Authenticate carries resource_metadata (RFC 9728) and a different error_description for a bad token vs no token; /.well-known/agent.json is an A2A 0.3-shaped card; token revoke of an unknown label exits 1 with a clear message (text, not raw JSON); pair approve in human mode names deploy --pairing-approval agent; Deny on /device needs no password and never runs PBKDF2; pairing off lists no pending requests; expired device codes return expired_token for 24 h, then invalid_grant.
  • PBKDF2: default stays 100,000 (Workers maximum); range 50,000–100,000 via init/deploy --pbkdf2-iterations (A2A_PBKDF2_ITERATIONS). Each stored hash keeps its own count. Documented in the threat model (online guessing is limited by lockouts; lower only if /device hits Cloudflare error 1102 on the free plan).
  • Worker: migration 0002_wake_budget.sql creates the wake_budget table on D1 databases whose 0001_init.sql came from an earlier build (D1 tracks migrations by file name, so the current 0001 never ran there). Without it, messages to such an agent address failed with -32603 Internal error and sent no wake. It does nothing on newer databases, and deploy applies it even where 0003_device_pairing.sql is already recorded (cf applies every unrecorded file in numeric order). A new test replays these upgrades.
  • Docs: setup skill section on adopting an existing deployment (same Worker, D1 and hostname) without reissuing tokens or re-uploading secrets: deploy keeps the Worker's secrets and applies 0002–0004, then status checks the result. Device-flow pairing is on after the deploy (human approval), or --pairing-approval off.
  • Skills: the setup skill suggests two optional companion skills near its prerequisites, with install commands: mise (telegraphic-dev/mise-skill (opens in a new tab), for Node 22.18+) and cloudflare (cloudflare/skills (opens in a new tab), for Workers, D1, the cf CLI, DNS, Tunnel and Access). The operate skill points to cloudflare for Cloudflare-side troubleshooting. related_skills lists them; the README's install section names both.

0.2.0

  • Tunnel on a workers.dev agent address: tunnel create (and init --workers-dev --tunnel) no longer needs a custom agent address hostname. The wake hostname goes on any zone of the account: the agent address hostname's zone, otherwise the account's only zone (it says which it picked). With several zones it stops and lists them; choose with the new --tunnel-zone <zone> (or --tunnel-hostname). With no zone it says plainly that polling is the option. Zones are listed for the deployment's account only.
  • status command: a read-only summary of the deployment and base URL, an agent-card check done by the CLI (no curl), the wake mode (webhook, tunnel, or none, which means polling), the tunnel state and connector connections, and a next step: line. Exit code 1 when something is broken. init and the card warning now point to it.
  • Safe re-runs: tunnel create on an existing tunnel creates nothing. It re-uploads Worker secrets if they're missing, together with an exported WAKE_WEBHOOK_KEY / WAKE_HMAC_SECRET, and warns if the Worker would still have no webhook auth. A missing connector token file is downloaded again. When an earlier run stopped halfway, it asks for tunnel rm. status flags a local preset (Hermes, OpenClaw) whose Worker sends no webhook auth. If init --tunnel fails at the tunnel step, it says the agent address is deployed and how to continue.
  • Setup skill: decide how wakes reach the agent (public webhook, local-only webhook, or none) before picking the agent address URL. For local-only webhooks, check for a zone and recommend the secure tunnel with either agent address URL, explaining the tradeoff to the user; polling is the fallback. Adds copy-paste polling commands for Hermes (hermes cron create "every 2m" ... --skill a2a-over-webhook --name a2a-inbox, checked against the Hermes source and docs) and OpenClaw (openclaw cron add --every 5m --session isolated ...), with a note about command approval prompts. Also adds resuming with status, moving from polling to the webhook without a redeploy, and new troubleshooting rows.
  • The agent card always advertises the public base URL: the Worker template no longer reads an A2A_PUBLIC_URL override from the deploy environment, so a local webhook, Tailnet or tunnel URL exported by the agent can't end up in the card. The card URL comes only from the custom hostname or the workers.dev URL. The Worker also ignores a non-https or private-network PUBLIC_URL and falls back to the request origin. init and deploy verify and print the deployment's own URL, and warn when an exported A2A_BASE_URL differs. They also warn when the card points elsewhere. status flags both cases (agent card: WRONG URL: ..., next step deploy). This was found in a Hermes setup where the card advertised a Tailnet URL and the agent fixed it by hand.
  • Device-flow pairing (OAuth 2.0 Device Authorization Grant, RFC 8628): agents connect without exchanging tokens in chat.
    • Each agent address serves POST /oauth/device_authorization, POST /oauth/token (authorization_pending, slow_down, access_denied, expired_token, invalid_grant), a /device approval page, and /.well-known/oauth-authorization-server. An approval issues a normal hashed a2aow_ peer token, single-use.
    • Approval is human by default: an approval password set with pair set-password (terminal only, no echo; stored as salted PBKDF2-SHA256), with lockouts. --pairing-approval agent adds pair approve <code>; off disables pairing.
    • New requests wake the agent with kind: "pairing_request". New commands: connect <url> (client; --no-wait, --json), pair list|approve|deny. token list shows tokens created by pairing.
    • The agent card advertises an A2A 1.0 oauth2SecurityScheme with a deviceCode flow next to bearer, and a 401 from the A2A endpoint explains the flow.
    • Flood limits apply per IP and globally. D1 migration 0003_device_pairing.sql (numbered to avoid a 0002 from another branch); run deploy to apply it.
  • Docs: both skills and the README check the endpoint with status instead of curl, because some agent sandboxes (Hermes) flag .dev URLs in shell commands. The README and the operate skill describe the tunnel as needing any zone on the account.

0.1.0

First npm release of the CLI (npm i -g a2a-over-webhook).

  • Worker: public agent card and A2A JSON-RPC endpoint (1.0 primary, 0.3 compatible) with a D1 database; per-peer a2aow_ bearer tokens stored as SHA-256 hashes; push notifications; per-conversation wake debounce and an hourly wake cap.
  • Wake presets: grok-bot, claude-code, openclaw-wake, openclaw-agent, hermes (HMAC-SHA256), generic; wake set|unset|test|preview|fingerprint with masked previews and SHA-256 fingerprints instead of secret values.
  • Deploy: init / deploy with the cf CLI, on a custom domain or a free *.workers.dev URL (--workers-dev-subdomain registers the account subdomain); deploy --workers-dev / --hostname move a deployment between the two and retire the old URL (301 for the card, 410 otherwise).
  • cf auth profiles: --cf-profile / CF_PROFILE for several Cloudflare logins on one machine.
  • Secure tunnel: tunnel create|status|rm publishes a local-only webhook (OpenClaw, Hermes) through a named Cloudflare Tunnel behind a Cloudflare Access app that admits only the Worker's service token.
  • Tasks and outbound: inbox, show, working, reply, history, contexts; peers add|list|rm, send, poll, outbound; token issue|list|revoke|rotate.
  • Skills: a2a-over-webhook-setup (deploy and wake configuration) and a2a-over-webhook (day-to-day), with Hermes and OpenClaw frontmatter metadata.
  • Release tooling: CI on pushes and PRs; tag-triggered npm publish with provenance and a GitHub release.