feat(acp): pair the extension with the backend instead of rejecting it (Phase 10A) - #70
feat(acp): pair the extension with the backend instead of rejecting it (Phase 10A)#70BOTOOM wants to merge 1 commit into
Conversation
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Phase 10A verified end to end (extension ↔ backend pairing)Tested on ✅ Clean state pairs, connects, and the ACP agents modal is populatedAfter Chat golden path over ACP with the deterministic fixture agent: session created from the modal, prompt sent, streamed message → thought → tool call → permission prompt → ✅ Negative path: a second extension is refused with an actionable errorDirect probes against the running backend:
With ✅ pnpm acp:unpair restores pairing and a working session
ℹ️ Known-pending (Phase 10D) — modal styling in dark modeCaptured for reference, not a blocker for this PR: agent card titles are low-contrast in dark mode and the profile inputs/selects are unstyled and overflow the panel width. One observation for a follow-up phase: the side-panel header status dot tracks HTTP backend health, not the ACP WebSocket — it stayed green while ACP was refused during the conflict test, so it can mask a pairing failure. |
Description
A locally loaded extension can never satisfy
ACP_EXTENSION_ORIGIN, so the gateway rejected everyWebSocket upgrade (
403 WebSocket origin is not allowed) and the ACP modal stayed empty becauseui/agents.listnever answered. This adds trust-on-first-use pairing so the connection works withno configuration, without loosening the check.
The token travels as the
devmentorai-pairing.<token>WebSocket subprotocol (echoed viahandleProtocols) so it never lands in request logs; a query parameter is still accepted fornative clients.
AcpPairingStorekeeps~/.devmentorai/pairing.jsonat mode0600and comparestokens with
timingSafeEqual;pnpm acp:unpairforgets the pairing.Two related fixes so the failure mode is visible instead of silent:
cors { origin: true }) — extension origins, loopback andthe configured overrides only — so the HTTP policy is no longer laxer than the WebSocket one.
AcpClientraisesAcpPairingErrorwith the recovery command (pnpm acp:unpair/ACP_EXTENSION_ORIGIN=<origin>),request()rejects instead of hanging when there is no socket,and
AcpCatalogViewrenders the failure instead of dropping it, which is what produced the emptymodal.
ACP_EXTENSION_ORIGIN/ACP_ALLOWED_ORIGINSkeep working as explicit overrides and skip pairing.Phases 10B–10E of
docs/specs/acp/05-agent-ux-plan.md(Enable model, declarative auth, agent UIredesign) follow in the next PR; the plan document itself lands here.
Type of Change
Related Issues
Checklist
pnpm typecheck)pnpm lint)pnpm test)Backend 190 passed, extension 177 passed, E2E 53 passed / 2 skipped (the two known Chromium
clipboard cases). New coverage: first pairing, repeated pairing, second extension rejected, env
override without token, page origin refused, file permissions and reset, plus the extension-side
subprotocol, actionable conflict error and no-pairing fallback.
Screenshots (if applicable)
Link to Devin session: https://app.devin.ai/sessions/bab32da5729e4a95a9cb79f1648f005e
Requested by: @BOTOOM