Skip to content

feat(acp): pair the extension with the backend instead of rejecting it (Phase 10A) - #70

Draft
BOTOOM wants to merge 1 commit into
devin/1786237834-acp-phase9from
devin/1786756205-acp-phase10a
Draft

feat(acp): pair the extension with the backend instead of rejecting it (Phase 10A)#70
BOTOOM wants to merge 1 commit into
devin/1786237834-acp-phase9from
devin/1786756205-acp-phase10a

Conversation

@BOTOOM

@BOTOOM BOTOOM commented Aug 15, 2026

Copy link
Copy Markdown
Owner

Description

A locally loaded extension can never satisfy ACP_EXTENSION_ORIGIN, so the gateway rejected every
WebSocket upgrade (403 WebSocket origin is not allowed) and the ACP modal stayed empty because
ui/agents.list never answered. This adds trust-on-first-use pairing so the connection works with
no configuration, without loosening the check.

POST /acp/pair          first chrome-extension:// origin -> { origin, token }   (persisted 0600)
                        same origin again              -> same token
                        another extension              -> 409 pairing_conflict
                        page origin                    -> 403 pairing_rejected
GET  /acp (upgrade)     requires paired origin AND token, or an env override

The token travels as the devmentorai-pairing.<token> WebSocket subprotocol (echoed via
handleProtocols) so it never lands in request logs; a query parameter is still accepted for
native clients. AcpPairingStore keeps ~/.devmentorai/pairing.json at mode 0600 and compares
tokens with timingSafeEqual; pnpm acp:unpair forgets the pairing.

Two related fixes so the failure mode is visible instead of silent:

  • REST no longer accepts every origin (cors { origin: true }) — extension origins, loopback and
    the configured overrides only — so the HTTP policy is no longer laxer than the WebSocket one.
  • AcpClient raises AcpPairingError with the recovery command (pnpm acp:unpair /
    ACP_EXTENSION_ORIGIN=<origin>), request() rejects instead of hanging when there is no socket,
    and AcpCatalogView renders the failure instead of dropping it, which is what produced the empty
    modal.

ACP_EXTENSION_ORIGIN / ACP_ALLOWED_ORIGINS keep working as explicit overrides and skip pairing.
Phases 10B–10E of docs/specs/acp/05-agent-ux-plan.md (Enable model, declarative auth, agent UI
redesign) follow in the next PR; the plan document itself lands here.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Dependency update

Related Issues

Checklist

  • My code compiles without errors (pnpm typecheck)
  • Linter passes (pnpm lint)
  • Tests pass (pnpm test)
  • I have added tests for new functionality (if applicable)
  • I have updated documentation (if applicable)
  • My changes follow the project coding conventions

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

@BOTOOM BOTOOM self-assigned this Aug 15, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@vercel

vercel Bot commented Aug 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
devmentorai-website-cli Ready Ready Preview Aug 15, 2026 1:20am

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Phase 10A verified end to end (extension ↔ backend pairing)

Tested on devin/1786756205-acp-phase10a with the backend on Node 22, ACP_FIXTURE_AGENT=1, and no ACP_EXTENSION_ORIGIN/ACP_ALLOWED_ORIGINS — i.e. exactly the configuration that used to fail with 403 WebSocket origin is not allowed and an empty ACP modal. Driven through the Chrome MV3 side panel.

✅ Clean state pairs, connects, and the ACP agents modal is populated

After pnpm acp:unpair, reopening the side panel writes ~/.devmentorai/pairing.json with the extension origin plus a 64-hex token, the /acp WS upgrade succeeds (no 403 /acp in the backend log), and the modal lists 6 agents plus the fixture profile instead of being empty.

ACP agents modal populated

Chat golden path over ACP with the deterministic fixture agent: session created from the modal, prompt sent, streamed message → thought → tool call → permission prompt → done, tool execute · completed, plan and usage rendered.

ACP chat golden path

✅ Negative path: a second extension is refused with an actionable error

Direct probes against the running backend:

Attempt Result
POST /acp/pair, Origin: chrome-extension://someotherid… 409 pairing_conflict (names pairedOrigin)
WS /acp, foreign extension origin + valid token 403 WebSocket origin is not allowed
POST /acp/pair, Origin: https://example.com 403 pairing_rejected
WS /acp, correct origin, no token 403

With pairing.json pointing at a different extension, the side panel shows an actionable error rather than an empty modal:

Pairing conflict error

✅ pnpm acp:unpair restores pairing and a working session

pnpm acp:unpair plus a panel reload re-pairs to the real extension origin, clears the error, lists the agents again, and a fresh ACP session streams to completion with the tool call finishing.

Re-paired

ℹ️ Known-pending (Phase 10D) — modal styling in dark mode

Captured 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.

Modal in dark mode

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant