* feat(core): db-migrate-user-api-keys script
One-shot migration that copies legacy `user-api-key:<provider>:<email>`
and `user-anthropic-api-key:<email>` settings rows into `app_secrets`
(encrypted, scope=user, scopeId=email) and deletes the originals.
Pairs with the secrets-write-path lockdown on PR #343:
`getOwnerApiKey()` reads `app_secrets` first and falls back to the
legacy settings rows for compat. This script clears that compat tail
so the rows don't sit around indefinitely after deploy.
Idempotent — re-running on a clean DB is a no-op. Supports Postgres
(Neon) and SQLite/libsql. Includes --dry-run.
Usage:
DATABASE_URL=postgres://... pnpm action db-migrate-user-api-keys
pnpm action db-migrate-user-api-keys --dry-run
* feat(a2a): cross-platform async+poll for slow handlers; sweep concurrent doc/core updates
The deployed dispatch was getting 'fetch failed' when delegating Slack
analytics questions to other agents, because:
1. Analytics's reply easily exceeded Netlify's 30s gateway timeout for
a single sync POST.
2. The existing async branch in handleSend used a detached promise +
keepAlive() that the Lambda runtime kills the moment the response
is flushed, so the handler never finished and pollers timed out.
Replaces the detached-promise path with the same self-fire pattern the
integration webhook queue uses:
- handleSend (async mode) bakes the verified caller email/org-domain
into the task's metadata column, then fire-and-forgets a signed POST
to /_agent-native/a2a/_process-task on the same deployment and
returns immediately in 'working' state.
- mountA2A() now also mounts /_agent-native/a2a/_process-task. The
route validates the HMAC token (5-min lifetime, A2A_SECRET-keyed,
same scheme as the webhook queue), atomically claims the task via
claimA2ATaskForProcessing(), reconstructs the request context from
metadata, and runs the handler with the fresh function's full
timeout budget.
- callAgent() defaults to async: true so cross-app calls get the
safe behavior out of the box; explicit sync callers can still pass
async: false.
Also picks up concurrent agent updates to docs, package.json,
framework-request-handler, terminal-plugin, and the workspace file.