Skip to content

Add generic provider env contribution hook for plugins - #3035

Merged
SawyerHood merged 5 commits into
mainfrom
bb/provider-env-contribution
Sep 4, 2026
Merged

Add generic provider env contribution hook for plugins#3035
SawyerHood merged 5 commits into
mainfrom
bb/provider-env-contribution

Conversation

@SawyerHood

Copy link
Copy Markdown
Collaborator

Stack layer 1 of 3 (bottom). No prerequisite; layers 2 (account-pool plugin) and 3 (pool wiring) build on it.

Human comments

What was wrong

Plugins could register providers and derive provider options, but there was no validated, per-provider hook for contributing environment variables on each daemon command. As a result the server had nothing to resolve, the versioned host wire carried no contribution metadata, the daemon could only build its fixed shell environment, and Claude did not rebuild its resident SDK session when a turn's merged environment changed. This implements BB-89.

What changed

  • Added bb.providers.experimental_contributeEnv(providerId, resolve) with public context and entry types, zod boundary validation, fake-host support, Plugin Guide coverage, the API audit entry, and Plugin API map inventory. The Plugin SDK version is now 0.4.44.
  • Added isolated five-second server resolution on every session and turn command, deterministic first-contributor-wins conflict handling, and plugin source attribution without logging values.
  • Added required contributedEnv command payloads and bumped HOST_DAEMON_PROTOCOL_VERSION to 180.
  • Merged contributions after thread shell variables on the daemon, expanded serverPath values against the daemon's loopback BB_SERVER_URL, and persisted provider.env-resolved events with contributed secrets masked. If that base URL is unavailable, the daemon drops only the unresolved entry, records it as masked, and emits a configuration warning naming the variable and plugin instead of preventing thread startup.
  • Passed the merged environment through Codex, Pi, and ACP with contribution precedence over ACP static launch variables.
  • Added stable Claude environment signatures and explicit restart-note metadata. A changed turn environment closes and resumes the same provider session id, preserves conversation context, and emits the visible note Execution settings changed; the Claude session was rebuilt to apply them. Ordinary context-preserving session replacements remain hidden.
  • Added timeline rendering and regression coverage across SDK validation, server resolution, host contracts, runtime precedence/masking/event suppression, provider bridges, and the fake plugin host.

How you verified

  • node .github/workflows/check-plugin-sdk-version.mjs
  • pnpm exec turbo run typecheck test --filter=@get-bb/plugin-sdk --filter=@bb/plugin-api-map --filter=@bb/domain --filter=@bb/host-daemon-contract --filter=@bb/thread-view --filter=@bb/provider-bridge-protocol --filter=@bb/agent-runtime --filter=@bb/provider-bridge-acp --filter=bb-plugin-provider-codex --filter=bb-plugin-provider-claude-code --filter=bb-plugin-provider-pi --filter=@bb/server --filter=@bb/host-daemon --continue --force (all typechecks passed; 33/34 test tasks passed, with one load-sensitive Pi test timing out)
  • pnpm exec turbo run test --filter=bb-plugin-provider-pi --force (21 files and 121 tests passed sequentially, including the timed-out test)
  • After the review fixes and rebase, pnpm exec turbo run typecheck test --filter=@bb/agent-runtime --filter=bb-plugin-provider-claude-code --continue --force passed both typechecks, 22 agent-runtime files / 319 tests, and 25 Claude files / 357 tests.
  • After the rebase, pnpm exec turbo run test:integration --filter=@bb/agent-runtime --force passed 9 files and 64 tests, including the refreshed live Claude coverage.
  • pnpm parity --old <dbc16017c9 checkout> --new . --allowlist <empty external allowlist> (48 passed, 0 failed, 4 process-only cells skipped)
  • Ran scripts/bb-dev-app current, installed a throwaway path plugin, and verified Claude Code, Codex, and ACP Cursor received the marker, secret, and loopback-expanded URL while the event masked the secret.
  • Reloaded the plugin with a changed marker and verified the same Claude provider session preserved context, observed the new environment, and displayed the required rebuild note. Captured and attached a real UI screenshot plus a redacted e2e report to BB-89.

Fixes BB-89

AGENT GENERATED

@SawyerHood
SawyerHood force-pushed the bb/provider-env-contribution branch from 56a5f8d to 969ab39 Compare September 4, 2026 04:39
@SawyerHood
SawyerHood marked this pull request as ready for review September 4, 2026 05:14
@SawyerHood
SawyerHood merged commit 8333ba0 into main Sep 4, 2026
29 checks passed
@SawyerHood
SawyerHood deleted the bb/provider-env-contribution branch September 4, 2026 05:31
SawyerHood added a commit that referenced this pull request Sep 4, 2026
Stack layer 3 of 3 (top). Prerequisites: #3035 (provider env
contribution hook) and #3036 (Account Pool hub) below it in this stack.

