Skip to content

Publish the ACP bridge as @get-bb/plugin-sdk/provider-bridge/acp - #2228

Closed
SawyerHood wants to merge 1 commit into
ws2b-acp-3-agent-dialectsfrom
ws2b-acp-4-publish-the-kit
Closed

Publish the ACP bridge as @get-bb/plugin-sdk/provider-bridge/acp#2228
SawyerHood wants to merge 1 commit into
ws2b-acp-3-agent-dialectsfrom
ws2b-acp-4-publish-the-kit

Conversation

@SawyerHood

@SawyerHood SawyerHood commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #2216.

What was wrong

bb's ACP bridge is already generic — the agent to launch arrives per command in providerOptions.acpLaunchSpec, and one implementation serves Cursor, opencode, omp, Grok Build and Hermes Agent. But it lived inside plugins/provider-acp/src/**, where no other plugin could reach it. A third party wanting to add an ACP agent (Amp) had to vendor a bridge or ask bb to add a customAcpAgents config entry. That is the first-party privilege this workstream exists to delete.

What changed

The implementation moves to packages/provider-bridge-acp (a git mv; the diff is renames plus import rewrites). It no longer imports the plugin SDK — it imports the private packages the SDK re-exports (@bb/provider-bridge-protocol, @bb/provider-bridge-protocol/bridge-kit, @bb/domain, @bb/host-daemon-contract) — because the SDK now depends on it.

The SDK publishes ./provider-bridge/acp: packages/plugin-sdk/src/provider-bridge-acp.ts, a hand-curated named-export module (never export *), with every value carrying the experimental_ prefix:

  • experimental_acpProviderBridge — the bridge a plugin re-exports from its bb.host artifact
  • experimental_registerAcpDialect / experimental_resolveAcpDialect / experimental_acpDialectIds and the shipped dialects — the dialect hooks
  • experimental_acpProfileFromLaunchSpec, type AcpAgentProfile — the launch profile
  • experimental_ACP_TOOL_KINDS / _STATUSES / _PROTOCOL_VERSION and the wire types a dialect reads
  • experimental_parseAcpAgentModelLines / _buildAcpAgentModelCatalog / _splitAcpPrimaryModels — the model picker from an agent's --list-models

Plumbing: the exports entry, build-runtime.mjs (a new esbuild entry, zod external), build-bundled-dts.mjs (a new bundled .d.ts), the ordered-key assertion in package-exports.test.ts, and an entry in docs/api_to_audit.md naming what to audit before the prefix comes off.

The first-party plugin is now what a third-party plugin is: server.ts (registrations), icons/, and src/host.ts — one line:

export { experimental_acpProviderBridge as experimental_providerBridge } from "@get-bb/plugin-sdk/provider-bridge/acp";

