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.
exportprints a JSON dump of this agent address.export --sqlprints the same tables as SQL. Putting that dump back is a separate step.pair set-oidcadds OpenID Connect approval on/deviceand the MCP consent page. It stays off unless the issuer, client id, client secret and allowlist are all set.- Hosted addresses at
<name>.a2a.exposedare not open. There is nosignuporlogincommand.
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).
sendandpollwork 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 listandinboxprint the dashboard URL whenMANAGE_URLis set.statusprints it on the pairing line.GET /owner/pairingincludesmanageUrlfor that URL. - Deploy flags.
initanddeploytake--manageand-url --event(-url noneclears the saved value).--eventuploads-secret -stdin EVENT_SECRETon that deploy. The secret stays out ofconfig.envand out of the command output. AnEVENT_SECRETargument is refused. - First run.
statuswith nothing deployed says to runinit.initchecks the Cloudflare login before it installs packages. A fresh init runsnpx --yes cf auth whoamifor that check. A later init uses thecfin the Worker folder. - Pairing errors. An unknown or expired code on the owner API stays HTTP 409 and says to run
connectagain.GET /devicefor that code is HTTP 404 with the same wording. The empty/deviceform stays HTTP 200. - MCP. The
pairing_requeststool 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_URLis https andEVENT_SECRETis set, the A2A endpoint POSTs a signed JSON event after a pairing request is created and after it is approved or denied. The body includespending, the number of requests still open.x-a2a-signatureissha256=plus the HMAC-SHA256 of the unix timestamp, a period, and the raw body.verifyEventSignaturechecks that signature and a 5-minute timestamp window.runInboxStepacceptseventUrlandeventSecretand writesEVENT_URLandEVENT_SECRET. On a hosted agent,createTenantstoresEVENT_URLin the tenant config and sealsEVENT_SECRETinsecrets_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
/deviceapproval 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_URLto an https URL and thepairing_requestwake and the MCPinboxline for a pending request includemanageUrl. - Cloudflare provisioning.
runInboxStepacceptsapprovalOidc(issuer,clientId,clientSecret,allowedSubjects). With an https issuer and those fields set, the uploaded Worker receivesAPPROVAL_OIDC_ISSUER,APPROVAL_OIDC_CLIENT_ID,APPROVAL_OIDC_CLIENT_SECRET(a secret),APPROVAL_OIDC_ALLOWED_SUBJECTS, andAPPROVAL_METHODS=oidc.inboxApprovalBindingsreturns that list. A call that omitsapprovalOidckeeps 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.
uninstallprints 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.--dryprints that plan, then-run Nothing was changed.An empty config exits 1 withnothing is deployed from <config dir>. Extra arguments are rejected.--helpprints this command's help. A missing Worker (Cloudflare error 10007) counts as already gone, onuninstalland ontunnel rm.uninstallskips 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 rerununinstall --yesonce, 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. Whileuninstallis deleting the Worker it does not print the standalonetunnel rmline about setting a newWAKE_WEBHOOK_URL. The owner token andWAKE_*values stay in the config directory. A laterinitreuses the owner token. - Tunnel.
tunnel create,tunnel status, andtunnel rmreject extra arguments.tunnel createprints the Access application name and ids, a recap, andtunnel rmas the undo.tunnel rmremoves 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 fortunnel rm. An existing Worker wake URL is named beforetunnel createreplaces it. Rollback deletes only the secrets this run uploaded.tunnel statussays 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. Aninactivetunnel reads asnever connectedin that JSON too.status --jsonuses the same words fortunnel.state.tunnel rmwith nothing saved saysnothing to remove.configprintsA2A_TUNNEL_TOKEN_FILEandA2A_TUNNEL_SECRETS_ATas their values. The generated wake hostname iswake-<word>-<word>-<4 hex>, and the two words are different. - status. For about a minute after
tunnel create, 0 connector connections exits 0 withwaiting for the connector to connectand tells you to startcloudflared tunnel run --tokennow. After that, 0 connections is broken. A tunnel status of-file … inactivereads asnever connected, includingstatus --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,statusandtunnel statussaynothing is deployed from <config dir>. - Pairing page.
/devicesays where the code comes from (connect <url>). A live code with under a minute left saysless 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
inboxcommand, the MCP tool nameinbox,/owner/inbox,mode: "inbox", and the#hosted-inboxanchor stay. Protocol-visible strings that say A2A endpoint: the MCPserverInfo.titlefallback, the OAuthresource_namefallback,mcpInstructions, the wake text heads, and the pairing wake text. Theopenclaw-agenthook name isA2A inbox. The wake text for that hook starts withA2A endpoint:. - Clients. The compatibility table is the source of truth. Its status column is
tested end to endoruntested. - Remove. After you delete a Worker, check that Worker's custom domains and routes, and the zone's DNS.
tunnel rmstill 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 withcf.
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
waitingwith "Waiting for Cloudflare to apply the new permission…". Retries in that window keep the access token from the one refresh for that step.runRemovalStepacceptsconsentedAtorsecondsSinceConsent, an optionalrefreshAccesshook, and an optionalonEventcallback. WithrefreshAccessomitted, the step refreshes the stored grant once. ArefreshAccesshook that throws is recorded as a removal failure. A token response that carriesrefresh_tokenreplaces the stored refresh token. A token response that omitsrefresh_tokenleaves the stored refresh token in place. The refresh request omitsscope. A response that omitsscopeleaves the stored scopes in place. Migration0015_cloudflare_grant_access.sqladds 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 uninstallremoves 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. Manualcf workers deleteandcf d1 deleterun from<config dir>/worker, or aftermkdir -p .cloudflare/cache, socfcan write its account cache. - Tunnel.
tunnel createnames the tunnel, the Access app, and the service tokena2a-exposed wake <worker>, and the DNS hostwake-<word>-<word>-<hex>.<zone>.tunnel rmprints each removed object and deletesWAKE_WEBHOOK_KEYandWAKE_HMAC_SECRETfrom the Worker. The connector machine needs cloudflared. Worker secrets,wake preview, andstatussettle in about 15 to 40 seconds; wait about 30 seconds and check again. Right after the firsttunnel create, while the connector reports 0 connections,statusprints 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 --ttlis 1 to 60 minutes (default 15). Aconnectcode 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, andWAKE_HMAC_SECRETin the shell that runswake previewandwake fingerprint.DIFFERENTmeans 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 rmpasses--forcetocf tunnels connections cleanupandcf tunnels delete. When the tunnel is already gone, the command clears the saved ids and succeeds, so a latertunnel createcan run. - Remove an agent address. The CLI reference lists what
tunnel rmremoves, thencf workers deleteandcf d1 delete.--zerocreates a permanent Zero Trust organization. When-trust -org WAKE_WEBHOOK_URLis already set, runwake unsetbeforetunnel create. - CLI.
wake unsettreats a missing Worker secret (HTTP 404) as already unset and prints one line.deploy --workersoutput stays on the-dev cfCLI. Applying D1 migrations omits the non-interactive confirm line.init --rotateprints the follow-up: update-owner -token A2A_OWNER_TOKEN, thenpeers sync. - README. The quick start covers the profile,
--account, and API token routes.-id
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 reportsthe agent owner denied this request. A denied MCP consent redirect reportsthe 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>andpair 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.
--helplistsinit --rotateand-owner -token --version.--workerand-dir connect --replaceare described in the present tense. CI checks that every a2a-exposed flag named inskills/appears in--help. - status. When
GET /owner/mcpreports enabled and the agent card is reachable,statusprintsclaude 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.mdstays under 150 lines. Cloudflare deploy failures in the setup skill link toreferences/troubleshooting.md. The hosted agent address at https://a2a.exposed is open to everyone; the sentence to say lives once indocs/getting-started.md, and other docs link there. Pricing is at https://a2a.exposed/pricing. In the compatibility table,Supportedmeans tested end to end. Each client is named once, in that table. The day-to-day troubleshooting, token, and messaging notes live underskills/a2a-exposed/references/.metadata.hermesandmetadata.openclawstay nested, withenvVarsand booleanrequired/sensitive. CI pinsskills-refto commit69ef37e. - Plugins. The Claude marketplace entry carries the same version as
plugin.json. The Cursor manifest setslogoandskills.extensions.com.openai.onboardingSkillpoints 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 at5229aad.
0.8.1 - 2026-10-13
npx -y a2a-exposed@latest deploy (no new database migration).
- Health and feedback. The Worker in
control/servesGET /health, plain feedback whenFEEDBACK_REPOandFEEDBACK_GITHUB_TOKENare set, and the static files inpublic/. Fromcontrol/,npm run deployapplies 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.
initdeploys 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, andexportare in the CLI.deploycopies 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
initauthenticates the owner commands (inbox,reply,export, and the rest).GET /in a browser shows the agent card./deviceapproves or denies a pairing request.pair set-passwordsets the approval password.pair set-oidcadds OpenID Connect on/deviceand 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,
initdeploys the Worker in that account. - Library.
@a2a-exposed/control-coreis this repository. The source entrycontrol/src/core/index.tsexports agent address provisioning, routing, and D1 migrations. The published module is compiled JavaScript and TypeScript declarations.preparebuilds it onnpm packand on afile:install. Thea2a-exposedCLI package leavescontrol/out. Av*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-httpsrefuses 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 whateverWAKE_WEBHOOK_URLthe 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 / 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 syncciphertext still decrypts; the owner API usesOWNER_TOKEN). OpenID Connect approval stays off until its issuer, client id, client secret, and allowlist are all set.initdoes not set any of this.Wake flush.
A2A_ENABLE_CRON=1flushes 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 exportprints a JSON dump (GET /owner/export);export --sqlprints 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 includesoidc_txns. A text value that contains a NUL byte is written withchar(0), so the SQL script does not contain a raw NUL. The owner token stays a Worker secret.d1_migrationsis omitted. Putting a saved dump back into an agent address is a follow-up and is not in this change.A2A_BACKUP_BUCKET=1adds an R2 bindingBACKUP_BUCKETand 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 secretAPPROVAL_OIDC_CLIENT_SECRETandAPPROVAL_OIDC_ALLOWED_SUBJECTSall set,/deviceand/oauth/authorizeadd an identity-provider button beside the password. The start is authorization code + PKCE (S256) and requests theopenidandemailscopes so the provider returnsemailandemail_verified;stateandnonceare 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. Asubmatch is enough. Anemailmatch also needsemail_verifiedtrue. A token with more than one audience, or anyazpclaim, must setazpto this client id. Deny still needs no login. Unset settings leave both pages as they are today.APPROVAL_METHODS=passwordkeeps the password even when an issuer is set.APPROVAL_METHODS=oidchides the password only once that config is complete; a partial config keeps the password so the operator is not locked out. LeavingAPPROVAL_METHODSunset offers both when the config is complete.npx -y a2a-exposed@latest pair set-oidcwrites the non-secret settings intoconfig.env. The client secret is read fromAPPROVAL_OIDC_CLIENT_SECRETor--secretand is not saved or printed; the next-stdin npx -y a2a-exposed@latest deployuploads that environment variable.statusandpair listname 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_COMMANDdefault), 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 forcednpm i -g a2a-exposedas untrusted code; npx needs no install.@latestrather than a pinned@0.5:deploycopies 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, thena2a-exposed <command>). The outdated-version notice now saysrun npx -y a2a-exposed@latest deploy(with a global install,npm i -g a2a-exposed@latestfirst); under npx@latestit doesn't appear, because the running copy is the newest.Skills without a global install:
npx -y skills add telegraphic-dev/a2a-exposedinto the current project is the documented path (--globaloptional), or the agent reads theSKILL.mdfiles 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>/mcpas 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
initializehandshake keep working; the behaviour is chosen per request). The new version is stateless: the version,clientCapabilitiesandclientInfocome from each request's_meta, and theMCP-Protocol-Version,Mcp-Methodand (fortools/call, also Base64-encoded)Mcp-Nameheaders are required and must match the body (-32020HeaderMismatch, HTTP 400). Newserver/discover(supported versions, capabilities, instructions); every result carriesresultType: "complete"and the server info in_meta;tools/listkeeps its deterministic order and addsttlMs(1 hour) andcacheScope: "private";subscriptions/listenis acknowledged with no notification types and closed at once;initialize,pingand other unknown methods get-32601with HTTP 404;Mcp-Session-IdandLast-Event-IDare ignored; GET and DELETE stay 405. An unknown protocol version (in_metaor the header, either era) gets-32022UnsupportedProtocolVersion withsupportedandrequested. 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 itsclient_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 perCache-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 echoesapplication_type(nativeorweb). The authorization server metadata advertisesauthorization_response_iss_parameter_supported(the redirect already carriediss, RFC 9207).Fix: an authorization request without the optional
resourceparameter 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), toolsinbox,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 withWWW-Authenticate: Bearer resource_metadata=...), RFC 8414 metadata (now also listingauthorization_endpoint,registration_endpoint,revocation_endpoint, S256), dynamic client registration (public clients, https or loopback redirects, rate limited, capped), authorization code with PKCE S256,resourcechecked 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 bytoken list(mcp-<client>) and ended bytoken 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 offdisables it; never in proxy mode./healthreportsmcp. The approval-password link (pair set-password --web) now also works with--pairingwhile MCP is on.-approval off New
peers sync [alias...]/peers unsync <alias>: upload outbound peers (URL and token) to the Worker so the connector'ssend/poll_outboundcan 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>/mcpat claude.ai/settings/connectors; routines include connectors and connector traffic bypasses the environment allowlist). The CLI checklist stays as the fallback. Theclaude-codewake text tells the session to use the connector's tools and falls back to the CLI requirements;wake set/wake testprint 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 logswake_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/homepageundermetadata; Hermes and OpenClaw blocks kept undermetadata.hermes/metadata.openclaw). Setup skill body split intoskills/a2a-exposed-setup/references/soSKILL.mdstays under the progressive-disclosure size guidance. Rootplugin.jsontargets Agent Plugins 1.0.0 (skills stay inskills/). Vendor manifests coexist:.cursor-plugin/plugin.json,.grok-plugin/plugin.json,.claude-plugin/plugin.json(+ marketplace.json for Claude). Codex/ChatGPT use the portable rootplugin.json(extensions.com.openai); no separate.codex-pluginoverlay. 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_TOKENon the routine's cloud environment and its risk, network allowlist for the Worker host, CLI in the setup script, a prompt that acts on theroutine-fire-payloadblock, end-to-end verification) and troubleshooting rows for each failure mode. Theclaude-codewake 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 testwith 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/deploywith--upstreamread 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.0securitySchemes+securityRequirements, 0.3securitySchemes+security; OAuth 2.0 / OpenID Connect count as bearer; an empty requirement means anonymous access is allowed), requireUPSTREAM_TOKEN: exported, piped with the new--upstream, or typed at a hidden prompt when stdin and stderr are a terminal. Never argv. Non-interactive runs exit 1 with what to do.-token -stdin --nois the explicit opt-out (saved as-upstream -token A2A_UPSTREAM_NO_TOKEN, cleared by an uploaded token or--upstream none). A redeploy is not asked again when the Worker already holdsUPSTREAM_TOKENfor 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 rowsupstream Access(credential configured + fingerprint, or NONE) andupstream bearer(configured + fingerprint,NONE, but the upstream card asks for one, or not needed), plusupstream check(the live check below),peer authandlast failure. An Access-only setup whose upstream card asks for a bearer is a failing next step, not "complete". WhenUPSTREAM_TOKENis exported locally, its fingerprint is compared with the Worker's.- New
upstream verify [--json](also run bystatus): 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 endpointPOST /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 logsupstream_errorwith thereasoncode and stores the last failed peer call (reason, HTTP status, method, peer, time; D1settings, no migration), whichGET /owner/facadereturns (lastFailure, plusupstreamAuth: what the upstream card asks for, scheme names and kinds only) andstatusshows. - Shadowed peer tokens in
status: aPEER_<ALIAS>_TOKENexported in the environment that differs from the token saved inconfig.env(e.g. a stale value afterconnectstored a fresh one) now also gets analso:line instatus(names only, never values), in addition to the existingconnect/send/poll/peerswarnings. - Cloudflare login blocked / API token hints: when
initfinds no login, it now says thatcf auth loginfailing withOAuth error: HTTP 403 Forbiddenbefore any code appears is Cloudflare's bot mitigation for datacenter IPs (don't retry; useCLOUDFLARE_API_TOKEN); with a token set it points at IP filters (IPv4/32and IPv6/128), and a token that can't list accounts gets "add User → Memberships → Read, or pass--account". Setup skill troubleshooting: account- vs zone-scoped permissions, API error-id 1001for 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 --cardwith 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.-url - 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 (av*tag now publishes onlya2a-exposed; the versions already on npm stay deprecated there), the fallback to~/.config/a2a-over-webhookwhen~/.config/a2a-exposeddoesn't exist (the config directory is always$XDG_CONFIG_HOME/a2a-exposedor~/.config/a2a-exposed, orA2A_CONFIG_DIR), the old default Worker name for a saved deployment withoutA2A_WORKER_NAME(the default is alwaysa2a-exposed;initsaves the name, anddeploynow refuses a hand-edited config with a saved D1 but no Worker name instead of guessing: rerun with--worker), the-name A2A_NO_RENAME_NOTICEvariable, and the rename notes in the README, both skills andAGENTS.md. Peer tokens keep theira2aow_prefix (existing tokens and the token format are unchanged). Breaking for a config still in~/.config/a2a-over-webhook(the CLI then reportsno saved deployment):mv ~/.config/a2a-over-webhook ~/.config/a2a-exposed, orexport 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; skillsa2a-exposed-setupanda2a-exposed; repositorytelegraphic-dev/a2a-exposed(GitHub redirects the old URL); install withnpm i -g a2a-exposed@latestandnpx --yes skills add telegraphic-dev/a2a-exposed. Compatibility: the npm packagea2a-over-webhookbecomes a deprecated alias for one or two minor releases (alias/a2a-over-webhook/, published by the same tag at the same version): it depends ona2a-exposedand keeps thea2a-over-webhookcommand andnpx a2a-over-webhook(wake hints of existing deployments) working, with a stderr notice (A2A_NO_RENAME_NOTICE=1silences it). Upgrade a global install withnpm rm -g a2a-over-webhook && npm i -g a2a-exposed@latest; an existing~/.config/a2a-over-webhookis used as is when~/.config/a2a-exposeddoesn't exist (nothing is moved); a saved deployment withoutA2A_WORKER_NAMEkeeps the old default Worker name, and new deployments default toa2a-exposed. Unchanged:A2A_*/WAKE_*variables, peer tokens (a2aow_prefix), D1 data and URLs. Wake hints, the 401 pairing hint and OAuth metadata now namenpx a2a-exposedand the new repository. The domaina2a.exposedis 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, which must be on the-card -url --upstreamorigin because the card is fetched with the upstream credentials: a cross-origin card URL is refused byinit/deployand by the Worker, which never sendsUPSTREAM_TOKENor the Access service token to any other origin;--upstream noneswitches 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) andUPSTREAM_ACCESS_CLIENT_ID/UPSTREAM_ACCESS_CLIENT_SECRET(Access service token). The peer's token is never forwarded; the peer label goes upstream asX-A2A-Peer. Peers sharing the one upstream identity are isolated by the façade (D1facade_owners: calls on another peer's task are refused before reaching the upstream, and a message may only name acontextIdthe façade recorded for that peer: client-chosen or unknown context ids are refused, fail closed).ListTasksis 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 like127.0.0.1.nip.iocan resolve to a private address; peers pollGetTask; 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 endpointGET /owner/facade(upstream card status, versions, card-leak check, secret fingerprints only);statusshows anupstream:row and proxy-mode next steps;/healthreportsmode. - 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,pushNotificationsandextendedAgentCardfalse,signaturesand 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,iconUrlandprovider.urlare dropped when private. The agent address card now applies the same scrubbing toAGENT_DESCRIPTION,AGENT_SKILLS,PROVIDER_URLandDOCUMENTATION_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/devicepairing 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 withoutAccept: text/html, or with?format=json, still get JSON, now withdescription,a2aandpairingalongsidenameandagentCard. - Tailnet agents pairing out:
connect --cardnames your card in the pairing request. A private https card (-url <url> *.ts.net, LAN) is sent as informational: the other agent address's/devicepage 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--tokenvariable, or-env A2A_PEER_TOKEN) still overrides the one saved inconfig.env, but when both are set and differ,send,poll,connectandpeers add|listnow warn on stderr, naming the variable and how to clear it (unset PEER_<ALIAS>_TOKEN), never either value.connect(andpeers add --token) also warns right after saving a new token that a stale variable would still win on the next-stdin send;connect --jsonaddsenv_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 listshow when the password was set and how (weborterminal). Agents must send the link to their human and never open it. - Re-pairing without orphan tokens:
connectrefuses an alias whose token still works, unless--replace(alias--force). With--replacethe old token is presented ondevice_authorization; the Worker storesreplaces_label(D1 migration0004_pairing_replace.sql, applied automatically bydeploy) 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
--noresumes: 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-wait --alias). Never starts a silent new request. - Polling agents see pairing requests:
inboxlists 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-passwordno 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 oldcommand -v || npm inever 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/CIopt out). Skills andcli/package.jsonversions are kept in sync with the release. - Deploy / status friction:
init/deploywait until the agent card is served twice in a row (Cloudflare 1042 while a new workers.dev Worker propagates).statusnames 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(and-dir --workerfor the Worker project;-dir --dirstill works).urlexits 1 when unset.connect --jsonusesnot_waitinginstead ofstarted. Friendly 401 fromsend/poll.--workersenables persisted Workers Logs (query strings redacted). Teardown steps are documented (no command).-logs on|off - Auth and pairing polish: 401 echoes the JSON-RPC
id;WWW-Authenticatecarriesresource_metadata(RFC 9728) and a differenterror_descriptionfor a bad token vs no token;/.well-known/agent.jsonis an A2A 0.3-shaped card;token revokeof an unknown label exits 1 with a clear message (text, not raw JSON);pair approvein human mode namesdeploy --pairing; Deny on-approval agent /deviceneeds no password and never runs PBKDF2; pairingofflists no pending requests; expired device codes returnexpired_tokenfor 24 h, theninvalid_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/devicehits Cloudflare error 1102 on the free plan). - Worker: migration
0002_wake_budget.sqlcreates thewake_budgettable on D1 databases whose0001_init.sqlcame from an earlier build (D1 tracks migrations by file name, so the current0001never ran there). Without it, messages to such an agent address failed with-32603 Internal errorand sent no wake. It does nothing on newer databases, anddeployapplies it even where0003_device_pairing.sqlis 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:
deploykeeps the Worker's secrets and applies0002–0004, thenstatuschecks the result. Device-flow pairing is on after the deploy (humanapproval), 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+) andcloudflare(cloudflare/skills (opens in a new tab), for Workers, D1, thecfCLI, DNS, Tunnel and Access). The operate skill points tocloudflarefor Cloudflare-side troubleshooting.related_skillslists them; the README's install section names both.
0.2.0
- Tunnel on a workers.dev agent address:
tunnel create(andinit --workers) 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-dev --tunnel --tunnel(or-zone <zone> --tunnel). With no zone it says plainly that polling is the option. Zones are listed for the deployment's account only.-hostname statuscommand: a read-only summary of the deployment and base URL, an agent-card check done by the CLI (nocurl), the wake mode (webhook, tunnel, or none, which means polling), the tunnel state and connector connections, and anext step:line. Exit code 1 when something is broken.initand the card warning now point to it.- Safe re-runs:
tunnel createon an existing tunnel creates nothing. It re-uploads Worker secrets if they're missing, together with an exportedWAKE_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 fortunnel rm.statusflags a local preset (Hermes, OpenClaw) whose Worker sends no webhook auth. Ifinit --tunnelfails 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 withstatus, 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_URLoverride 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-networkPUBLIC_URLand falls back to the request origin.initanddeployverify and print the deployment's own URL, and warn when an exportedA2A_BASE_URLdiffers. They also warn when the card points elsewhere.statusflags both cases (agent card: WRONG URL: ..., next stepdeploy). 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/deviceapproval page, and/.well-known/oauth-authorization-server. An approval issues a normal hasheda2aow_peer token, single-use. - Approval is
humanby default: an approval password set withpair set-password(terminal only, no echo; stored as salted PBKDF2-SHA256), with lockouts.--pairingadds-approval agent pair approve <code>;offdisables pairing. - New requests wake the agent with
kind: "pairing_request". New commands:connect <url>(client;--no,-wait --json),pair list|approve|deny.token listshows tokens created by pairing. - The agent card advertises an A2A 1.0
oauth2SecuritySchemewith adeviceCodeflow next tobearer, 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 a0002from another branch); rundeployto apply it.
- Each agent address serves
- Docs: both skills and the README check the endpoint with
statusinstead ofcurl, because some agent sandboxes (Hermes) flag.devURLs 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|fingerprintwith masked previews and SHA-256 fingerprints instead of secret values. - Deploy:
init/deploywith thecfCLI, on a custom domain or a free*.workers.devURL (--workersregisters the account subdomain);-dev -subdomain deploy --workers/-dev --hostnamemove a deployment between the two and retire the old URL (301 for the card, 410 otherwise). - cf auth profiles:
--cf/-profile CF_PROFILEfor several Cloudflare logins on one machine. - Secure tunnel:
tunnel create|status|rmpublishes 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) anda2a-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.