## Human comments

## What was wrong

The Account Pool hub existed, but Claude Code sessions were not routed
to it
automatically: the plugin still exposed one global bearer key, had no
per-thread bypass, and could not replace an unauthenticated machine's
local
Claude health with the readiness supplied by an enabled pool account.
This is
the BB-90 stack layer on `bb/account-pool-hub` and
`bb/provider-env-contribution`.

## What changed

- Registered the Account Pool's Claude Code environment contribution
with the
exact server-relative hub route and a secret bearer token minted lazily
per
machine. The resolver contributes nothing when no account is enabled or
the
  thread is bypassed.
- Replaced the global hub key with per-machine 0600 token files loaded
once
into a single-process cache, constant-time bearer matching across every
current and grace token, serialized mint/use/rotation updates, a
ten-minute
rotation grace window, and removal of the obsolete global key file.
Last-use
persistence is throttled to once per machine per minute, expired grace
tokens
  are pruned with the next write, and status never returns token values.
- Added `bb pool token rotate --machine <id-or-name>` and
`bb pool bypass <thread-id> [--off]`, with matching plugin RPC
operations and
  discoverability updates in the CLI skill, generated guide source, and
  configuration docs.
- Tracked the last 24 hours of routed threads and made unsafe
disablement
visible in `bb pool status`, including proxied hosts that are relying on
the
pool, plus a plugin warning log when a disable leaves those threads on
hosts
without usable local Claude credentials. The dispose-time inspection is
  guarded against failures and bounded to two seconds.
- Added the single experimental SDK member
`bb.providers.experimental_contributeEnvHealth`, with fake-host support,
Plugin Guide documentation, API audit notes, API map inventory, and SDK
version 0.4.45. The server presents an `unauthenticated` or `expired`
Claude
provider as ready/`Proxied` only when the same live plugin also has an
env
  resolver and the Account Pool has an enabled account.
- Reused the lower stack layers' env command payload and hub drain. This
PR
  does not change the server-to-daemon wire, so it does not bump
  `HOST_DAEMON_PROTOCOL_VERSION`.

## Not in this PR

- BB's plugin SDK has no system-notice append surface or fitting backend
thread
action. `bb.sdk.threads.send` would create a real provider turn, so this
PR
does not fake the requested disable-time thread notice or bypass action.
The
safety gap is instead explicit in `bb pool status` and the disable log.
- Disabling a plugin reloads the Claude bridge, which rebuilds the
session but
  discards the previous attachment before it can emit the same cosmetic
  rebuild warning used by an in-place bypass change. In e2e thread
  `thr_stxrb938ec`, the fresh `provider.env-resolved` event without pool
entries and the successful `LOCAL AGAIN` response demonstrate the actual
disable behavior. The bypass path in that thread does retain the
attachment
  and emits the expected rebuild warning.
- A packaged remote daemon cannot be enrolled into an isolated dev app.
The
multi-machine path is verified by the daemon-side runtime test that
expands
`serverPath` against its loopback machine-auth URL, plus a live 200
response
from the dev hub's `HEAD /api/hello`; this does not claim a live remote
  thread.

## How you verified

- Added Account Pool regressions for empty resolver/health output, exact
per-host contributions and secret masking, distinct machine tokens,
bypass
through CLI and RPC, token rotation and grace expiry, the single-process
  cache and last-use write throttle, secret-free status, proxied and
unauthenticated routed-host warnings, bounded/failure-safe disable
logging,
and the existing authenticated streaming/drain paths. These behaviors
had no
  implementation before this change; all 16 tests pass.
- Added server and fake-plugin-host coverage proving health is ignored
without
an adjacent env resolver and marks only credential-blocked provider
health
  ready when a contribution is active.
- `pnpm exec turbo run typecheck --filter=@get-bb/plugin-sdk
--filter=@bb/server --filter=@bb/plugin-api-map
--filter=bb-plugin-account-pool --continue` — 9 tasks passed.
- `pnpm exec turbo run test --filter=@get-bb/plugin-sdk
--filter=@bb/server --filter=@bb/plugin-api-map
--filter=bb-plugin-account-pool --continue --force` — 11 tasks passed;
Account Pool 13 tests, SDK 22 files, API map 11 files, and server 227
files / 2205 tests.
- Review follow-up: `pnpm exec turbo run typecheck test
--filter=bb-plugin-account-pool --filter=@bb/server
--filter=@get-bb/plugin-sdk --continue --force` — 13 tasks passed;
Account Pool 16 tests, SDK 22 files, and server 227 files / 2205 tests.
- `pnpm exec turbo run typecheck test --filter=@bb/agent-runtime
--continue --force` — typecheck passed and 22 files / 319 tests passed,
including loopback `serverPath` expansion and secret event masking.
- `node .github/workflows/check-plugin-sdk-version.mjs` — passed with
the SDK surface change and version bump.
- `git diff --check origin/bb/account-pool-hub...HEAD` — passed.
- In the isolated dev app, Claude Code thread `thr_stxrb938ec` returned
`POOL OK` through the hub with an expanded loopback URL and masked token
in
`provider.env-resolved`; quota utilization moved from unknown to
28%/34%.
Bypass returned `DIRECT OK` with no pool env and the rebuild warning.
Plugin
disable returned `LOCAL AGAIN` using local credentials with no pool env,
and
the plugin was re-enabled. Per-machine token rotation also completed,
and a
live hub hello request returned HTTP 200. BB-90 has the reduced JSON
evidence
  and both rendered UI screenshots attached.

Part of #1552

Task: BB-90

> AGENT GENERATED

---------

Co-authored-by: Sawyer Hood <kirbyhood@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
SawyerHood added a commit that referenced this pull request Sep 4, 2026
Stack layer 4 of 4 (top). Prerequisites: #3035, #3036, and #3042 below
it in this stack.

## Human comments

## What was wrong

Account Pool had no settings UI or interactive Claude login path, so
users had to provision accounts through the CLI. The plugin could also
report `needs-configuration` when empty, but the app frontend loader
discarded every plugin frontend outside `running`; that made the
configuration UI that an empty pool needs impossible to load. Part of
#1552.

## What changed

- Added an Account Pool settings section with the minimal account list,
ready/held/exhausted/error/disabled status, 5-hour and 7-day
percentages, enable/disable, confirmed removal, machine import, and a
password-field API-key flow.
- Added Claude Code-compatible PKCE login with a ten-minute in-memory
session, manual callback paste parsing, state validation, token
exchange, profile enrichment, protected account storage, and
user-readable errors that do not expose upstream payloads.
- Exposed login through typed plugin RPC and two plugin-owned CLI
commands: `bb pool account add --provider claude --login` prints the
authorization URL and session ID, then `bb pool account login-complete
--session <id> --code-stdin` completes it without putting the pasted
code in process arguments.
- Generalized the plugin CLI proxy's protected stdin handling so any
`--<name>-stdin` flag becomes `--<name> <single-line-value>` with the
existing 16 KiB limit; core contains no Account Pool-specific routing.
- Published account mutation notifications and refreshed the settings
list after mutations.
- Allowed frontend bundles for `needs-configuration` and `degraded`
plugins, with regression coverage for all supported settings statuses.
- Updated the plugin guide, built-in bb CLI skill, and configuration
documentation. There is no server/host-daemon wire change, so
`HOST_DAEMON_PROTOCOL_VERSION` is unchanged.

## How you verified

- `pnpm exec turbo run typecheck test --filter=bb-plugin-account-pool
--filter=@bb/cli --filter=@bb/app --filter=@bb/templates --continue
--force` — 14/14 tasks passed: Account Pool 28 tests, CLI 530 tests, app
3,853 tests (4 skipped), templates 43 tests.
- `pnpm --silent bb plugin build plugins/account-pool` — emitted server
and app JS/CSS/meta bundles.
- `pnpm install --frozen-lockfile --offline` — lockfile accepted without
resolution changes.
- Live dev-app proof: captured the empty state, started the
PKCE/manual-paste flow, verified the API-key input is `type=password`,
imported the machine OAuth account, ran Claude Code thread
`thr_qr935hirfw` to an `ok` response through the pool, observed
5-hour/7-day utilization, removed the account in the UI, and confirmed
its mode-600 secret file was deleted. Screenshots and command artifacts
are attached to BB-92.
- The isolated headless QA browser reached Claude's Cloudflare
“Performing security verification” page, so live OAuth completion
stopped at the brief's human-browser boundary. The exact state is
attached; successful exchange/profile/storage and all three paste shapes
are covered against fake HTTP servers.

Part of #1552

> AGENT GENERATED
SawyerHood added a commit that referenced this pull request Sep 4, 2026
…rite (#3053)

Standalone follow-up to the Account Pool stack (#3035, #3036, #3042,
#3049); based on main.

## Human comments

## What was wrong

Account Pool selected accounts using only shared 5-hour and 7-day quota
observations, even though Anthropic applies separate weekly limits to
model families such as Fable. Scoped `7d_<bucket>` headers were recorded
as opaque diagnostics and never governed selection, the OAuth usage
endpoint was not polled, and Claude Code request metadata could retain
an account UUID belonging to a different OAuth account after routing.
Part of #1552.

## What changed

- Replaced opaque bucket exhaustion diagnostics with fixed Fable,
Sonnet, Opus, Haiku, and other weekly quota slots carrying utilization,
reset, status, observation time, and source.
- Added five-minute idle OAuth usage refreshes, immediate refresh after
add or enable, scoped usage-limit parsing, and lazy account UUID
backfill through the OAuth profile endpoint. Refresh failures preserve
the last known state.
- Parsed the request model once and made eligibility and reset
tie-breaking use its governing family bucket. Scoped 429 responses now
rotate accounts without globally exhausting the rejected account.
- Stored account UUIDs during Claude Code import and OAuth sign-in, and
guarded request rewriting to the selected account for recognized
stringified-JSON and `user_*_account_<uuid>_session_*` metadata forms.
Unrecognized, absent, non-JSON, or UUID-less requests remain
byte-identical.
- Added dynamic family columns to `bb pool account list` and updated the
CLI skill, plugin guide, and configuration docs.
- Added focused migration, usage parsing/refresh, family routing,
scoped-header/429, UUID import/sign-in/backfill/rewrite, passthrough,
CLI, and app regression coverage. There is no Plugin SDK or
server/host-daemon wire change, so neither protocol version changed.

## How you verified

- `pnpm exec turbo run typecheck test --filter=bb-plugin-account-pool
--continue --force` — 6 tasks passed; 4 test files and 35 tests passed.
The new routing, scoped-429, refresh, migration, and UUID tests fail
against the prior implementation and pass with this change.
- `pnpm exec turbo run typecheck test --filter=@bb/templates --continue
--force` — 7 tasks passed; 7 test files and 43 tests passed.
- `pnpm exec turbo run typecheck --filter=@bb/server --continue` — 5
tasks passed.
- `pnpm --silent bb plugin build plugins/account-pool` — server and app
bundles built successfully.
- `git diff --check origin/main...HEAD` — passed after rebasing onto
current `origin/main`.
- Isolated dev-app proof with two OAuth accounts and
`switchThreshold=0.70`: Account A had shared 7d at 43% but Fable at 80%;
Account B had Fable at 7%. A real `claude-fable-5-1` turn completed
through Account B, then a real `claude-sonnet-5` turn completed through
Account A, proving family-only exhaustion does not disable the account
globally. A redacted shape probe on a further real turn found
`metadata.user_id` present without a recognized account UUID component,
so the production path correctly preserved it byte-for-byte. The probe
was removed, both imported test accounts were removed from the isolated
store, and the dev app was stopped. Evidence is attached to BB-94.

Part of #1552

> AGENT GENERATED
ymichael added a commit that referenced this pull request Sep 9, 2026
## Human comments

## What was wrong

[PR #3035](#3035) made
plugin-contributed provider environments available on every command, but
Pi reconciled only model and reasoning changes while ACP ignored later
`turn/start` options. Their long-lived child processes therefore kept
the environment from session creation even after BB accepted and
recorded new credentials or endpoints, so a later turn could use the
wrong account or fail with stale credentials.

## What changed

Pi now compares the effective turn environment with its construction
environment and uses its existing rollback-safe rebuild and resume path
when values differ. ACP now retains its construction parameters, merges
current launch and turn environments, and restarts through its existing
resume path; it reports context loss only when fallback changes the
provider thread id. Identical environments do not trigger replacement,
and legacy empty replay payloads keep their existing no-change behavior.

The rebase preserves current main's deterministic Pi skill-input
coverage and drops the now-obsolete timeout-only commit for the deleted
process-backed test. No public contract, CLI, documentation, or
host-daemon wire behavior changed, so no protocol bump is required.

## How you verified

- Added red-first Pi and ACP regressions that reproduce stale child
environments and pass after the fix.
- Full provider suites passed: Pi 157/157 tests and ACP 312/312 tests.
- Focused post-format reruns passed: Pi turn options 7/7 and ACP bridge
96/96.
- `pnpm exec turbo run test typecheck --filter=bb-plugin-provider-pi
--filter=@bb/provider-bridge-acp --force` passed all eight Turbo tasks.
- `bb plugin build plugins/provider-pi` and `bb plugin build
plugins/provider-acp` emitted their packages successfully.
- Targeted formatting for the four normally formatted source/test files
and `git diff --check origin/main...HEAD` passed. The three-line fake-Pi
fixture addition follows formatter output; the remainder of that legacy
fixture is unchanged.

No linked issue; this is a follow-up to #3035.


> AGENT GENERATED
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