Its four @bb/* devDependencies are gone, and public-sdk-only.test.ts — the same guard the echo canary carries (#2189) — walks every file in the package and fails on any private import or any specifier outside the allowlist.

The kit's one real privilege is gone. The G1 ratchet caught it during the move: provider-maintenance.ts branched on the bb provider id "acp-cursor" in five places to decide sign-in, installation, account and usage. That is now AcpMaintenanceDialect, an optional surface on the agent's dialect: CURSOR_ACP_MAINTENANCE hangs off the cursor dialect, a plugin registering Cursor under any id gets it, and a plugin bringing its own agent supplies its own. The generic path reports only whether the executable exists, as it must.

How I verified

gate result
typecheck (whole repo) 75/75 tasks
test --filter=@bb/provider-bridge-acp 15 files, 230 tests (12-rule scripted conformance + 8 recorded acp-cursor cells included)
test --filter=bb-plugin-provider-acp 2 files, 9 tests (the SDK-only guard + the #1688 model-policy test, which now reaches the kit through the published subpath)
test --filter=@get-bb/plugin-sdk 16 files, 127 tests
test --filter=@bb/provider-parity --force 43/43, all 10 replayable acp-cursor cells reproduce their recording through the moved bridge
test --filter=@bb/plugin-build --force 7 files, 42 tests
node scripts/check-provider-literal-ratchet.mjs OK: 148 references across 40 core files — unchanged

Two guards are new and are the ones worth reading:

  1. The artifact really builds from the published bundle. builtin-host-artifacts.test.ts now builds the ACP plugin's host artifact the way the daemon does — inlining the SDK's published dist/provider-bridge-acp.js from the plugin's own node_modules — and imports the result, asserting it is a valid provider bridge. A kit that only resolved through the workspace source condition would pass every other test here and fail this one.
  2. The SDK-only guard has teeth. I added import type { ThreadEvent } from "@bb/domain" to src/host.ts and confirmed it fails (src/host.ts imports private packages: expected [ '@bb/domain' ] to deeply equal []), then reverted.

On G1: packages/provider-bridge-acp joins plugins/provider-* in the ratchet's carve-out, because it is the same kind of code — a provider implementation, which is allowed to name its own provider — moved under packages/ only so the SDK can re-export it. A fixture test pins that the carve-out is exactly that path and does not leak to its neighbours (packages/provider-bridge-protocol still counts). The carve-out hides nothing today: after the maintenance fix the kit contains no bb provider id at all — the five remaining regex hits are a filesystem path segment ("cursor" in ~/.cursor/auth.json), the synthetic model sentinel "acp-default", the synthetic item id "acp-permission", and the executable name "cursor-agent" in the dialect table.

Spike §8, stated plainly: I did not record a fresh turn-tools cell per installed agent in this layer. Only grok and cursor-agent are installed on this machine — opencode, gemini and hermes are not — and recording needs a dev app, which the machine cannot host right now without disturbing two other resident dev apps and the perf gate. The dialect rules this stack commits to are backed by the live wire logs in #2211/#2216 rather than by fresh cells; a fresh cursor cell would additionally carry acpDialect and let the recorded oracle exercise the dialect, which today it cannot (the committed recordings predate the field).

AGENT GENERATED: by Claude Opus 5

bb runs every ACP agent through one generic bridge: the agent to launch
arrives per command in the provider options, so nothing in it is
first-party. It lived inside the first-party plugin, where no other plugin
could reach it, and a third party wanting to add an ACP agent had to vendor
a bridge or ask bb for a config entry.

The implementation moves to packages/provider-bridge-acp and the plugin SDK
publishes a curated surface at ./provider-bridge/acp: the bridge, the
dialect hooks, the launch-profile type, the protocol vocabularies and the
model-catalog helpers. The first-party plugin is now registrations, icons
and a one-line host entry that re-exports the kit — exactly what a
third-party plugin writes — and public-sdk-only.test.ts proves it takes no
shortcut: no file in the plugin may import a private @bb/* package.

The move also removed the kit's one real privilege, which the G1 ratchet
caught: provider maintenance (sign-in, install, account, usage) branched on
the bb provider id "acp-cursor". It is now an optional surface on the agent's
dialect, so a plugin registering Cursor under any id gets it and a plugin
bringing its own agent can supply its own.

Co-Authored-By: Claude <noreply@anthropic.com>
@SawyerHood

Copy link
Copy Markdown
Collaborator Author

Coordinator review — WS2b layer 4 (#2228)

PASS against REVIEW-GATE.md. Do not merge: the stack accumulates until the full migration is done.

Verified on the branch

  • The kit lives in packages/provider-bridge-acp and the SDK publishes it as @get-bb/plugin-sdk/provider-bridge/acp with bundled types. The SDK depends on the kit with workspace:*.
  • The first-party plugin now has the canary shape: server.ts imports only @get-bb/plugin-sdk; src/host.ts re-exports the bridge from the public subpath; package.json depends only on the SDK and zod. A third party gets the same surface.
  • All 9 export statements carry the experimental_ prefix. docs/api_to_audit.md has the section.

Note for stabilization (no action now)

experimental_CURSOR, experimental_GROK, and experimental_GENERIC publish vendor dialect profiles from the kit. This is protocol-adapter knowledge and any plugin can use it, so it is not a first-party privilege. The stabilization audit decides whether the profiles stay in the kit or move to the plugin that owns the agents.

AGENT GENERATED: by Claude (coordinator)

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