{
  "$schema": "https://github.com/nobilix/bb-plugin-atlas#briefs",
  "generatedFor": {
    "bbVersion": "0.43.3",
    "sdkVersion": "0.4.104",
    "commit": "e865697f56bea89f3413dd4cc7fae964850d20a0",
    "tag": "desktop-v0.43.3",
    "url": "https://github.com/get-bb/bb",
    "permalinkBase": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/"
  },
  "count": 103,
  "rules": {
    "frontend-no-network": "The frontend has no network plane of its own. Call your server entry through `useRpc` → `bb.rpc`; never `fetch` from `app.tsx`.",
    "shims-devdeps": "React, the SDK and every shimmed package (sonner, vaul, radix, @pierre/diffs, clsx, tailwind-merge, cva) come from the host at runtime. They belong in `devDependencies` — putting one in `dependencies` bundles a second copy and breaks the plugin.",
    "replacement-original": "bb passes `Original`. Render it to delegate — that is the supported way to handle the cases you do not want to own.",
    "exclusive-slot": "Only one plugin can own this at a time; the user picks the winner in Settings → Appearance. Do not assume yours is active.",
    "crash-boundary": "Each slot is wrapped in its own error boundary. A crash shows a chip and the rest of bb keeps working — but the crash is sticky for the session, so fix it rather than relying on the fallback.",
    "css-scope": "Your styles are scoped to `:where([data-bb-plugin=<id>],[data-bb-plugin-root]:not([data-bb-plugin]))`. Only default-theme Tailwind utilities are emitted; custom `@theme` colors and hand-written `oklch(...)` literals will not survive the build.",
    "icons-hugeicons": "Icon names are bb's own set (hugeicons), not lucide. A plugin-owned glyph is declared in `bb.branding.experimental_icons` and referenced as `\"<pluginId>/<name>\"`.",
    "backend-full-trust": "Plugins are full-trust code inside the server process: they can read all local bb data, including other plugins' secrets. `secret: true` settings are plaintext files with mode 0600, not encrypted. Design accordingly.",
    "secrets-agent-reach": "An agent shell running as the same user on the same machine can read your plugin's data and secrets, and the local API is unauthenticated. If a capability must not be triggered by the agent alone, split it: the agent proposes, a human confirms through `bb.ui.requestInput`.",
    "experimental-churn": "Every `experimental_` member is in `docs/api_to_audit.md` and gets renamed project-wide when it stabilizes. Compatibility aliases warn for one release and then throw.",
    "tool-output-bounded": "Return bounded output — `PLUGIN_CLI_OUTPUT_MAX_BYTES` caps CLI output and oversized tool results get truncated.",
    "interaction-detached": "`bb.ui.requestInput` survives the end of the agent's turn: the answer arrives later as a system message. Handle `cancelled` with all of its reasons (user, request-aborted, thread-stopped, thread-deleted, plugin-disposed, server-restarted, timeout).",
    "dispose-lifo": "The factory is time-boxed at 30 s and its return value is discarded. Register cleanup with `bb.onDispose` (LIFO) — services, schedules, listeners, content scripts, everything.",
    "panel-params-untrusted": "Panel `params` round-trip through persistence, so treat them as untrusted input: re-fetch fresh data by id instead of embedding whole payloads.",
    "directive-attrs-untrusted": "Directive attributes are untrusted strings parsed out of the message. Validate your own fields; anything malformed falls back to rendering the original source text.",
    "thread-list-keyboard": "Row anchors must carry `data-sidebar-thread-shortcut-target=\"\"` and `data-sidebar-thread-id={thread.id}`, or bb's `thread.next` / `thread.previous` shortcuts silently stop working.",
    "content-script-trust": "A content script is trusted, same-origin page code — not a sandbox. It runs once per window, tab and remote client, and everything it touches must be released through the abort signal or the returned disposer.",
    "mount-timeout": "Async mount setup is time-boxed at 10 s. A throwing or slow mount aborts the candidate generation, disposes what already mounted in reverse and publishes nothing.",
    "header-geometry": "The thread header is a 48 px chrome row with 28 px controls: render one inline control and portal anything taller.",
    "split-instances": "A split layout mounts one instance per visible pane. Never keep per-thread state in a module-level singleton.",
    "browser-page-null": "`experimental_page` is null outside the desktop app, and it takes no CDP control lease — no control banner appears. The `isolated` world shares the DOM but not page globals.",
    "timeline-kind-ownership": "You may only render your own extension kinds (`\"<pluginId>/<name>\"`) and `\"tool\"` rows of providers your plugin registered. Core kinds always use core renderers.",
    "message-reference": "`ThreadChatMessageReference` is a narrow stable reference, never an internal timeline row; `sourceSeqEnd` is the anchor the server accepts for provider-history forks.",
    "interaction-limits": "Hard limits: title 1–160 chars, payload ≤64 KiB of JSON, `timeoutMs` default 10 min and capped at 1 hour. bb stores neither the payload nor the submitted value — only what `describeSubmission` returns.",
    "diff-patch-complete": "`patch` is always a complete single-file unified patch whatever the caller supplied. Verify paths and hunk lines agree before treating `experimental_fullFileContents` as complete.",
    "file-source-kinds": "`path` semantics follow `source.kind`: worktree-relative for `workspace`, absolute for `host`, storage-relative for `thread-storage`.",
    "overlay-owns-chrome": "bb supplies no chrome, positioning or visibility for an overlay: it mounts once per app window and you own everything it draws.",
    "composer-scopes": "Declare `scopes` deliberately: `thread`, `queued-message`, `side-chat` and `new-thread` behave differently, and some (queued-message, side chat) have no submit pipeline at all.",
    "composer-submit-pipeline": "Submit through `composer.experimental_submit(...)`, not your own request: the draft's attachments, @-mentions and (in new-thread) provider/model/reasoning/tier/permission/environment selections only travel that way.",
    "composer-inline-cap": "bb keeps at most three plugin inline actions in the control row, ranked by use; the rest move into an overflow menu.",
    "composer-scoped-effects": "`setTextEffect` and `setInputLock` are scoped to your plugin and auto-release when your slot unmounts or the composer scope changes.",
    "composer-write-target": "Writes land in the composer that mounted your slot: the queued-message editor, the visible side-chat draft, the thread draft, or otherwise the new-thread draft — which persists until the user sends or clears it.",
    "richtext-paint-only": "Rich-text effects are paint-only: they never mutate the draft, and offsets are plain-text offsets into the current structured draft.",
    "command-shortcuts": "A `defaultShortcut` is validated strictly: bare modifiers are rejected and Command/Control/Alt or an F1–F24 key is required. User overrides live under `plugin:<plugin-id>/<command-id>`.",
    "command-isavailable": "`isAvailable` runs before listing and before keyboard invocation — keep it cheap and synchronous; errors are contained.",
    "engines-gate": "Declare the floor you rely on in `engines.bbPluginSdk` (`app.commands.register` needs `>=0.4.91`, keyboard bindings `>=0.4.92`). Managed installs refuse a plugin whose SDK is newer than the host.",
    "mention-search-timebox": "`search` runs on the server, is time-boxed to 2 s and is failure-isolated: a slow or throwing provider simply contributes nothing.",
    "mention-resolve-blocks": "`resolve` runs once per unique item at send time and a throw blocks the send with a visible error. Item ids are namespaced by the host as `\"<providerId>:<itemId>\"`.",
    "mention-images-untrusted": "Treat remote and page-derived image content attached through `experimental_images` as untrusted evidence.",
    "settings-load-once": "Settings are read once per load. Saving does not reload the plugin in V1 — tell the user to run `bb plugin reload <id>`, or subscribe with `onChange`.",
    "settings-secrets": "Only string settings can be `secret: true`; secrets are never sent to the frontend (`useSettings()` sees non-secret values only) and never stored in the database.",
    "status-cleared-on-load": "`needsConfiguration` is cleared on the next load, so re-report it from the factory on every load while the configuration is still missing.",
    "needs-configuration-error": "`NeedsConfigurationError` is matched by name — `throw Object.assign(new Error(msg), { name: \"NeedsConfigurationError\" })` — so a service can mark the plugin instead of crash-looping.",
    "cli-runs-on-server": "The CLI handler runs inside the server process, not in the CLI: `argv` excludes the command name and `ctx.signal` aborts when the invoking request disconnects.",
    "cli-output-cap": "`PLUGIN_CLI_OUTPUT_MAX_BYTES` is 1 MiB of stdout + stderr combined, and the host rejects an oversized result atomically instead of clipping it.",
    "cli-argv": "Core bb commands always win a name collision; your plugin stays reachable as `bb plugin run <id>`. Use `defineCli` so `--help`, unknown-flag hints and `--json` envelopes come for free.",
    "tools-next-session": "A changed tool set or changed instructions take effect only when the provider session is next started or resumed — bb never hot-mutates a running session.",
    "tool-name-global": "Tool names are unique across plugins. A collision is not fatal: the later tool is dropped and the reason lands in your plugin's status detail.",
    "instructions-cap": "Tool `instructions` and every instruction contribution are capped at 4096 characters; `configure` selections are capped at 256 ids.",
    "rpc-strict-json": "RPC results must be strict JSON: cycles, bigint, `undefined`, functions, class instances, symbol keys and non-finite numbers are rejected, not coerced.",
    "http-exact-match": "HTTP routes are exact-match under `/api/v1/plugins/<id>/http`: `:` and `*` are literal characters. `auth: \"none\"` is only for webhooks that verify their own signature.",
    "realtime-broadcast": "`bb.realtime.publish` broadcasts to every connected client and nothing is persisted or replayed — it is not a confidentiality boundary, and durable state must be reconciled on later `connected` transitions.",
    "events-are-announcements": "Events are announcements: the return value is ignored and a handler can never block or veto the change. The surface that can decide is `bb.experimental_hooks`.",
    "events-see-everything": "Every listener sees every row, including other plugins'. Filter on `entry.waitingOn?.kind === \"plugin\" && entry.waitingOn.pluginId === bb.pluginId`.",
    "message-cancelled-only-signal": "`message.cancelled` is the only signal that a queued row was removed before it ever dispatched.",
    "hook-fail-closed": "The dispatch hook is fail-closed: a handler that throws or exceeds the 10 s decision box fails the attempt with your plugin named. Decide in milliseconds.",
    "hook-idempotent": "The pass re-runs on every drain, restart and retry, so a handler must be idempotent for one logical dispatch.",
    "hook-no-amendment": "There is no \"handled it myself\" answer and no amendment arm: a hook decides (proceed / wait / reject), it never rewrites the dispatch.",
    "send-now-bypass": "A user's explicit Send-now bypasses the pass by design, as do compaction and an edited message's re-send.",
    "service-abort-sleep": "A service must resolve when its `signal` aborts — a plain `setTimeout` sleeps through the stop window and the plugin reports \"degraded (service did not stop)\" on reload.",
    "schedule-only-while-loaded": "A cron schedule fires only while the plugin is loaded, so anything that must survive a disable needs its own startup reconciliation.",
    "kv-value-cap": "`bb.storage.kv` values are JSON capped at 256 KB each; anything bigger or relational belongs in `bb.storage.database()`.",
    "migrations-append-only": "Migrations are append-only by index: the host records each statement's hash and rejects a changed or reused index.",
    "storage-not-a-boundary": "Storage isolation is a convention of the `bb.storage` helpers, not an enforced boundary — another plugin can open your `data.db` with ordinary `fs`.",
    "sdk-bind-gated": "`bb.sdk` is bind-gated: reading it before the host binds it throws. Prefer using it from handlers, services and timers so isolated harnesses behave the same.",
    "sdk-hidden-threads": "Hidden worker threads need explicit cleanup — stop and archive them in a `finally` so a plugin failure cannot retain the agent process.",
    "sdk-attribution": "`threads.spawn` / `threads.fork` default `origin` to `\"plugin\"` and `originPluginId` to your id; seeding `pluginMetadata` always attributes the new thread to you.",
    "metadata-untrusted": "Namespace ids are not an ownership or authorization boundary: any API client, another plugin or the thread's own agent can write any namespace. Treat values as untrusted input.",
    "metadata-size": "Each namespace caps at 256 KiB of UTF-8 JSON; an over-size patch fails with HTTP 413 and leaves the namespace unchanged.",
    "metadata-frozen-in-configure": "Inside `bb.agents.configure` the namespace arrives deep-frozen — copy it before handing values to anything that expects mutable `JsonValue`.",
    "provider-needs-host": "Registering a provider without a `bb.host` entry fails the plugin load: the bridge ships in the host artifact.",
    "provider-id-immutable": "A provider `id` is persisted on every thread that used it — never change it. Ids are flat and collision-rejected across plugins.",
    "provider-derive-sync": "`deriveProviderOptions` is synchronous, runs on every session and turn command and is capped at 64 KiB; a throw fails the command with your plugin named.",
    "bridge-grammar-v3": "The delta assembler speaks grammar v3 only. Every turn needs `input.accepted` → `turn.open` → deltas → `turn.boundary`, even a zero-work prompt.",
    "env-create-idempotent": "`create` is one long call and must be idempotent for its `pathKey`: after a restart core calls it again with the same attempt and pathKey.",
    "env-failure-terminal": "A failed create is terminal — there is no automatic retry ladder. Return `{ status: \"failed\", message }` with user-safe recovery copy.",
    "env-inputs-public": "Parsed `inputs` are persisted and readable by every plugin, including after the environment is destroyed. They are configuration, not a credential store.",
    "env-hooks-owned-by-core": "Core runs `.bb-env-setup.sh` after a create that returns `ownsPath: true` and `.bb-env-teardown.sh` before remove. Providers must not call these hooks themselves.",
    "machine-checkpoint": "`await checkpoint(resource)` after durable allocation and before bootstrap, and never put the bootstrap bundle in the resource JSON.",
    "machine-needs-composition": "Registering a machine provider contributes no environment-picker entry: also register a composition naming `machineProviderId` + `environmentProviderId`.",
    "machine-resource-public": "`inputs`, `resource` and every `report.step` / `report.log` line are persisted and readable by other plugins and copied into thread transcripts. Store references, never secrets.",
    "machine-idle-is-yours": "Idle timing is the plugin's, not core's: watch activity events and sweep with a schedule plus startup reconciliation.",
    "access-acquire-idempotent": "`acquire` is idempotent by key and must persist the revocation identity before returning, so `release` works even before enrolment completes.",
    "access-release-null-grant": "`release` receives a null `grantId` when acquisition was interrupted — reconcile by key and hostId.",
    "host-call-not-in-factory": "Create the client and register signal handlers in the factory, but call host methods only from an RPC/event handler, background service or timer — candidate-time calls are rejected.",
    "host-no-private-imports": "A host artifact may not import bb's private `@bb/*` packages at all, and it is fully self-contained: pure-JS dependencies are bundled into `dist/host.js`.",
    "host-limits": "Host RPC limits: 32 MiB in, 8 MiB out, 30 s default timeout capped at 30 minutes, worker start 10 s, idle eviction after 5 minutes unless retained.",
    "host-worker-exit": "`experimental_onWorkerExit` fires only on unexpected exits: graceful reload, disable, uninstall, daemon shutdown and idle eviction are silent.",
    "browser-lease-ttl": "Control leases really expire: clamp your session's own expiry to the minimum of your TTL, the lease's and the connection's.",
    "browser-allow-personal": "Pass `allowPersonal: true` only when the user explicitly picked an existing tab, and close only the tabs your plugin created.",
    "browser-host-match": "Treat a connection whose `hostId` differs from your scope as fatal — the CDP endpoint is host-scoped.",
    "ai-needs-host": "An AI service must declare a `bb.host` entry; registering without one fails the load, and core always calls the primary host.",
    "ai-failures-returned": "Return failures as `{ ok: false, code }` instead of throwing, so core can apply its retry and fallback policy (timeouts and rate limits fall back; auth failures do not).",
    "ai-reserved-ids": "Ids the server serves itself (`openai` transcription and the built-in inference providers) are reserved and refused.",
    "jsx-alias-experimental": "JSX reads a lowercase-initial name as an intrinsic element, so import `experimental_*` components under an aliased capitalised name (`experimental_Diff as Diff`).",
    "threadchat-owns-thread": "`ThreadChat` owns timeline loading, streaming, drafts, send/queue/steer/stop, attachments and pending interactions — do not proxy thread data through your own RPC.",
    "no-worker-pool": "`experimental_Diff` takes the host's shared highlight worker pool from React context: thread and nav panels have one, homepage and settings sections do not, so code there renders unhighlighted.",
    "harness-same-collector": "`loadPluginApp` runs the same validating collector as the host with the same error messages — pass the thunk form `() => import(\"./app\")` so the module evaluates after the runtime is installed.",
    "harness-stubs": "Host components in the frontend harness are prop-recording stubs, not the real engines, and the harness composer has no submit pipeline."
  },
  "briefs": [
    {
      "kind": "surface",
      "id": "sidebar-navigation",
      "title": "Sidebar navigation",
      "groupTitle": "The bb app window",
      "number": 1,
      "summary": "Replaces bb's navigation controls above the thread list with a component your plugin renders. With this, a plugin can:",
      "bullets": [
        "Arrange New thread, Search, Plugins, Skills, and plugin destinations",
        "Activate each destination through bb, including split placement for supported items",
        "Render bb's original controls when the plugin wants to delegate",
        "Leave the thread list, footer, drawer, and resize handle under bb's control"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_sidebarNavigation({ id, title, description, component })",
      "propsType": "ExperimentalSidebarNavigationProps",
      "slotKind": "replacement",
      "stability": "experimental",
      "symbols": [
        {
          "name": "ExperimentalSidebarNavigationRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1192,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1192"
        },
        {
          "name": "ExperimentalSidebarNavigationProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 170,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L170"
        },
        {
          "name": "ExperimentalSidebarNavigationItem",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 152,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L152"
        },
        {
          "name": "ExperimentalSidebarNavigationAction",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 136,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L136"
        },
        {
          "name": "ExperimentalSidebarNavigationIcon",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 147,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L147"
        },
        {
          "name": "ExperimentalSidebarNavigationShortcut",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 130,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L130"
        },
        {
          "name": "ExperimentalSidebarNavigationActivationOptions",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 165,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L165"
        }
      ],
      "reference": [
        {
          "plugin": "sidebar-navigation-example",
          "file": "examples/plugins/sidebar-navigation/app.tsx",
          "note": "Replaces the whole navigation block with a compact grid and delegates to experimental_Original when it does not want to own an item."
        }
      ],
      "rules": [
        "frontend-no-network",
        "shims-devdeps",
        "replacement-original",
        "crash-boundary",
        "icons-hugeicons",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Settings → Appearance → Navigation lists your plugin and selecting it replaces the New thread / Search / Plugins / Skills block with your component",
        "Activating an item through experimental_activate opens the same destination bb would have opened, including split placement",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L48",
      "guideAnchor": "surface-sidebar-navigation",
      "mockupHash": "shell,surface=sidebar-navigation"
    },
    {
      "kind": "surface",
      "id": "nav-panel",
      "title": "Full-page panels",
      "groupTitle": "The bb app window",
      "number": 2,
      "summary": "Adds a row to bb's sidebar that opens a page your plugin renders where threads normally appear. With this, a plugin can:",
      "bullets": [
        "Render any React you write across that whole area",
        "Get its own URL, so the page can be linked to and bb's back and forward buttons work",
        "Register tabs in the panel to the right of its page, beside bb's own Browser and Terminal tabs"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.navPanel({ id, title, icon, path, component, fixedTabs?, experimental_sidebarAccessory?, headerContent? })",
      "propsType": "PluginNavPanelProps",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginNavPanelRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 600,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L600"
        }
      ],
      "reference": [
        {
          "plugin": "tasks",
          "file": "plugins/tasks/app.tsx",
          "note": "Registers a nav panel with a sidebar accessory and a fixed tab, and routes the remaining subPath inside the page."
        },
        {
          "plugin": "github",
          "file": "plugins/github/app.tsx",
          "note": "Uses headerContent to put controls into the shared app title bar."
        }
      ],
      "rules": [
        "frontend-no-network",
        "shims-devdeps",
        "css-scope",
        "icons-hugeicons",
        "crash-boundary",
        "panel-params-untrusted"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "A row with your icon and title appears under the built-in navigation rows and opens /plugins/<plugin-id>/<path>",
        "Deep links into the page work: navigating to /plugins/<plugin-id>/<path>/foo hands your component subPath = \"foo\"",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L70",
      "guideAnchor": "surface-nav-panel",
      "mockupHash": "shell,surface=nav-panel"
    },
    {
      "kind": "surface",
      "id": "thread-row-status",
      "title": "Thread row status",
      "groupTitle": "The bb app window",
      "number": 3,
      "summary": "A small status bb can draw on a thread's row in the sidebar. With this, a plugin can:",
      "bullets": [
        "Give the status an icon and a label",
        "Mark a thread as running while it works on it, and bb shimmers the icon",
        "Mark it succeeded or failed when the work ends, and bb settles the icon",
        "Set it only from an [app-wide script](content-scripts). A status needs an owner that outlives any single screen, and those scripts are the only plugin code that does",
        "Rely on bb to clear it when the script unmounts"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.contentScripts.register({ id, mount(context) }) → context.experimental_setThreadRowStatus(threadId, status)",
      "propsType": "PluginComposerThreadRowStatus",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginComposerThreadRowStatus",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1957,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1957"
        },
        {
          "name": "PluginContentScriptContext",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1700,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1700"
        }
      ],
      "reference": [
        {
          "plugin": "provider-usage",
          "file": "plugins/provider-usage/app.tsx",
          "note": "Registers a content script and schedules work around visibility and blur, releasing everything on the abort signal."
        }
      ],
      "rules": [
        "content-script-trust",
        "frontend-no-network",
        "experimental-churn",
        "dispose-lifo"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "The thread row shows your icon and label, and tone \"running\" shimmers on its own",
        "Clearing the status with null restores bb's own row rendering",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L83",
      "guideAnchor": "surface-thread-row-status",
      "mockupHash": "shell,surface=thread-row-status"
    },
    {
      "kind": "surface",
      "id": "thread-list",
      "title": "The thread list",
      "groupTitle": "The bb app window",
      "number": 4,
      "summary": "Replaces the list of threads in bb's sidebar with a component your plugin renders. With this, a plugin can:",
      "bullets": [
        "Render every row, and decide the grouping, the ordering, and what each row shows",
        "Read the same live thread data and run statuses bb's own list reads",
        "Replace only the list. The New thread button, the search action, the plugin rows, and the sidebar footer stay bb's"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_threadList({ id, title, description, component })",
      "propsType": "PluginThreadListProps",
      "slotKind": "exclusive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginThreadListRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1181,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1181"
        },
        {
          "name": "PluginSidebarThreadsState",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 991,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L991"
        }
      ],
      "reference": [
        {
          "plugin": "replacement-lab-alpha",
          "file": "examples/plugins/replacement-lab-alpha/app.tsx",
          "note": "Shows conditional delegation to Original, the deliberate crash path and how two plugins contend for the same area."
        }
      ],
      "rules": [
        "exclusive-slot",
        "replacement-original",
        "frontend-no-network",
        "thread-list-keyboard",
        "crash-boundary",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Settings → Appearance → Sidebar lets the user pin your list, and the sidebar scroll area renders it",
        "Rows carry data-sidebar-thread-shortcut-target and data-sidebar-thread-id so thread.next / thread.previous keep working",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L101",
      "guideAnchor": "surface-thread-list",
      "mockupHash": "shell,surface=thread-list"
    },
    {
      "kind": "surface",
      "id": "sidebar-footer",
      "title": "Sidebar footer items",
      "groupTitle": "The bb app window",
      "number": 5,
      "summary": "Adds a host-rendered icon item to the bottom of bb's sidebar. With this, a plugin can:",
      "bullets": [
        "Run an action, or reveal plugin-rendered content above the footer row",
        "Let bb coordinate one open disclosure across every enabled plugin",
        "Respect user ordering and visibility in Appearance; hidden actions and disclosures remain usable from More",
        "Keep navigation, tabs, data, and controls inside the plugin's disclosure component"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.experimental_sidebarFooter.register({ kind: \"action\" | \"disclosure\", id, label, icon, … })",
      "propsType": "ExperimentalSidebarFooterDisclosureProps",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "ExperimentalSidebarFooter",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 835,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L835"
        },
        {
          "name": "ExperimentalSidebarFooterItemBase",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 797,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L797"
        },
        {
          "name": "ExperimentalSidebarFooterItemRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 820,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L820"
        },
        {
          "name": "ExperimentalSidebarFooterActionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 806,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L806"
        },
        {
          "name": "ExperimentalSidebarFooterActionContext",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 791,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L791"
        },
        {
          "name": "ExperimentalSidebarFooterDisclosureRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 814,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L814"
        },
        {
          "name": "ExperimentalSidebarFooterDisclosureProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 124,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L124"
        },
        {
          "name": "ExperimentalSidebarFooterDisclosureController",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 825,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L825"
        },
        {
          "name": "PluginSidebarFooterActionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 775,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L775"
        }
      ],
      "reference": [
        {
          "plugin": "connect",
          "file": "plugins/connect/app.tsx",
          "note": "Registers a footer action that opens the plugin's own details page."
        },
        {
          "plugin": "provider-usage",
          "file": "plugins/provider-usage/app.tsx",
          "note": "Registers a disclosure and keeps the returned controller to open and close it."
        }
      ],
      "rules": [
        "frontend-no-network",
        "shims-devdeps",
        "icons-hugeicons",
        "crash-boundary",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your item appears in the sidebar footer strip between Settings and the bug-report icon",
        "A disclosure opens above the footer row and closes when another plugin's disclosure opens",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L117",
      "guideAnchor": "surface-sidebar-footer",
      "mockupHash": "shell,surface=sidebar-footer"
    },
    {
      "kind": "surface",
      "id": "thread-header",
      "title": "Thread header controls",
      "groupTitle": "The bb app window",
      "number": 6,
      "summary": "Adds a control to the header bar at the top of an open thread. With this, a plugin can:",
      "bullets": [
        "Render a React component rather than a plain button, so it can show live state",
        "Receive the id of the thread currently on screen",
        "Render in the same row as bb's own header controls"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_threadHeaderAction({ id, title, component })",
      "propsType": "PluginThreadHeaderActionProps",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginThreadHeaderActionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1103,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1103"
        }
      ],
      "reference": [],
      "rules": [
        "frontend-no-network",
        "shims-devdeps",
        "header-geometry",
        "split-instances",
        "crash-boundary",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your control renders at the left end of the thread header action row and stays inside the 48px chrome row",
        "With a split layout open, each visible pane renders its own instance and neither shares per-thread state",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L142",
      "guideAnchor": "surface-thread-header",
      "mockupHash": "shell,surface=thread-header"
    },
    {
      "kind": "surface",
      "id": "browser-toolbar",
      "title": "Browser toolbar controls",
      "groupTitle": "The bb app window",
      "number": 7,
      "summary": "Adds a plugin control to the toolbar of each open Browser tab. With this, a plugin can:",
      "bullets": [
        "Act on the Browser tab currently in front of the user",
        "Receive the owning thread id, tab id, and current URL",
        "Render beside the Browser address bar and native controls",
        "Run scripts in the tab's page and receive messages back without a CDP lease"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_browserToolbarAction({ id, title, component })",
      "propsType": "ExperimentalPluginBrowserToolbarActionProps",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "ExperimentalPluginBrowserToolbarActionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1115,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1115"
        },
        {
          "name": "ExperimentalPluginBrowserToolbarActionProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 269,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L269"
        },
        {
          "name": "ExperimentalPluginBrowserPage",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 251,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L251"
        },
        {
          "name": "ExperimentalPluginBrowserPageEvaluateOptions",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 238,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L238"
        },
        {
          "name": "ExperimentalPluginBrowserPageWorld",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 236,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L236"
        }
      ],
      "reference": [
        {
          "plugin": "agent-annotations",
          "file": "plugins/agent-annotations/app.tsx",
          "note": "Adds a control beside the address bar and drives the page through experimental_page.evaluate / onMessage."
        }
      ],
      "rules": [
        "frontend-no-network",
        "browser-page-null",
        "shims-devdeps",
        "crash-boundary",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "The control appears beside the address bar of every built-in Browser tab",
        "Outside the desktop app experimental_page is null and your component still renders without throwing",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L155",
      "guideAnchor": "surface-browser-toolbar",
      "mockupHash": "shell,surface=browser-toolbar"
    },
    {
      "kind": "surface",
      "id": "timeline-renderers",
      "title": "Timeline entry content",
      "groupTitle": "The bb app window",
      "number": 8,
      "summary": "Renders the expanded content of plugin-owned timeline entries while bb keeps each entry's header and controls. With this, a plugin can:",
      "bullets": [
        "Draw the expanded content beneath timeline entries created by the plugin's own provider",
        "Receive the entry data and plugin payload, plus bb's default content as `Original`",
        "Fall back to bb's default content automatically when the plugin is unavailable or crashes"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_timelineRenderer({ kind, component })",
      "propsType": "PluginTimelineRendererProps",
      "slotKind": "replacement",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginTimelineRendererRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1504,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1504"
        },
        {
          "name": "PluginTimelineRendererProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1466,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1466"
        }
      ],
      "reference": [],
      "rules": [
        "timeline-kind-ownership",
        "replacement-original",
        "frontend-no-network",
        "crash-boundary",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Rows whose kind is \"<plugin-id>/<name>\" (or \"tool\" for your own provider) render your body while bb keeps the row header",
        "Removing the renderer falls back to the declarative base rendering instead of a blank row",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L175",
      "guideAnchor": "surface-timeline-renderers",
      "mockupHash": "shell,surface=timeline-renderers"
    },
    {
      "kind": "surface",
      "id": "message-directives",
      "title": "Rich message embeds",
      "groupTitle": "The bb app window",
      "number": 9,
      "summary": "Renders your component inside an agent's reply, in place of a marker the agent writes into its message. With this, a plugin can:",
      "bullets": [
        "Claim a directive name; an agent writes `::name` in a message to invoke it",
        "Replace that marker with a live component, inline in the conversation",
        "Open a file from the workspace when someone interacts with the embed"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.messageDirective({ id, component })",
      "propsType": "PluginMessageDirectiveProps",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginMessageDirectiveRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1264,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1264"
        }
      ],
      "reference": [
        {
          "plugin": "inline-vis",
          "file": "plugins/inline-vis/app.tsx",
          "note": "Smallest directive plugin: renders workspace or thread-storage HTML and Markdown inline in assistant messages."
        },
        {
          "plugin": "workflows",
          "file": "plugins/workflows/src/app.tsx",
          "note": "Emits a ::workflow-preview directive from a tool result and renders it."
        }
      ],
      "rules": [
        "directive-attrs-untrusted",
        "frontend-no-network",
        "css-scope",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "An assistant message containing ::<your-id>{…} renders your component instead of the literal text",
        "Malformed attributes fall back to the original source text rather than throwing",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L191",
      "guideAnchor": "surface-message-directives",
      "mockupHash": "shell,surface=message-directives"
    },
    {
      "kind": "surface",
      "id": "message-actions",
      "title": "Message actions",
      "groupTitle": "The bb app window",
      "number": 10,
      "summary": "Adds an action to individual messages in a thread. With this, a plugin can:",
      "bullets": [
        "Appear in the row that shows under messages on hover, or in the toolbar that appears when text in an agent's message is selected",
        "Receive the message, plus the selected text when the action was run from a selection",
        "Open one of the plugin's own [side-panel tabs](thread-panel) with what it received"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.messageAction({ id, title, icon, run })",
      "propsType": "PluginMessageActionContext",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginMessageActionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1327,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1327"
        }
      ],
      "reference": [
        {
          "plugin": "side-chat",
          "file": "plugins/side-chat/app.tsx",
          "note": "Opens a side-chat panel from the message action, passing message.sourceSeqEnd as the fork anchor."
        }
      ],
      "rules": [
        "frontend-no-network",
        "message-reference",
        "icons-hugeicons",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your entry appears last in the hover action row under user and assistant messages",
        "Invoking it from the assistant text-selection menu passes selectedText",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L204",
      "guideAnchor": "surface-message-actions",
      "mockupHash": "shell,surface=message-actions"
    },
    {
      "kind": "surface",
      "id": "pending-interaction",
      "title": "In-thread forms",
      "groupTitle": "The bb app window",
      "number": 11,
      "summary": "Pauses an agent mid-turn to ask the person a question, and hands their answer back to the agent. With this, a plugin can:",
      "bullets": [
        "Replace the prompt box with a form the plugin draws, even after the agent's turn has ended",
        "Receive the submitted answer, or a cancellation and its reason",
        "Leave a row in the thread timeline: the plugin names its header and describes what a submission shows, so the transcript keeps exactly what the plugin chooses"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.pendingInteraction({ id, component }) paired with bb.ui.requestInput({ rendererId: id, … })",
      "propsType": "PluginPendingInteractionProps",
      "slotKind": "replacement",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginUi",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1808,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1808"
        },
        {
          "name": "PluginPendingInteractionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 745,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L745"
        }
      ],
      "reference": [
        {
          "plugin": "ask-user-question",
          "file": "plugins/ask-user-question/src/server.ts",
          "note": "Requests a multiple-choice answer from the backend and renders it with the paired slot."
        },
        {
          "plugin": "secrets",
          "file": "plugins/secrets/src/server.ts",
          "note": "Asks for credentials from a CLI command and reconciles them into a dotenv file."
        }
      ],
      "rules": [
        "interaction-detached",
        "interaction-limits",
        "secrets-agent-reach",
        "frontend-no-network",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "While the interaction is pending your form replaces the composer and submit / cancel resolve the backend promise",
        "Every cancellation reason is handled: user, request-aborted, thread-stopped, thread-deleted, plugin-disposed, server-restarted, timeout",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L217",
      "guideAnchor": "surface-pending-interaction",
      "mockupHash": "shell,surface=pending-interaction"
    },
    {
      "kind": "surface",
      "id": "code-renderers",
      "title": "Code & diff renderers",
      "groupTitle": "The bb app window",
      "number": 12,
      "summary": "Replaces bb's source-code or diff renderer everywhere that kind of content appears. With this, a plugin can:",
      "bullets": [
        "Register the source-code and diff replacements independently",
        "Apply each replacement across bb's file previews, timeline and environment diffs, and plugin pages",
        "Hand any individual render back to bb's built-in renderer, and fall back to it automatically if the plugin is unavailable or crashes"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_sourceCodeRenderer({ id, title, description, component }) · app.slots.experimental_diffRenderer({ … })",
      "propsType": "PluginSourceCodeRendererProps · PluginDiffRendererProps",
      "slotKind": "exclusive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginSourceCodeRendererRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1232,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1232"
        },
        {
          "name": "PluginSourceCodeRendererProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 419,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L419"
        },
        {
          "name": "PluginDiffRendererRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1249,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1249"
        },
        {
          "name": "PluginDiffRendererProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 440,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L440"
        }
      ],
      "reference": [],
      "rules": [
        "exclusive-slot",
        "replacement-original",
        "diff-patch-complete",
        "frontend-no-network",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Source or diff surfaces across bb — the Diff tab, file previews, timeline diffs — render through your component",
        "Delegating to Original for the cases you do not handle produces bb's own rendering, not an error",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L230",
      "guideAnchor": "surface-code-renderers",
      "mockupHash": "shell,surface=code-renderers"
    },
    {
      "kind": "surface",
      "id": "thread-panel",
      "title": "Thread side-panel tabs",
      "groupTitle": "The bb app window",
      "number": 13,
      "summary": "Adds a tab to the side panel that opens to the right of a thread. With this, a plugin can:",
      "bullets": [
        "Render the tab's contents and receive the id of the thread it was opened from",
        "Open the tab from a [message action](message-actions), from the + button in the side panel, or from its own code"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.threadPanelAction({ id, title, icon, component, layout?, run? })",
      "propsType": "PluginThreadPanelProps",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginThreadPanelActionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 684,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L684"
        }
      ],
      "reference": [
        {
          "plugin": "side-chat",
          "file": "plugins/side-chat/app.tsx",
          "note": "Registers the panel action a message action opens, with layout \"flush\"."
        },
        {
          "plugin": "tasks",
          "file": "plugins/tasks/app.tsx",
          "note": "Opens a task panel tab from the thread side panel."
        }
      ],
      "rules": [
        "frontend-no-network",
        "panel-params-untrusted",
        "icons-hugeicons",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your row appears in the thread side panel's new-tab Actions list and opens a tab",
        "Re-opening the tab from persisted params re-fetches by id instead of trusting the stored payload",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L248",
      "guideAnchor": "surface-thread-panel",
      "mockupHash": "shell,surface=thread-panel"
    },
    {
      "kind": "surface",
      "id": "file-opener",
      "title": "File viewers & editors",
      "groupTitle": "The bb app window",
      "number": 14,
      "summary": "Registers a viewer for the file types you name, so bb opens those files there instead of its built-in preview. With this, a plugin can:",
      "bullets": [
        "Declare the file extensions it handles, for example `.csv` or `.excalidraw`",
        "Render its own viewer or editor whenever a file of that type is opened in bb",
        "Receive the file's path, then read it however the plugin already reads files",
        "Reveal linked lines with experimental_lineRange, including repeated targets in an already open editor"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.fileOpener({ id, title, extensions, component })",
      "propsType": "PluginFileOpenerProps",
      "slotKind": "replacement",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginFileOpenerRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1212,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1212"
        },
        {
          "name": "PluginFileOpenerProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 303,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L303"
        }
      ],
      "reference": [
        {
          "plugin": "pdf-preview",
          "file": "plugins/pdf-preview/app.tsx",
          "note": "Minimal file viewer: one fileOpener registration and nothing else."
        },
        {
          "plugin": "monaco-editor",
          "file": "plugins/monaco-editor/app.tsx",
          "note": "Full editor, and the reference consumer of experimental_lineRange revealed by object identity."
        }
      ],
      "rules": [
        "replacement-original",
        "file-source-kinds",
        "frontend-no-network",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Opening a file with one of your extensions renders your viewer in the file tab",
        "Settings → File openers lets the user pin another opener and yours steps aside",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L260",
      "guideAnchor": "surface-file-opener",
      "mockupHash": "shell,surface=file-opener"
    },
    {
      "kind": "surface",
      "id": "app-overlay",
      "title": "App-wide overlays",
      "groupTitle": "The bb app window",
      "number": 15,
      "summary": "Mounts floating plugin UI across the bb app, outside route-owned layout regions. With this, a plugin can:",
      "bullets": [
        "Render a persistent widget once per bb window while the plugin is enabled",
        "Use app-level SDK hooks and preserve their React context through portals",
        "Own the widget's chrome, position, visibility, and responsive behavior",
        "Coexist with other overlays while crashes remain isolated to the overlay that failed"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_appOverlay({ id, component })",
      "propsType": "ExperimentalAppOverlayProps",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "ExperimentalAppOverlayRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 534,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L534"
        },
        {
          "name": "ExperimentalAppOverlayProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 59,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L59"
        }
      ],
      "reference": [
        {
          "plugin": "push-notifications",
          "file": "plugins/push-notifications/app.tsx",
          "note": "Mounts a delivery overlay once per app window."
        },
        {
          "plugin": "browser-automation",
          "file": "plugins/browser-automation/app.tsx",
          "note": "Mounts a preview lightbox outside the layout regions."
        }
      ],
      "rules": [
        "overlay-owns-chrome",
        "frontend-no-network",
        "crash-boundary",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your overlay mounts once per app window and survives route changes",
        "It positions and hides itself: bb supplies no chrome, no placement and no visibility",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L274",
      "guideAnchor": "surface-app-overlay",
      "mockupHash": "shell,surface=app-overlay"
    },
    {
      "kind": "surface",
      "id": "content-scripts",
      "title": "App-wide scripts",
      "groupTitle": "The bb app window",
      "number": 16,
      "summary": "Runs your code inside the bb window itself, without rendering a UI of its own. With this, a plugin can:",
      "bullets": [
        "Mount once per bb window and unmount when the window reloads",
        "Add behavior that is not tied to one screen, such as a keyboard shortcut",
        "Set a [thread row status](thread-row-status) on any thread, for as long as the script is mounted",
        "Add plugin-owned elements to app pages without taking ownership of bb's built-in layout",
        "Return a cleanup function. bb calls it once on unmount, and clears any row statuses the script set"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.contentScripts.register({ id, mount(context) })",
      "propsType": "PluginContentScriptContext",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginContentScriptRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1731,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1731"
        },
        {
          "name": "PluginContentScriptContext",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1700,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1700"
        }
      ],
      "reference": [
        {
          "plugin": "content-script-example",
          "file": "examples/plugins/content-script/app.ts",
          "note": "Cleanup-complete reference: adds a focus ring, releases it on the abort signal and returns a disposer."
        }
      ],
      "rules": [
        "content-script-trust",
        "mount-timeout",
        "frontend-no-network",
        "dispose-lifo"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "The script mounts once per window, tab and remote client, and its disposer plus the abort signal both release everything",
        "A reload of the plugin disposes the previous generation before the new one mounts — no duplicated listeners",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L291",
      "guideAnchor": "surface-content-scripts",
      "mockupHash": "shell,surface=content-scripts"
    },
    {
      "kind": "surface",
      "id": "command-palette-actions",
      "title": "Command palette actions",
      "groupTitle": "Command palette",
      "number": 1,
      "summary": "Registers a command with app.commands.register and adds a row under Plugins in bb's quick command palette. With this, a plugin can:",
      "bullets": [
        "Supply the row's label and run behavior; bb owns matching, ordering, and recency",
        "Offer a defaultShortcut with key and optional mod, meta, control, alt, and shift modifiers; mod means Command on macOS and Control elsewhere",
        "Let users bind or rebind every command in Keyboard Settings; conflicts offer Replace binding or Cancel, and conflicting plugin defaults stay unbound",
        "Keep saved bindings across reloads and disable/re-enable using plugin:<plugin-id>/<command-id>; palette and keyboard invocation share availability and error handling",
        "Migrate slots.commandPaletteAction to commands.register with the same fields; the old method remains a deprecated alias",
        "Read the current thread and project, and hide the row when it is unavailable",
        "Open one of the plugin's own thread side-panel tabs when a thread is on screen"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.commands.register({ id, title, defaultShortcut?, isAvailable?, run })",
      "propsType": "PluginCommandContext",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginAppBuilder.commands",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1812,
          "kind": "property",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1812"
        },
        {
          "name": "PluginAppCommands",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1396,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1396"
        },
        {
          "name": "PluginCommandRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1375,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1375"
        },
        {
          "name": "PluginCommandContext",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1341,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1341"
        },
        {
          "name": "PluginCommandShortcut",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1359,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1359"
        }
      ],
      "reference": [
        {
          "plugin": "monaco-editor",
          "file": "plugins/monaco-editor/app.tsx",
          "note": "Registers one command per editor action with isAvailable gating."
        }
      ],
      "rules": [
        "command-shortcuts",
        "command-isavailable",
        "frontend-no-network",
        "engines-gate"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your command appears in the quick palette (Mod+Shift+P) under the Plugins bucket with your plugin's name",
        "A declared defaultShortcut fires, and the user can rebind it under plugin:<plugin-id>/<command-id>",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L317",
      "guideAnchor": "surface-command-palette-actions",
      "mockupHash": "palette,surface=command-palette-actions"
    },
    {
      "kind": "surface",
      "id": "composer-banners",
      "title": "Banners",
      "groupTitle": "The composer",
      "number": 1,
      "summary": "Renders a banner above the prompt box. With this, a plugin can:",
      "bullets": [
        "Render its own component in the strip directly above the draft prompt",
        "Name which prompt boxes it appears in: the new-thread screen, the follow-up composer in a thread, or a queued message being edited. Omit the list to appear in all of them",
        "Show something the person should read before sending, such as a warning or a status"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.composer.customize({ id, scopes?, banners: [{ id, chrome?, component }] })",
      "propsType": "ComposerCustomization",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "ComposerCustomization",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1889,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1889"
        },
        {
          "name": "PluginComposerScope",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1868,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1868"
        }
      ],
      "reference": [
        {
          "plugin": "workflows",
          "file": "plugins/workflows/src/app.tsx",
          "note": "Uses a bare banner as an always-mounted status strip."
        },
        {
          "plugin": "scheduled-send",
          "file": "plugins/scheduled-send/app.tsx",
          "note": "Uses a bare banner purely as a mount point for a portalled dialog."
        }
      ],
      "rules": [
        "composer-scopes",
        "frontend-no-network",
        "css-scope",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "The banner appears above the prompt box only in the scopes you declared",
        "chrome: \"bare\" renders with no card, and the composer keeps its own layout",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L348",
      "guideAnchor": "surface-composer-banners",
      "mockupHash": "composer,surface=composer-banners"
    },
    {
      "kind": "surface",
      "id": "mention-provider",
      "title": "Mentions",
      "groupTitle": "The composer",
      "number": 2,
      "summary": "Adds results to the menu that opens when someone types a trigger character in the prompt box. On a trigger bb does not use itself, your plugin opens that menu and owns it. With this, a plugin can:",
      "bullets": [
        "Answer each keystroke after the trigger with a list of items to show",
        "Claim one or more of the trigger characters @, #, $, !, and ~. Omit them to answer the default @",
        "Turn a picked item into a chip in the draft prompt, and send its content to the agent along with the message"
      ],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.ui.registerMentionProvider({ id, label, triggers?, search, resolve })",
      "propsType": "PluginMentionProviderRegistration",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginMentionProviderRegistration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1771,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1771"
        },
        {
          "name": "PluginMentionSearchContext",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1744,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1744"
        },
        {
          "name": "PluginMentionItem",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1753,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1753"
        }
      ],
      "reference": [
        {
          "plugin": "github",
          "file": "plugins/github/server.ts",
          "note": "Two providers (issues and pull requests) answering @ and # with resolve() building the agent context."
        },
        {
          "plugin": "tasks",
          "file": "plugins/tasks/mentions/index.ts",
          "note": "Searches its own SQLite store and resolves an item into task context."
        }
      ],
      "rules": [
        "mention-search-timebox",
        "mention-resolve-blocks",
        "mention-images-untrusted",
        "backend-full-trust"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Typing your trigger shows your section in the mention menu within the 2 s search box",
        "Sending the message attaches resolve()'s context to the prompt, visible to the agent and hidden from the user",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L361",
      "guideAnchor": "surface-mention-provider",
      "mockupHash": "composer,surface=mention-provider"
    },
    {
      "kind": "surface",
      "id": "composer-rich-text",
      "title": "Draft prompt highlighting",
      "groupTitle": "The composer",
      "number": 3,
      "summary": "Styles text ranges as the person types a prompt, without changing the text. With this, a plugin can:",
      "bullets": [
        "Match ranges in the draft prompt, such as a ticket number or the word TODO",
        "Change only how those ranges look; the text the agent receives is untouched",
        "Re-run its matcher on every keystroke",
        "Observe the draft prompt and its @-mentions as they change, read-only",
        "Remove a plugin-owned mention from the draft, including its visible text",
        "Respond after a local message is successfully sent or queued; failed sends do not notify"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.composer.customize({ id, richText: { effects, onDraftChange } })",
      "propsType": "ComposerRichTextSpec",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "ComposerRichTextSpec",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1928,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1928"
        },
        {
          "name": "ComposerStructuredDraft",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1940,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1940"
        },
        {
          "name": "PluginComposerApi.experimental_removeMention",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 2034,
          "kind": "method",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L2034"
        },
        {
          "name": "PluginComposerApi.experimental_onSubmitted",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 2036,
          "kind": "method",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L2036"
        }
      ],
      "reference": [
        {
          "plugin": "composer-customization",
          "file": "examples/plugins/composer-customization/app.tsx",
          "note": "Reference implementation of effects plus onDraftChange over the structured draft."
        }
      ],
      "rules": [
        "richtext-paint-only",
        "composer-scopes",
        "frontend-no-network"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your matches paint over the draft text and never mutate it",
        "Offsets stay correct after the user edits around a mention",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L378",
      "guideAnchor": "surface-composer-rich-text",
      "mockupHash": "composer,surface=composer-rich-text"
    },
    {
      "kind": "surface",
      "id": "composer-state",
      "title": "Draft prompt state & locking",
      "groupTitle": "The composer",
      "number": 4,
      "summary": "Reads the draft prompt, and can block typing while the plugin works. With this, a plugin can:",
      "bullets": [
        "Read the draft prompt's text, whether it is empty, and how many files are attached",
        "Read the prompt box's layout and whether the thread is already running a turn",
        "Lock the input and release it again, so the draft prompt cannot change mid-operation",
        "Mark the thread row as running while the input is locked, with a [thread row status](thread-row-status)"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "useComposer() / useComposerView() inside a composer customization or slot",
      "propsType": "PluginComposerApi · ComposerView",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "ComposerView",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1921,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1921"
        },
        {
          "name": "PluginComposerApi",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1992,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1992"
        }
      ],
      "reference": [
        {
          "plugin": "drafts",
          "file": "plugins/drafts/app.tsx",
          "note": "Reads view.draft.isEmpty / view.run.isSubmitting to disable its plus-menu row and submits through the composer pipeline."
        }
      ],
      "rules": [
        "composer-write-target",
        "composer-scoped-effects",
        "frontend-no-network"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your reads and writes land in the composer that mounted your slot (thread draft, queued-message editor, side chat or new thread)",
        "setTextEffect and setInputLock release automatically when your slot unmounts",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L398",
      "guideAnchor": "surface-composer-state",
      "mockupHash": "composer,surface=composer-state"
    },
    {
      "kind": "surface",
      "id": "composer-plus-menu",
      "title": "The + menu",
      "groupTitle": "The composer",
      "number": 5,
      "summary": "Adds rows to the menu that opens from the + button beside the prompt box. With this, a plugin can:",
      "bullets": [
        "Supply each row's icon, label, and disabled state; bb renders the row itself",
        "Run a callback when someone picks the row",
        "Read and rewrite the draft prompt from that callback",
        "Send the draft at a time the person picks, through the prompt box's own send — so a scheduled message keeps its attachments, its @-mentions, and on the new-thread screen the agent and environment chosen on screen",
        "Submit the draft with plugin-owned JSON that its dispatch hook can interpret and use to queue the message"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.composer.customize({ id, plusMenu: [{ id, label, icon?, description?, disabled?, experimental_sendMenu?, run }] })",
      "propsType": "ComposerPlusMenuItem",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "ComposerPlusMenuItem",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1906,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1906"
        },
        {
          "name": "ExperimentalComposerSubmitOptions",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 2158,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L2158"
        }
      ],
      "reference": [
        {
          "plugin": "drafts",
          "file": "plugins/drafts/app.tsx",
          "note": "Whole plugin: a plus-menu row that submits with experimental_data and a dispatch hook that keeps it queued."
        },
        {
          "plugin": "scheduled-send",
          "file": "plugins/scheduled-send/app.tsx",
          "note": "Adds a send-menu row that schedules the draft with sendAt."
        }
      ],
      "rules": [
        "composer-submit-pipeline",
        "composer-scopes",
        "icons-hugeicons",
        "frontend-no-network"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your row appears in the + menu (and, with experimental_sendMenu, in the desktop send dropdown and mobile long-press menu)",
        "run() submits through composer.experimental_submit so attachments, mentions and picker selections travel with the message",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L411",
      "guideAnchor": "surface-composer-plus-menu",
      "mockupHash": "composer,surface=composer-plus-menu"
    },
    {
      "kind": "surface",
      "id": "provider-picker",
      "title": "Agent providers",
      "groupTitle": "The composer",
      "number": 6,
      "summary": "Adds an agent to bb's model picker and runs the threads started with it. With this, a plugin can:",
      "bullets": [
        "Appear in the model picker beside bb's built-in providers",
        "Declare what the provider supports, then serve its model list at runtime",
        "Supply a small icon that appears next to its name; React icon overrides require providerKind and providerId",
        "Publish context snapshots through contextWindow deltas, with provider-defined category IDs and labels. Each category declares used, free, reserved, or deferred accounting; entries are included in its total and may be partial. Snapshots include capture time, session identity, model, totals, and an optional auto-compaction threshold",
        "Receive every message in a thread started with it, through a bridge process the plugin ships",
        "Contribute validated environment variables to any provider for each session and turn"
      ],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.providers.register(declaration) — requires a bb.host entry; optional app.slots.experimental_providerIcon({ providerKind, providerId, icon })",
      "propsType": "PluginProviderDeclaration",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "contextSnapshotSchema",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 21,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L21"
        },
        {
          "name": "ContextSnapshot",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 32,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L32"
        },
        {
          "name": "ContextCategory",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 19,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L19"
        },
        {
          "name": "ContextEntry",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 10,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L10"
        },
        {
          "name": "PluginProviderDeclaration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1439,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1439"
        },
        {
          "name": "PluginProviderIconRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1415,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1415"
        },
        {
          "name": "ExperimentalPluginProviderEnvContext",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1715,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1715"
        },
        {
          "name": "ExperimentalPluginProviderEnvEntry",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1721,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1721"
        },
        {
          "name": "ExperimentalPluginProviderEnvHealthContext",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1727,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1727"
        },
        {
          "name": "ExperimentalPluginProviderEnvHealth",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1731,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1731"
        }
      ],
      "reference": [
        {
          "plugin": "provider-acp",
          "file": "plugins/provider-acp/src/declaration.ts",
          "note": "Builds one declaration per ACP agent and ships the bridge from the same host artifact."
        },
        {
          "plugin": "echo-provider",
          "file": "examples/plugins/echo-provider/src/provider-bridge.ts",
          "note": "Smallest complete bridge: handshake, a session start and the minimal turn loop."
        }
      ],
      "rules": [
        "provider-needs-host",
        "provider-id-immutable",
        "provider-derive-sync",
        "bridge-grammar-v3",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your provider appears in the composer's provider/model picker with its mark and models",
        "A thread runs end to end on it: input.accepted → turn.open → item deltas → turn.boundary",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L429",
      "guideAnchor": "surface-provider-picker",
      "mockupHash": "composer,surface=provider-picker"
    },
    {
      "kind": "surface",
      "id": "composer-actions",
      "title": "Inline actions",
      "groupTitle": "The composer",
      "number": 7,
      "summary": "Adds a button to the row of controls inside the prompt box, beside the voice and send buttons. With this, a plugin can:",
      "bullets": [
        "Read and rewrite the draft prompt, for example rephrasing it or inserting a template",
        "Insert an @-mention into the draft so its provider can resolve fresh context when the message is sent",
        "Lock the input while it works, and tint the whole draft while it does",
        "Set the composer's pickers (provider, model, reasoning level, service tier, permission mode, and on the new-thread screen the project and environment) through the same paths the pickers use, and read back what the composer settled on",
        "Render in the same row as bb's own prompt-box buttons. If you have more than 3 plugins enabled, bb keeps the 3 most-used plugins inline and moves the rest into an overflow menu"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.composer.customize({ id, actions: [{ id, component }] })",
      "propsType": "ComposerCustomization",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginComposerApi",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1992,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1992"
        },
        {
          "name": "ExperimentalComposerSelection",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 2130,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L2130"
        }
      ],
      "reference": [
        {
          "plugin": "composer-customization",
          "file": "examples/plugins/composer-customization/app.tsx",
          "note": "Registers an inline action beside the native controls."
        }
      ],
      "rules": [
        "composer-inline-cap",
        "composer-scopes",
        "frontend-no-network",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your button renders at the right end of the control row, before the mic and send buttons",
        "With three other plugins installed yours degrades into the overflow menu instead of breaking the row",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L462",
      "guideAnchor": "surface-composer-actions",
      "mockupHash": "composer,surface=composer-actions"
    },
    {
      "kind": "surface",
      "id": "homepage-section",
      "title": "Home-screen sections",
      "groupTitle": "Home page",
      "number": 1,
      "summary": "Adds a full-width section to the page bb opens on, below the prompt box. With this, a plugin can:",
      "bullets": [
        "Render its own component across the width of the content area",
        "Render before any thread exists, which suits shortcuts and pinned work",
        "Render after bb's own content, in the order plugins registered"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.homepageSection({ id, title, component })",
      "propsType": "PluginHomepageSectionProps",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginHomepageSectionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 504,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L504"
        }
      ],
      "reference": [],
      "rules": [
        "frontend-no-network",
        "no-worker-pool",
        "css-scope",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your section renders full width under the new-thread prompt box, after bb's own content",
        "projectId === null (no project selected) renders something sensible rather than throwing",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L485",
      "guideAnchor": "surface-homepage-section",
      "mockupHash": "home,surface=homepage-section"
    },
    {
      "kind": "surface",
      "id": "new-thread-panel",
      "title": "New-thread side panel",
      "groupTitle": "Home page",
      "number": 2,
      "summary": "Adds a plugin tab to the side panel on the new-thread screen. With this, a plugin can:",
      "bullets": [
        "Render before a thread exists, so it receives no thread id",
        "Host setup the person does while writing the first prompt",
        "Receive the project selected in the prompt box"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_newThreadPanelAction({ id, title, icon, component, layout?, run? })",
      "propsType": "PluginNewThreadPanelProps",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginNewThreadPanelActionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 727,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L727"
        }
      ],
      "reference": [],
      "rules": [
        "frontend-no-network",
        "panel-params-untrusted",
        "icons-hugeicons",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your row appears in the home screen's Actions list after Open browser and Start terminal",
        "run() opens your tab with the params you pass and projectId may be null",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L497",
      "guideAnchor": "surface-new-thread-panel",
      "mockupHash": "home,surface=new-thread-panel"
    },
    {
      "kind": "surface",
      "id": "declarative-settings",
      "title": "Settings fields",
      "groupTitle": "Plugin settings page",
      "number": 1,
      "summary": "Declares the settings your plugin needs as plain data; bb renders the form for them on the plugin's settings page and stores the values. With this, a plugin can:",
      "bullets": [
        "Declare each field's type (text, number, toggle, choice, or project) with a label and an optional default",
        "Get the form, its validation, and autosaving without writing any UI",
        "Validate each proposed value with a synchronous, non-transforming Standard Schema through `experimental_schema`; Zod schemas qualify",
        "Render multi-line text with `experimental_multiline`",
        "Mark a text field secret: bb stores it in a protected file on the server and never sends it to the browser",
        "Read values from server code, update them with `experimental_set`, or read non-secret values from plugin UI with `useSettings()`"
      ],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.settings.define({ <key>: { type, label, default?, secret? } })",
      "propsType": "PluginSettingDescriptor",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginSettings",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 162,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L162"
        },
        {
          "name": "PluginSettingsHandle",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 142,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L142"
        },
        {
          "name": "PluginSettingDescriptor",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 72,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L72"
        },
        {
          "name": "PluginSettingsState",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1852,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1852"
        }
      ],
      "reference": [
        {
          "plugin": "custom-instructions",
          "file": "plugins/custom-instructions/server.ts",
          "note": "Defines one setting, migrates a legacy kv value into it with experimental_set and tracks changes with onChange."
        },
        {
          "plugin": "github",
          "file": "plugins/github/server.ts",
          "note": "Validates a string setting with a zod superRefine through experimental_schema."
        }
      ],
      "rules": [
        "settings-load-once",
        "settings-secrets",
        "backend-full-trust",
        "dispose-lifo"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "bb renders your fields on the plugin's settings page and bb plugin config <id> set … writes them",
        "A secret: true field never reaches useSettings() and lives as a 0600 file, not in the database",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L519",
      "guideAnchor": "surface-declarative-settings",
      "mockupHash": "settings,surface=declarative-settings"
    },
    {
      "kind": "surface",
      "id": "settings-section",
      "title": "Custom settings section",
      "groupTitle": "Plugin settings page",
      "number": 2,
      "summary": "Renders your own React component on the plugin's settings page, below the [fields bb generated](declarative-settings). Use it for anything that is not a value in a form. With this, a plugin can:",
      "bullets": [
        "Render whatever UI it needs, such as a connect-account button, a test-connection result, or a preview",
        "Run in the browser, so it stores nothing itself. It calls the plugin's own backend to do that",
        "Supply a heading and a one-line description for bb to render above it"
      ],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.settingsSection({ id, title?, description?, component })",
      "propsType": "PluginSettingsSectionProps",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginSettingsSectionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 511,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L511"
        }
      ],
      "reference": [
        {
          "plugin": "connect",
          "file": "plugins/connect/app.tsx",
          "note": "Renders remote-access controls under the generated settings form."
        },
        {
          "plugin": "keep-awake",
          "file": "plugins/keep-awake/app.tsx",
          "note": "Renders per-host toggles backed by its own RPC."
        }
      ],
      "rules": [
        "frontend-no-network",
        "no-worker-pool",
        "css-scope",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your component renders directly below the generated Configuration card",
        "The plugin appears in the settings sidebar even with no declared settings, because the section alone qualifies it",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L545",
      "guideAnchor": "surface-settings-section",
      "mockupHash": "settings,surface=settings-section"
    },
    {
      "kind": "surface",
      "id": "plugin-status",
      "title": "Configuration status",
      "groupTitle": "Plugin page",
      "number": 1,
      "summary": "Reports that the plugin cannot run until someone configures it, so bb can say so instead of the plugin failing silently. With this, a plugin can:",
      "bullets": [
        "Set a needs-configuration state with a message naming what is missing",
        "Show a warning banner with that message on the plugin's page"
      ],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.status.needsConfiguration(message)",
      "propsType": "PluginStatusApi",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginStatusApi",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1961,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1961"
        }
      ],
      "reference": [
        {
          "plugin": "github",
          "file": "plugins/github/server.ts",
          "note": "Reports needs-configuration when gh auth is missing instead of failing the load."
        },
        {
          "plugin": "workflows",
          "file": "plugins/workflows/src/server.ts",
          "note": "Reports it from the factory so an unconfigured plugin does not crash-loop."
        }
      ],
      "rules": [
        "status-cleared-on-load",
        "needs-configuration-error",
        "backend-full-trust"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "An unconfigured plugin shows your message as a banner on its page and in bb plugin list",
        "After the user configures it, bb plugin reload <id> clears the state",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L567",
      "guideAnchor": "surface-plugin-status",
      "mockupHash": "plugins,surface=plugin-status"
    },
    {
      "kind": "surface",
      "id": "cli",
      "title": "bb CLI commands",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Registers a top-level `bb <name>` command, available in the terminal and to agents. With this, a plugin can:",
      "bullets": [
        "Be invoked the same way by a person at a terminal and by an agent mid-task",
        "Receive the thread and project it was invoked from, when bb knows them",
        "Make the plugin usable from scripts and automations, not only from the UI",
        "Declare commands, arguments and options once and get parsing, `--help`, nearest-name suggestions and JSON errors"
      ],
      "tagline": "Your own `bb <name>` command",
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.cli.register(defineCli({ name, summary, commands }))",
      "propsType": "PluginCliRegistration",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginCli",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1033,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1033"
        },
        {
          "name": "PluginCliResult",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 980,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L980"
        },
        {
          "name": "defineCli",
          "path": "packages/plugin-sdk/src/cli-spec.ts",
          "line": 1174,
          "kind": "function",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/cli-spec.ts#L1174"
        },
        {
          "name": "cliCommand",
          "path": "packages/plugin-sdk/src/cli-spec.ts",
          "line": 252,
          "kind": "function",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/cli-spec.ts#L252"
        },
        {
          "name": "PluginCliError",
          "path": "packages/plugin-sdk/src/cli-spec.ts",
          "line": 230,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/cli-spec.ts#L230"
        },
        {
          "name": "PluginCliSpec",
          "path": "packages/plugin-sdk/src/cli-spec.ts",
          "line": 211,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/cli-spec.ts#L211"
        },
        {
          "name": "PluginCliCommand",
          "path": "packages/plugin-sdk/src/cli-spec.ts",
          "line": 189,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/cli-spec.ts#L189"
        },
        {
          "name": "PluginCliOption",
          "path": "packages/plugin-sdk/src/cli-spec.ts",
          "line": 99,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/cli-spec.ts#L99"
        }
      ],
      "reference": [
        {
          "plugin": "concurrency-limit",
          "file": "plugins/concurrency-limit/server.ts",
          "note": "defineCli + cliCommand with typed options and PluginCliError with a hint."
        },
        {
          "plugin": "tasks",
          "file": "plugins/tasks/cli/index.ts",
          "note": "Grouped command paths such as \"project create\"."
        }
      ],
      "rules": [
        "cli-runs-on-server",
        "cli-output-cap",
        "cli-argv",
        "backend-full-trust"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "bb <name> --help renders without executing plugin code and bb <name> <command> returns your exit code",
        "Output stays under PLUGIN_CLI_OUTPUT_MAX_BYTES; the host rejects an oversized result atomically",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L621",
      "guideAnchor": "surface-cli",
      "mockupHash": "headless,surface=cli"
    },
    {
      "kind": "surface",
      "id": "agent-tools",
      "title": "Agent tools & skills",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Adds tools, skills, and instructions to the agent sessions bb runs. With this, a plugin can:",
      "bullets": [
        "Register tools an agent calls the same way it calls bb's built-in tools",
        "Decide per thread which of its tools and skills are available",
        "Append instructions to a session's system prompt as that session starts"
      ],
      "tagline": "Native tools, skills, and instructions in every session",
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.agents.registerTool({ name, description, parameters, execute }) · bb.agents.configure(provider) · bb.agents.contributeInstructions(provider)",
      "propsType": "PluginAgentToolContext",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginAgents",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1601,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1601"
        }
      ],
      "reference": [
        {
          "plugin": "workflows",
          "file": "plugins/workflows/src/server.ts",
          "note": "Registers two tools with presentation labels and narrows the parameter schema per resolution in configure()."
        },
        {
          "plugin": "ask-user-question",
          "file": "plugins/ask-user-question/src/server.ts",
          "note": "Drops its tool when the provider answers questions natively."
        }
      ],
      "rules": [
        "tools-next-session",
        "tool-name-global",
        "tool-output-bounded",
        "instructions-cap",
        "backend-full-trust"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "A new thread on a provider that selects your tool lists it and executes it with validated parameters",
        "Changing the tool set takes effect on the next session start, not mid-session",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L657",
      "guideAnchor": "surface-agent-tools",
      "mockupHash": "headless,surface=agent-tools"
    },
    {
      "kind": "surface",
      "id": "background",
      "title": "Background work",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Runs code on the bb server when no window is open. With this, a plugin can:",
      "bullets": [
        "Register long-running services that bb starts, supervises, and restarts after a failure",
        "Register jobs that run on a cron schedule",
        "Be told to shut down cleanly before it reloads or is disabled"
      ],
      "tagline": "Supervised services and cron schedules",
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.background.service(name, { start(signal) }) · bb.background.schedule(name, cron, fn)",
      "propsType": "PluginBackground",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginBackground",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 881,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L881"
        }
      ],
      "reference": [
        {
          "plugin": "automations",
          "file": "plugins/automations/src/server.ts",
          "note": "Startup reconciliation then a loop that sleeps on the abort signal."
        },
        {
          "plugin": "environment-modal-sandbox",
          "file": "plugins/environment-modal-sandbox/server.ts",
          "note": "A one-minute schedule that suspends idle machines."
        }
      ],
      "rules": [
        "service-abort-sleep",
        "schedule-only-while-loaded",
        "needs-configuration-error",
        "dispose-lifo"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your service starts after the factory and resolves when its signal aborts; bb plugin list shows it running",
        "Reload stops it within the 5 s window — no \"degraded (service did not stop)\"",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L677",
      "guideAnchor": "surface-background",
      "mockupHash": "headless,surface=background"
    },
    {
      "kind": "surface",
      "id": "wire",
      "title": "HTTP, WebSocket, RPC & realtime",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Connects the plugin's own UI, its server code, and outside services. With this, a plugin can:",
      "bullets": [
        "Call its server from its UI over RPC, with arguments and results checked against a schema",
        "Publish RPC methods with experimental_discoverable and registration/method experimental_description; other plugins discover implementations and copy their published JSON Schemas using bb plugin rpc inspect",
        "Serve exact-path HTTP and WebSocket routes other systems can call, webhooks included",
        "Push messages to every open bb window, so the UI does not have to poll"
      ],
      "tagline": "Typed RPC, HTTP & WebSocket routes, realtime push",
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.rpc.register(contract, handlers, options?) · bb.http.route(method, path, handler, { auth }) · bb.realtime.publish(channel, payload)",
      "propsType": "PluginRpcContract · PluginHttpHandler",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginRpc",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 848,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L848"
        },
        {
          "name": "PluginRpcMethodContract",
          "path": "packages/plugin-sdk/src/rpc-contract.ts",
          "line": 68,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/rpc-contract.ts#L68"
        },
        {
          "name": "PluginsArea.experimental_discoverRpc",
          "path": "packages/sdk/src/areas/plugins.ts",
          "line": 226,
          "kind": "method",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/sdk/src/areas/plugins.ts#L226"
        },
        {
          "name": "PluginHttp",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 819,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L819"
        },
        {
          "name": "PluginRealtime",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 867,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L867"
        },
        {
          "name": "ExperimentalPluginWebSocket",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 790,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L790"
        },
        {
          "name": "ExperimentalPluginWebSocketContext",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 796,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L796"
        },
        {
          "name": "ExperimentalPluginWebSocketHandler",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 815,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L815"
        },
        {
          "name": "ExperimentalPluginWebSocketHandlers",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 802,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L802"
        }
      ],
      "reference": [
        {
          "plugin": "tasks",
          "file": "plugins/tasks/attachments/index.ts",
          "note": "Token-authenticated upload route plus realtime publishes after every write."
        },
        {
          "plugin": "slack-bot",
          "file": "examples/plugins/slack-bot/server.ts",
          "note": "auth: \"none\" webhook that verifies the Slack signature inside the handler."
        }
      ],
      "rules": [
        "frontend-no-network",
        "rpc-strict-json",
        "http-exact-match",
        "realtime-broadcast",
        "backend-full-trust"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "useRpc().call(...) from app.tsx reaches your handler and returns typed, strict-JSON output",
        "A realtime publish reaches every open window and your UI reconciles on the next connected transition",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L701",
      "guideAnchor": "surface-wire",
      "mockupHash": "headless,surface=wire"
    },
    {
      "kind": "surface",
      "id": "thread-events",
      "title": "Thread lifecycle events",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Runs server code when a thread changes state. With this, a plugin can:",
      "bullets": [
        "Subscribe to threads being created, going active or idle, failing, being archived or unarchived, or being deleted",
        "Subscribe to messages being queued behind a wait, dispatching when it clears, or being cancelled before dispatch",
        "Subscribe when a thread receives a pending interaction",
        "Observe debounced experimental_thread.events notifications with the latest sequence and current thread, or experimental_terminal.input without keystroke contents",
        "Subscribe to a turn failing, with the provider's error and rate-limit windows attached",
        "Respond by sending a notification, asking for a retry, or writing to its own storage"
      ],
      "tagline": "React when threads start, finish, or fail",
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.events.on(event, handler)",
      "propsType": "PluginThreadEventPayloads",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginEvents",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1832,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1832"
        },
        {
          "name": "PluginThreadEventPayloads",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 267,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L267"
        },
        {
          "name": "PluginTurnFailedEvent",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 214,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L214"
        }
      ],
      "reference": [
        {
          "plugin": "provider-retry",
          "file": "plugins/provider-retry/server.ts",
          "note": "Retries by reference on turn.failed with bb.sdk.threads.retry."
        },
        {
          "plugin": "tasks",
          "file": "plugins/tasks/lifecycle/index.ts",
          "note": "Tracks thread lifecycle transitions to update its own rows."
        }
      ],
      "rules": [
        "events-are-announcements",
        "events-see-everything",
        "message-cancelled-only-signal",
        "backend-full-trust"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your handler runs on the real transitions (thread.active / thread.idle / thread.failed) and its return value is ignored",
        "Queue events are filtered to your own rows through entry.waitingOn.pluginId",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L739",
      "guideAnchor": "surface-thread-events",
      "mockupHash": "headless,surface=thread-events"
    },
    {
      "kind": "surface",
      "id": "dispatch-hook",
      "title": "Dispatch hook",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Answers the checkpoint every message passes on its way to a provider. With this, a plugin can:",
      "bullets": [
        "Let a dispatch proceed, queue it with a user-visible reason, or refuse it outright",
        "See the thread, project, machine, prompt and resolved execution tuple before the turn runs",
        "Read each queued message, its author, origin, and originPluginId in queuedMessages, with an empty array for inline attempts",
        "Read the shared initiator category or mixed for a grouped dispatch, and the shared senderThreadId, null when nobody sent it, or mixed",
        "Read the shared origin and originPluginId, each independently mixed when grouped messages differ",
        "Read plugin-owned JSON attached by experimental_submit, including on queued re-attempts",
        "Hold work until a moment it names, then ask core to re-decide every queued message when its condition changes"
      ],
      "tagline": "Decide whether a message may go",
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.experimental_hooks.on(\"message.dispatch\", handler) · bb.experimental_hooks.recheck(\"message.dispatch\")",
      "propsType": "MessageDispatchHookContext → MessageDispatchHookDecision",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginHooks",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 714,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L714"
        },
        {
          "name": "PluginHookSignatures",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 699,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L699"
        },
        {
          "name": "MessageDispatchHookContext",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 605,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L605"
        },
        {
          "name": "PluginDispatchEnvironmentIntent",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 502,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L502"
        },
        {
          "name": "MessageDispatchHookDecision",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 537,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L537"
        }
      ],
      "reference": [
        {
          "plugin": "drafts",
          "file": "plugins/drafts/server.ts",
          "note": "Shortest complete hook: waits forever on its own submissions until the user sends."
        },
        {
          "plugin": "concurrency-limit",
          "file": "plugins/concurrency-limit/server.ts",
          "note": "Counts running threads, waits with a reason and rechecks when capacity frees up."
        }
      ],
      "rules": [
        "hook-fail-closed",
        "hook-idempotent",
        "hook-no-amendment",
        "send-now-bypass",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "A message that should wait shows your reason on the queued card above the composer and a clock in the sidebar row",
        "Deciding takes milliseconds: a handler that throws or exceeds the 10 s box fails the attempt with your plugin named",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L766",
      "guideAnchor": "surface-dispatch-hook",
      "mockupHash": "headless,surface=dispatch-hook"
    },
    {
      "kind": "surface",
      "id": "environment-providers",
      "title": "Environment providers",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Offers plugin-provisioned places a thread can run, picked like any environment. With this, a plugin can:",
      "bullets": [
        "Declare a provider with a required display name, description, and icon, picked in New Thread or bb thread spawn --environment-provider",
        "Use a host glyph, plugin-relative asset, declared icon, or React provider icon slot targeted by required providerKind and providerId",
        "Declare the project facts it consumes in one place — requires.projectCheckout, requires.gitCheckout, requires.gitRemote, requires.projectless — which structurally decides where the picker offers it",
        "Answer availability for a project and machine with available, setup-required, or unavailable; core probes connected machines in the background so pickers hide unsupported ones, caches the answer, and checks it afresh for the selected machine at thread creation",
        "Declare what it needs from the request as a zod inputs schema; bb parses the request with it before the thread exists, publishes it as JSON Schema for the CLI, and hands create the parsed value as inputs",
        "Validate a resolved selection once before thread creation; host-dependent preflight requires connectivity, and create checks conditions that can change afterward",
        "Read the facts as typed values on the create context: host is always non-null, while projectCheckout and gitRemote are non-null exactly when required",
        "Read projectCheckout.experimental_ownsPath to distinguish core clones from user-maintained attachments; core runs environment hooks for owned paths",
        "Render its own control for those inputs beside the picked provider with app.slots.experimental_environmentProviderInputs, reporting either ready inputs or a blocked reason",
        "Use experimental_BranchPicker for a standard branch choice, or compose experimental_useBranches with experimental_useCheckoutState when it needs checkout-aware branch selection",
        "Run one idempotent long create call that returns a created directory or failure; a failed create is terminal and an explicit retry starts a new attempt on the same environment; provider policy exposes only retirement grace and path-key strategy",
        "Let bb run the repo setup hook after an owned-path create and teardown before removal; attached paths skip both hooks; unknown hook outcomes after daemon restart block automatic cleanup",
        "Use core's pathKey for stable resource identity; core records it as the environment instance key",
        "Reserve a shared checkout before mutation with create.experimental_claimPath; core holds the host/path claim through attachment or cleanup",
        "Name a branch the way bb would, from the suggestedBranchName core hands every create, and stream progress with report.step and report.log",
        "Honor create and remove abort signals; core aborts create before asking remove to clean everything under the same path key",
        "Work on the existing enrolled machine carried by the selection, returning the path it produced",
        "Environment input controls receive target: { kind: 'existing-host', hostId } or { kind: 'new-host' }; compositions reuse the underlying control before provisioning, and backend create receives the real host",
        "Register a composition with an explicit display name, description, icon, machineProviderId and environmentProviderId instead of lifecycle callbacks; core creates the machine and uses the concrete environment provider, preserving its checkout ownership",
        "Return an opaque JSON resource handle from a created launch; core keeps up to 16 KiB private and supplies it only to recovery and removal callbacks from the recorded owning plugin"
      ],
      "tagline": "Provision where a thread runs",
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.experimental_environments.register(declaration | composition)",
      "propsType": "PluginEnvironmentProviderDefinition",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginEnvironments",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 401,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L401"
        },
        {
          "name": "PluginEnvironmentProviderDeclaration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 390,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L390"
        },
        {
          "name": "PluginEnvironmentProviderRequirements",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 370,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L370"
        },
        {
          "name": "PluginEnvironmentValidateDecision",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 381,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L381"
        },
        {
          "name": "PluginEnvironmentProviderInputsRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1550,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1550"
        },
        {
          "name": "PluginEnvironmentProviderInputsProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1518,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1518"
        },
        {
          "name": "PluginEnvironmentProviderInputsChange",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1537,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1537"
        },
        {
          "name": "experimental_BranchPicker",
          "path": "packages/plugin-sdk/src/app.ts",
          "line": 62,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app.ts#L62"
        },
        {
          "name": "BranchPickerProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 2286,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L2286"
        },
        {
          "name": "experimental_useBranches",
          "path": "packages/plugin-sdk/src/app.ts",
          "line": 63,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app.ts#L63"
        },
        {
          "name": "UseBranchesArgs",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 2316,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L2316"
        },
        {
          "name": "BranchesState",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 2322,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L2322"
        },
        {
          "name": "experimental_useCheckoutState",
          "path": "packages/plugin-sdk/src/app.ts",
          "line": 64,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app.ts#L64"
        },
        {
          "name": "UseCheckoutStateArgs",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 2329,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L2329"
        },
        {
          "name": "CheckoutState",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 2334,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L2334"
        },
        {
          "name": "PluginEnvironmentProviderDefinition",
          "path": "packages/plugin-sdk/src/environment-provider.ts",
          "line": 106,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/environment-provider.ts#L106"
        },
        {
          "name": "PluginEnvironmentProviderInputsSchema",
          "path": "packages/plugin-sdk/src/environment-provider.ts",
          "line": 11,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/environment-provider.ts#L11"
        },
        {
          "name": "PluginEnvironmentProviderPolicy",
          "path": "packages/plugin-sdk/src/environment-provider.ts",
          "line": 98,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/environment-provider.ts#L98"
        },
        {
          "name": "PluginEnvironmentProviderValidateContext",
          "path": "packages/plugin-sdk/src/environment-provider.ts",
          "line": 29,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/environment-provider.ts#L29"
        },
        {
          "name": "PluginEnvironmentProviderAvailabilityContext",
          "path": "packages/plugin-sdk/src/environment-provider.ts",
          "line": 42,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/environment-provider.ts#L42"
        },
        {
          "name": "PluginEnvironmentProviderAvailability",
          "path": "packages/plugin-sdk/src/environment-provider.ts",
          "line": 49,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/environment-provider.ts#L49"
        },
        {
          "name": "PluginEnvironmentProviderCreateContext",
          "path": "packages/plugin-sdk/src/environment-provider.ts",
          "line": 54,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/environment-provider.ts#L54"
        },
        {
          "name": "PluginEnvironmentProviderCreateResult",
          "path": "packages/plugin-sdk/src/environment-provider.ts",
          "line": 72,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/environment-provider.ts#L72"
        },
        {
          "name": "PluginEnvironmentProviderProgress",
          "path": "packages/plugin-sdk/src/environment-provider.ts",
          "line": 24,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/environment-provider.ts#L24"
        },
        {
          "name": "PluginEnvironmentProviderRemoveContext",
          "path": "packages/plugin-sdk/src/environment-provider.ts",
          "line": 82,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/environment-provider.ts#L82"
        },
        {
          "name": "PluginEnvironmentProviderRemoveResult",
          "path": "packages/plugin-sdk/src/environment-provider.ts",
          "line": 93,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/environment-provider.ts#L93"
        }
      ],
      "reference": [
        {
          "plugin": "environment-personal-workspace",
          "file": "plugins/environment-personal-workspace/server.ts",
          "note": "Tiniest provider: requires projectless, one host call to create and one to remove."
        },
        {
          "plugin": "environment-git-worktree",
          "file": "plugins/environment-git-worktree/server.ts",
          "note": "Per-attempt path keys, existing-path adoption and experimental_claimPath."
        }
      ],
      "rules": [
        "env-create-idempotent",
        "env-failure-terminal",
        "env-inputs-public",
        "env-hooks-owned-by-core",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your provider appears in the New Thread environment picker and creates a workspace a thread can run in",
        "create() called twice with the same pathKey converges instead of allocating twice",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L791",
      "guideAnchor": "surface-environment-providers",
      "mockupHash": "headless,surface=environment-providers"
    },
    {
      "kind": "surface",
      "id": "machine-providers",
      "title": "Machine providers",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Adds plugin-provisioned machines that compose with environment providers. With this, a plugin can:",
      "bullets": [
        "Register bb.experimental_machines with a display name, one-line description, and required glyph, plugin-relative SVG, declared icon, or React icon",
        "Show the provider display name and icon as the kind next to every machine it creates; manually enrolled machines have no kind",
        "Declare automatic retirement for thread-created ephemeral machines; standalone machines remain until explicit removal. Core parses Standard Schema inputs and checks availability and validation before create",
        "Keep secrets in plugin settings because persisted machine inputs are readable by every plugin; pass only non-secret configuration or references",
        "Register an environment composition with machineProviderId and environmentProviderId to create a machine and then use a concrete environment provider; machine registration alone adds no picker option; CLI selects the composition with --environment-provider",
        "Create machines that belong to no project; projects reach a machine later through project sources",
        "Make create idempotent by its host launch key so a restart after enrolment recovers the same machine",
        "Bootstrap over a required MachineExecutor; core Manual setup uses internal enrollment operations; never put credentials in resource JSON or output",
        "Install a daemon for pending enrollment and restart an enrolled identity after snapshot restore",
        "Stream progress on the creating host and honor abort signals for create, suspend, resume and remove",
        "Keep credentials out of report.step and report.log: core persists progress and copies it into thread transcripts; core exposes manual enrollment commands transiently by host ID",
        "Await create.checkpoint(resource) immediately after allocation so cancellation can remove it without waiting for bootstrap",
        "Implement reconcileCleanup to discover and remove uncertain allocations by durable key when no checkpoint exists; remove receives known resources; never create or bootstrap; return failed while allocation intent is unresolved so core retries on its cleanup interval",
        "Await suspend.checkpoint(resource) before destructive cleanup",
        "Await resume.checkpoint(resource) before bootstrap; core fences provider ownership, phase and operation and recovers the same enrollment after restart",
        "Allocation checkpoints are recovery records, not filesystem saves; daemon-connected does not mean agent-ready",
        "Read the current persisted machine resource by host ID with bb.experimental_machines.getResource; reads work across plugins and return null for absent hosts or resources",
        "Own idle timing in the plugin using thread-sequence and terminal-input events plus background schedules",
        "Render one compact machine-inputs control in composed thread creation with app.slots.experimental_machineProviderInputs, reporting a ready non-secret JSON value on mount or a one-sentence blocked reason",
        "Request suspend/resume through the host SDK; calls return the updated host when the tracked operation starts, core coordinates drain, starting thread launches, provisioning environments, and project checkout setup reject suspend with machine_busy, and plugins own idle policy",
        "Read maintenance state and lifecycle failures from each host's lifecycle phase and message",
        "Call hosts.experimental_reconcile from plugin-owned maintenance to enforce core’s suspended state through the provider; active and transitional states are unchanged, the call returns after acceptance; poll host status for completion, and core does not poll. Suspend and resume must be idempotent: preserve stopped resources and reuse running compute. Request new pauses with experimental_suspend",
        "Await suspend.checkpoint(resource) to persist opaque resource state before termination; schedule vendor maintenance in the plugin using bb.background.schedule and bb.sdk.hosts.experimental_suspend",
        "Optionally declare suspend and resume together; plugins own idle timing and core coordinates transitions",
        "Return an opaque JSON resource that core persists and passes back to lifecycle operations; never include credentials",
        "Return a required readable machine name from create",
        "Treat a failed create as terminal and retry vendor API hiccups inside the create call"
      ],
      "tagline": "Create and own execution machines",
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.experimental_machines.register(declaration) + bb.experimental_machines.bootstrap({ key, executor, report, signal })",
      "propsType": "PluginMachineProviderDefinition",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginMachines",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 485,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L485"
        },
        {
          "name": "HostsArea.experimental_create",
          "path": "packages/sdk/src/areas/hosts.ts",
          "line": 102,
          "kind": "method",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/sdk/src/areas/hosts.ts#L102"
        },
        {
          "name": "HostsArea.experimental_getEnrollmentCommand",
          "path": "packages/sdk/src/areas/hosts.ts",
          "line": 103,
          "kind": "method",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/sdk/src/areas/hosts.ts#L103"
        },
        {
          "name": "HostsArea.experimental_listProviders",
          "path": "packages/sdk/src/areas/hosts.ts",
          "line": 120,
          "kind": "method",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/sdk/src/areas/hosts.ts#L120"
        },
        {
          "name": "HostsArea.experimental_suspend",
          "path": "packages/sdk/src/areas/hosts.ts",
          "line": 129,
          "kind": "method",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/sdk/src/areas/hosts.ts#L129"
        },
        {
          "name": "HostsArea.experimental_reconcile",
          "path": "packages/sdk/src/areas/hosts.ts",
          "line": 130,
          "kind": "method",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/sdk/src/areas/hosts.ts#L130"
        },
        {
          "name": "HostsArea.experimental_resume",
          "path": "packages/sdk/src/areas/hosts.ts",
          "line": 126,
          "kind": "method",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/sdk/src/areas/hosts.ts#L126"
        },
        {
          "name": "HostsArea.experimental_retryCleanup",
          "path": "packages/sdk/src/areas/hosts.ts",
          "line": 127,
          "kind": "method",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/sdk/src/areas/hosts.ts#L127"
        },
        {
          "name": "PluginMachines.getResource",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 487,
          "kind": "method",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L487"
        },
        {
          "name": "MachineExecutorRequest",
          "path": "packages/plugin-sdk/src/machine-bootstrap.ts",
          "line": 3,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/machine-bootstrap.ts#L3"
        },
        {
          "name": "MachineExecutor",
          "path": "packages/plugin-sdk/src/machine-bootstrap.ts",
          "line": 11,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/machine-bootstrap.ts#L11"
        },
        {
          "name": "MachineBootstrapRequest",
          "path": "packages/plugin-sdk/src/machine-bootstrap.ts",
          "line": 15,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/machine-bootstrap.ts#L15"
        },
        {
          "name": "MachineBootstrapApi",
          "path": "packages/plugin-sdk/src/machine-bootstrap.ts",
          "line": 22,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/machine-bootstrap.ts#L22"
        },
        {
          "name": "PluginMachineProviderDeclaration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 437,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L437"
        },
        {
          "name": "PluginMachineValidateDecision",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 433,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L433"
        },
        {
          "name": "PluginMachineProviderInputsRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1577,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1577"
        },
        {
          "name": "PluginMachineProviderInputsProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1562,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1562"
        },
        {
          "name": "PluginMachineProviderInputsChange",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1569,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1569"
        },
        {
          "name": "PluginMachineProviderDefinition",
          "path": "packages/plugin-sdk/src/machine-provider.ts",
          "line": 69,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/machine-provider.ts#L69"
        },
        {
          "name": "PluginMachineProviderInputsSchema",
          "path": "packages/plugin-sdk/src/machine-provider.ts",
          "line": 10,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/machine-provider.ts#L10"
        },
        {
          "name": "PluginMachineProviderAvailability",
          "path": "packages/plugin-sdk/src/machine-provider.ts",
          "line": 19,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/machine-provider.ts#L19"
        },
        {
          "name": "PluginMachineProviderValidateContext",
          "path": "packages/plugin-sdk/src/machine-provider.ts",
          "line": 24,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/machine-provider.ts#L24"
        },
        {
          "name": "PluginMachineProviderCreateContext",
          "path": "packages/plugin-sdk/src/machine-provider.ts",
          "line": 37,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/machine-provider.ts#L37"
        },
        {
          "name": "PluginMachineProviderCreateResult",
          "path": "packages/plugin-sdk/src/machine-provider.ts",
          "line": 46,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/machine-provider.ts#L46"
        },
        {
          "name": "PluginMachineProviderLifecycleContext",
          "path": "packages/plugin-sdk/src/machine-provider.ts",
          "line": 31,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/machine-provider.ts#L31"
        },
        {
          "name": "PluginMachineProviderProgress",
          "path": "packages/plugin-sdk/src/machine-provider.ts",
          "line": 14,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/machine-provider.ts#L14"
        },
        {
          "name": "PluginMachineProviderResourceResult",
          "path": "packages/plugin-sdk/src/machine-provider.ts",
          "line": 61,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/machine-provider.ts#L61"
        },
        {
          "name": "PluginMachineProviderResource",
          "path": "packages/plugin-sdk/src/machine-provider.ts",
          "line": 8,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/machine-provider.ts#L8"
        },
        {
          "name": "PluginMachineProviderRemoveResult",
          "path": "packages/plugin-sdk/src/machine-provider.ts",
          "line": 65,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/machine-provider.ts#L65"
        }
      ],
      "reference": [
        {
          "plugin": "environment-modal-sandbox",
          "file": "plugins/environment-modal-sandbox/providers/register.ts",
          "note": "The only first-party machine provider: create → checkpoint → bootstrap, plus suspend / resume / reconcileCleanup."
        }
      ],
      "rules": [
        "machine-checkpoint",
        "machine-needs-composition",
        "machine-resource-public",
        "machine-idle-is-yours",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Creating a machine enrolls a host daemon that connects back and appears in bb machine list",
        "Removal cleans up by durable key even when no checkpoint was ever written",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L850",
      "guideAnchor": "surface-machine-providers",
      "mockupHash": "headless,surface=machine-providers"
    },
    {
      "kind": "surface",
      "id": "server-access",
      "title": "Machine server access",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Registers server access for enrolment and ongoing machine runtime requests. With this, a plugin can:",
      "bullets": [
        "Register bb.experimental_serverAccess with picker copy, availability (including an optional public serverUrl), idempotent acquire and release",
        "Call recheck when access is gained or lost; refreshed configuration checks availability for Machines settings, manual setup and creation banners",
        "Return { id, serverUrl, headers? }; machines attach headers to all server requests without provider-specific redemption",
        "Choose a General default; core retains the selection for each machine; automatic selection uses the first registered provider, or direct when none are registered",
        "Use the Server URL reachable by machines setting or BB_EXTERNAL_URL fallback; the URL is not a reachability guarantee",
        "Return { status: 'failed', message } for a user-safe recovery message; persist acquisition intent and keep credentials in secret storage; release receives key, hostId and a nullable grantId to reconcile interrupted acquisitions before enrollment"
      ],
      "tagline": "Connect machines to their server",
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.experimental_serverAccess.register({ id, displayName, description, availability, acquire, release })",
      "propsType": "ServerAccessProviderDeclaration",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginServerAccess",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 475,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L475"
        },
        {
          "name": "ServerAccessProviderDeclaration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 449,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L449"
        },
        {
          "name": "ServerAccessGrant",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 443,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L443"
        }
      ],
      "reference": [
        {
          "plugin": "connect",
          "file": "plugins/connect/src/server-access.ts",
          "note": "The sole implementation: persists the intent before redeeming a code so release works before enrolment."
        }
      ],
      "rules": [
        "access-acquire-idempotent",
        "access-release-null-grant",
        "machine-resource-public",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "A new machine reaches the server through your grant: serverUrl plus headers are used for enrolment and runtime",
        "release() reconciles by key and hostId even when grantId is null",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L920",
      "guideAnchor": "surface-server-access",
      "mockupHash": "headless,surface=server-access"
    },
    {
      "kind": "surface",
      "id": "host-workers",
      "title": "Host workers",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Runs the plugin's code on an enrolled machine, not only on the bb server. With this, a plugin can:",
      "bullets": [
        "Ship a Node entry point bb starts on demand on the machine it calls",
        "Call that worker from its server code over typed RPC",
        "Do work that has to happen on the machine itself, such as watching files or holding a wake lock",
        "Declare desired loopback ports once and let bb deliver retained declarations when an enrolled machine reconnects",
        "Kill whatever is still running under a directory it is about to delete, SIGTERM then SIGKILL, so a torn-down workspace leaves nothing behind",
        "Spawn host-local commands with a sanitized inherited environment"
      ],
      "tagline": "Run code on enrolled machines",
      "entry": "host",
      "manifestEntry": "bb.host",
      "registration": "bb.hosts.experimental_client({ contract, experimental_signals }) on the server + experimental_defineHostEntry({ contract, handlers, dispose }) in bb.host",
      "propsType": "ExperimentalHostRpcContext",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginHosts",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1926,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1926"
        },
        {
          "name": "experimental_killProcessesWithCwdUnder",
          "path": "packages/plugin-sdk/src/host.ts",
          "line": 44,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/host.ts#L44"
        },
        {
          "name": "experimental_sanitizeInheritedChildProcessEnv",
          "path": "packages/plugin-sdk/src/host.ts",
          "line": 52,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/host.ts#L52"
        },
        {
          "name": "ExperimentalSanitizeInheritedChildProcessEnvArgs",
          "path": "packages/plugin-sdk/src/host.ts",
          "line": 55,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/host.ts#L55"
        },
        {
          "name": "experimental_spawnPortableOutputProcess",
          "path": "packages/plugin-sdk/src/host.ts",
          "line": 53,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/host.ts#L53"
        }
      ],
      "reference": [
        {
          "plugin": "keep-awake",
          "file": "plugins/keep-awake/server.ts",
          "note": "Calls the host worker to hold a wake lock and reacts to experimental_onWorkerExit."
        },
        {
          "plugin": "environment-git-worktree",
          "file": "plugins/environment-git-worktree/host.ts",
          "note": "Streams progress back with experimental_emitSignal correlated by an operationId."
        }
      ],
      "rules": [
        "host-call-not-in-factory",
        "host-no-private-imports",
        "host-limits",
        "host-worker-exit",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "bb plugin build emits dist/host.js and the daemon runs it after verifying its sha256 digest",
        "A call from an RPC handler or service reaches the worker; calls made during the factory are rejected",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L942",
      "guideAnchor": "surface-host-workers",
      "mockupHash": "headless,surface=host-workers"
    },
    {
      "kind": "surface",
      "id": "storage",
      "title": "Storage",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Stores the plugin's data on the bb server. With this, a plugin can:",
      "bullets": [
        "Get a key-value store for small values such as flags and cursors",
        "Store internal credentials in plugin KV without exposing settings fields",
        "Get its own SQLite database, with migrations, for larger or relational data",
        "Reject a changed or reused migration number before it can hide a schema change",
        "Read and write only its own namespace; other plugins cannot see it"
      ],
      "tagline": "Namespaced KV plus your own SQLite",
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.storage.kv.{get,set,delete,list} · bb.storage.database() · bb.storage.migrate(db, statements)",
      "propsType": "PluginStorage",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginStorage",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 179,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L179"
        }
      ],
      "reference": [
        {
          "plugin": "memory",
          "file": "plugins/memory/server.ts",
          "note": "Owns a SQLite schema through migrate() and queries it directly."
        },
        {
          "plugin": "side-chat",
          "file": "plugins/side-chat/server.ts",
          "note": "Uses namespaced kv with a prefix sweep instead of a database."
        }
      ],
      "rules": [
        "kv-value-cap",
        "migrations-append-only",
        "storage-not-a-boundary",
        "backend-full-trust"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your data survives a plugin reload and a bb restart",
        "Adding a migration appends a new statement instead of editing an applied one",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L972",
      "guideAnchor": "surface-storage",
      "mockupHash": "headless,surface=storage"
    },
    {
      "kind": "surface",
      "id": "bb-sdk",
      "title": "The bb SDK",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Calls bb's own API from the plugin's server code. With this, a plugin can:",
      "bullets": [
        "Create threads, send messages to them, and manage projects",
        "Spawn or fork with lifecycleOwnerThreadId to archive/delete a dependent with a live owner across projects; ownership is immutable, independent of sidebar parents and supports different hosts/environments. Thread responses return the owner or null. Unarchive owner first; Stop does not cascade",
        "List machines and suspend, resume, or remove provider-managed machines",
        "Read recorded context usage with sdk.threads.context({ threadId }); usage is null when unavailable, and its snapshot is present only when the latest measurement includes a breakdown",
        "Reach the same operations the [bb CLI](cli) and the bb UI use",
        "Have the threads it creates attributed back to the plugin",
        "Read the server's loopback URL, public app URL, and data directory when it needs server facts"
      ],
      "tagline": "Create threads and projects from plugin code",
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.sdk.<area>.<method>(...) — threads, projects, environments, hosts, files, terminals, skills, plugins, theme, system",
      "propsType": "PluginBbSdk",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "BbPluginApi",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 2002,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L2002"
        },
        {
          "name": "PluginServerApi",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1847,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1847"
        }
      ],
      "reference": [
        {
          "plugin": "side-chat",
          "file": "plugins/side-chat/server.ts",
          "note": "Forks hidden threads and lists them back by originPluginId."
        },
        {
          "plugin": "concurrency-limit",
          "file": "plugins/concurrency-limit/server.ts",
          "note": "Reads threads.listRunning() and subscribes to host changes."
        }
      ],
      "rules": [
        "sdk-bind-gated",
        "sdk-hidden-threads",
        "sdk-attribution",
        "backend-full-trust"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your call runs from a handler, service or timer (not from the factory in a harness) and returns real data",
        "Every hidden thread you spawn is stopped and archived in a finally block",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L999",
      "guideAnchor": "surface-bb-sdk",
      "mockupHash": "headless,surface=bb-sdk"
    },
    {
      "kind": "surface",
      "id": "thread-plugin-metadata",
      "title": "Thread plugin metadata",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Stores namespaced plugin JSON for a thread without automatically exposing it to the model. With this, a plugin can:",
      "bullets": [
        "Seed its namespace when spawning a thread or explicitly when forking one",
        "Read and atomically patch any namespace allowed by ordinary thread access",
        "Receive only its own deep-frozen namespace in bb.agents.configure",
        "Keep up to 256 KiB of JSON per namespace; a patch that would exceed it fails and leaves the namespace unchanged"
      ],
      "tagline": "Keep plugin data with a thread",
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.sdk.threads.getPluginMetadata({ threadId }) · bb.sdk.threads.updatePluginMetadata({ threadId, set, remove })",
      "propsType": "ThreadPluginMetadataResult",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginBbSdk",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1981,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1981"
        },
        {
          "name": "PluginAgentConfigurationContext",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1130,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1130"
        },
        {
          "name": "ReadonlyJsonValue",
          "path": "packages/plugin-sdk/src/json-value.ts",
          "line": 20,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/json-value.ts#L20"
        },
        {
          "name": "BbPluginApi",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 2002,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L2002"
        }
      ],
      "reference": [],
      "rules": [
        "metadata-untrusted",
        "metadata-size",
        "metadata-frozen-in-configure",
        "backend-full-trust"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your namespace travels with the thread and is readable from bb.agents.configure as a frozen snapshot",
        "An oversized patch fails with 413 and leaves the previous value intact",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L1029",
      "guideAnchor": "surface-thread-plugin-metadata",
      "mockupHash": "headless,surface=thread-plugin-metadata"
    },
    {
      "kind": "surface",
      "id": "desktop-browsers",
      "title": "Desktop browser control",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Controls a selected desktop window through bb.sdk.experimental_desktopBrowsers. With this, a plugin can:",
      "bullets": [
        "Discover instances on an explicit host and create thread-owned tabs with separate automation profiles",
        "Acquire expiring control; reveal the first tab and new CDP pages only in the already focused thread, without activating the desktop window. Personal tabs require an explicit handoff",
        "Give a worker on that host a private, scoped CDP WebSocket connection for DevBrowser or agent-browser",
        "Capture or reveal a tab and release control while preserving the tab and its login",
        "Observe changed tab and control state with a disposable two-second polling subscription; report disconnect errors",
        "List browsers installed on the desktop host and copy a profile's signed-in cookies into the personal BB browser or an automation profile"
      ],
      "tagline": "Use your automation tool on BB-owned tabs",
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.sdk.experimental_desktopBrowsers.{listInstances,createTab,acquireControl,openConnection,releaseControl,closeTab,…}",
      "propsType": "ExperimentalDesktopBrowsersArea",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "ExperimentalDesktopBrowsersArea",
          "path": "packages/sdk/src/areas/desktop-browsers.ts",
          "line": 40,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/sdk/src/areas/desktop-browsers.ts#L40"
        },
        {
          "name": "ExperimentalDesktopBrowserScope",
          "path": "packages/server-contract/src/api/desktop-browsers.ts",
          "line": 61,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/server-contract/src/api/desktop-browsers.ts#L61"
        },
        {
          "name": "ExperimentalDesktopBrowserLease",
          "path": "packages/server-contract/src/api/desktop-browsers.ts",
          "line": 83,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/server-contract/src/api/desktop-browsers.ts#L83"
        },
        {
          "name": "ExperimentalDesktopBrowserCreateInput",
          "path": "packages/server-contract/src/api/desktop-browsers.ts",
          "line": 77,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/server-contract/src/api/desktop-browsers.ts#L77"
        },
        {
          "name": "ExperimentalDesktopBrowserAcquireInput",
          "path": "packages/server-contract/src/api/desktop-browsers.ts",
          "line": 80,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/server-contract/src/api/desktop-browsers.ts#L80"
        },
        {
          "name": "ExperimentalDesktopBrowserInstanceRequest",
          "path": "packages/server-contract/src/api/desktop-browsers.ts",
          "line": 106,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/server-contract/src/api/desktop-browsers.ts#L106"
        },
        {
          "name": "ExperimentalDesktopBrowserImportCookiesInput",
          "path": "packages/server-contract/src/api/desktop-browsers.ts",
          "line": 112,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/server-contract/src/api/desktop-browsers.ts#L112"
        },
        {
          "name": "ExperimentalDesktopBrowserImportSources",
          "path": "packages/server-contract/src/api/desktop-browsers.ts",
          "line": 115,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/server-contract/src/api/desktop-browsers.ts#L115"
        },
        {
          "name": "ExperimentalDesktopBrowserImportOutcome",
          "path": "packages/server-contract/src/api/desktop-browsers.ts",
          "line": 117,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/server-contract/src/api/desktop-browsers.ts#L117"
        }
      ],
      "reference": [
        {
          "plugin": "browser-automation",
          "file": "plugins/browser-automation/server.ts",
          "note": "The only consumer: scope → createTab → acquireControl → openConnection, with min() over every expiry."
        }
      ],
      "rules": [
        "browser-lease-ttl",
        "browser-allow-personal",
        "browser-host-match",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your automation drives a real desktop tab through the scoped CDP endpoint",
        "Releasing control and closing only the tabs you created leaves the user's own tabs and logins intact",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L1048",
      "guideAnchor": "surface-desktop-browsers",
      "mockupHash": "headless,surface=desktop-browsers"
    },
    {
      "kind": "surface",
      "id": "ai-services",
      "title": "AI services",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Lets a plugin answer bb's own helper-model calls — the short model calls behind thread titles and commit messages, and the microphone button's transcription. With this, a plugin can:",
      "bullets": [
        "Serve those calls from an enrolled machine, so bb's helper model can be one the plugin holds the credentials for",
        "Serve voice transcription the same way, for the microphone button in the prompt box",
        "Appear as a choice in the AI-service settings, alongside the models bb reaches itself"
      ],
      "tagline": "Serve bb's helper model from your own machine",
      "entry": "host",
      "manifestEntry": "bb.host",
      "registration": "bb.experimental_aiServices.register({ id, displayName, kinds }) + experimental_aiServicesHostContract in bb.host",
      "propsType": "PluginAiServiceDeclaration",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginAiServices",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1903,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1903"
        },
        {
          "name": "PluginAiServiceDeclaration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1894,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1894"
        }
      ],
      "reference": [
        {
          "plugin": "provider-codex",
          "file": "plugins/provider-codex/server.ts",
          "note": "The only first-party AI service: registers inference and voice from the codex host entry."
        }
      ],
      "rules": [
        "ai-needs-host",
        "ai-failures-returned",
        "ai-reserved-ids",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "With BB_INFERENCE=<id>/<model> bb's helper inference (thread titles, commit messages) runs through your host entry",
        "Failures come back as { ok: false, code } so core can apply its retry and fallback policy",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L1076",
      "guideAnchor": "surface-ai-services",
      "mockupHash": "headless,surface=ai-services"
    },
    {
      "kind": "surface",
      "id": "host-components",
      "title": "Host components",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Renders bb's conversation, prompt box, and shared app icons inside plugin pages. With this, a plugin can:",
      "bullets": [
        "Embed the thread view and the new-thread prompt box as components",
        "Seed experimental_NewThreadComposer or navigate.toCompose with initialPrompt containing @thread:<id>, @project:<id>, or @section:<id> to create mention pills with host-resolved labels; composer seeds preserve non-empty drafts",
        "Render message text with the same Markdown renderer bb uses",
        "Resolve document links and images beside a workspace or thread-storage file with Markdown.experimental_document",
        "Inherit bb's styling, so embedded UI matches the rest of the app",
        "Register inline React artwork with app.experimental_icons.register({ name, component }); namespacing is recommended, but any plugin can use any name",
        "Add names or override built-in app icons; conflicts between plugins use the first plugin id in lexical order and warn, while duplicate names within one plugin reject setup",
        "Render experimental_ProviderIcon with required providerKind (agent, machine, environment), provider={provider}, and optional fallback (Code by default). Pass an existing provider record: it reads id, logoUrl, icon, and strings.iconTint, resolving the matching kind/id slot override, then legacy unscoped overrides, then declared artwork. It fetches no metadata. Marks are decorative by default; pass aria-label for a meaningful standalone image",
        "Provider icons update on plugin load/reload/unload; throwing or recursive overrides fall back to supplied artwork. Use the same component for agent, machine, and environment providers",
        "Render experimental_Icon with a name and optional fallback; missing names try the fallback, then Zap. Registered artwork receives className and should use currentColor",
        "Return nothing from icon registration: bb replaces icons on plugin reload and restores previous definitions on unload. Manifest branding and SVG asset declarations remain separate",
        "Load the same registrations in web, desktop, and mobile's web app; manage their plugin through bb plugin build, install, reload, and remove"
      ],
      "tagline": "Embed bb's chat and prompt box",
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "import { ThreadChat, Markdown, experimental_SourceCode, experimental_Diff, experimental_Icon, … } from \"@get-bb/plugin-sdk/app\"",
      "propsType": "ThreadChatProps · MarkdownProps",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "experimental_Icon",
          "path": "packages/plugin-sdk/src/app.ts",
          "line": 49,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app.ts#L49"
        },
        {
          "name": "experimental_ProviderIcon",
          "path": "packages/plugin-sdk/src/app.ts",
          "line": 50,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app.ts#L50"
        },
        {
          "name": "ExperimentalProviderIconProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1765,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1765"
        },
        {
          "name": "ExperimentalIconProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1754,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1754"
        },
        {
          "name": "ExperimentalIconRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1792,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1792"
        },
        {
          "name": "ExperimentalAppIcons",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1799,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1799"
        },
        {
          "name": "PluginAppBuilder.experimental_icons",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1811,
          "kind": "property",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1811"
        },
        {
          "name": "ThreadChat",
          "path": "packages/plugin-sdk/src/app.ts",
          "line": 52,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app.ts#L52"
        },
        {
          "name": "Markdown",
          "path": "packages/plugin-sdk/src/app.ts",
          "line": 53,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app.ts#L53"
        },
        {
          "name": "MarkdownProps.experimental_document",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 2531,
          "kind": "property",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L2531"
        },
        {
          "name": "experimental_NewThreadComposer",
          "path": "packages/plugin-sdk/src/app.ts",
          "line": 56,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app.ts#L56"
        }
      ],
      "reference": [
        {
          "plugin": "side-chat",
          "file": "plugins/side-chat/app.tsx",
          "note": "Embeds ThreadChat in compact mode with its own messageActions."
        },
        {
          "plugin": "thread-chat-demo",
          "file": "examples/plugins/thread-chat-demo/app.tsx",
          "note": "Demonstrates ThreadChat plus targeted fixed tabs."
        }
      ],
      "rules": [
        "jsx-alias-experimental",
        "threadchat-owns-thread",
        "no-worker-pool",
        "shims-devdeps"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your panel renders a live thread through ThreadChat instead of proxying thread data through your own RPC",
        "experimental_* components are imported under an aliased capitalised name so JSX compiles",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L1091",
      "guideAnchor": "surface-host-components",
      "mockupHash": "headless,surface=host-components"
    },
    {
      "kind": "surface",
      "id": "testing",
      "title": "Testing harnesses",
      "groupTitle": "Plugin backend",
      "number": null,
      "summary": "Tests the plugin without a running bb. With this, a plugin can:",
      "bullets": [
        "Run its server code against an in-process fake of the bb server",
        "Render its UI slots under vitest and jsdom",
        "Drive its host worker with no host daemon running"
      ],
      "tagline": "Unit-test every surface without a running bb",
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "createFakePluginHost(options) · loadPluginApp(() => import(\"./app\")) + renderSlot(...) · createFakeSdk(...) · experimental_createHostEntryHarness(entry)",
      "propsType": "FakePluginHost · CapturedPluginApp",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "createFakePluginHost",
          "path": "packages/plugin-sdk/src/testing/fake-plugin-host.ts",
          "line": 625,
          "kind": "function",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/testing/fake-plugin-host.ts#L625"
        },
        {
          "name": "renderSlot",
          "path": "packages/plugin-sdk/src/testing/app.tsx",
          "line": 1355,
          "kind": "function",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/testing/app.tsx#L1355"
        },
        {
          "name": "createFakeSdk",
          "path": "packages/plugin-sdk/src/testing/fake-sdk.ts",
          "line": 137,
          "kind": "function",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/testing/fake-sdk.ts#L137"
        },
        {
          "name": "experimental_createHostEntryHarness",
          "path": "packages/plugin-sdk/src/testing/host.ts",
          "line": 98,
          "kind": "function",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/testing/host.ts#L98"
        }
      ],
      "reference": [
        {
          "plugin": "tasks",
          "file": "plugins/tasks/app.test.tsx",
          "note": "Frontend harness driven by realtime, asserting on inspection.rpcCalls."
        },
        {
          "plugin": "echo-provider",
          "file": "examples/plugins/echo-provider/provider-bridge.conformance.test.ts",
          "note": "Bridge conformance, parity and stream tests against recordings."
        }
      ],
      "rules": [
        "harness-same-collector",
        "harness-stubs",
        "dispose-lifo"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your surfaces are exercised without a running bb: RPC, CLI, services and slots all covered",
        "The harness disposes cleanly in a finally block so temporary storage is removed",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-api-map/src/surfaces.ts#L1126",
      "guideAnchor": "surface-testing",
      "mockupHash": "headless,surface=testing"
    },
    {
      "kind": "slot",
      "id": "homepageSection",
      "title": "app.slots.homepageSection",
      "groupTitle": null,
      "number": null,
      "summary": "Adds a full-width section to the page bb opens on, below the prompt box. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.homepageSection({ id, title, component })",
      "propsType": "PluginHomepageSectionProps",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginHomepageSectionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 504,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L504"
        }
      ],
      "reference": [],
      "rules": [
        "frontend-no-network",
        "no-worker-pool",
        "css-scope",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your section renders full width under the new-thread prompt box, after bb's own content",
        "projectId === null (no project selected) renders something sensible rather than throwing",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-homepageSection",
      "mockupHash": "home,surface=homepage-section"
    },
    {
      "kind": "slot",
      "id": "settingsSection",
      "title": "app.slots.settingsSection",
      "groupTitle": null,
      "number": null,
      "summary": "Renders your own React component on the plugin's settings page, below the [fields bb generated](declarative-settings). Use it for anything that is not a value in a form. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.settingsSection({ id, title?, description?, component })",
      "propsType": "PluginSettingsSectionProps",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginSettingsSectionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 511,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L511"
        }
      ],
      "reference": [
        {
          "plugin": "connect",
          "file": "plugins/connect/app.tsx",
          "note": "Renders remote-access controls under the generated settings form."
        },
        {
          "plugin": "keep-awake",
          "file": "plugins/keep-awake/app.tsx",
          "note": "Renders per-host toggles backed by its own RPC."
        }
      ],
      "rules": [
        "frontend-no-network",
        "no-worker-pool",
        "css-scope",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your component renders directly below the generated Configuration card",
        "The plugin appears in the settings sidebar even with no declared settings, because the section alone qualifies it",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-settingsSection",
      "mockupHash": "settings,surface=settings-section"
    },
    {
      "kind": "slot",
      "id": "experimental_appOverlay",
      "title": "app.slots.experimental_appOverlay",
      "groupTitle": null,
      "number": null,
      "summary": "Mounts floating plugin UI across the bb app, outside route-owned layout regions. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_appOverlay({ id, component })",
      "propsType": "ExperimentalAppOverlayProps",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "ExperimentalAppOverlayRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 534,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L534"
        },
        {
          "name": "ExperimentalAppOverlayProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 59,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L59"
        }
      ],
      "reference": [
        {
          "plugin": "push-notifications",
          "file": "plugins/push-notifications/app.tsx",
          "note": "Mounts a delivery overlay once per app window."
        },
        {
          "plugin": "browser-automation",
          "file": "plugins/browser-automation/app.tsx",
          "note": "Mounts a preview lightbox outside the layout regions."
        }
      ],
      "rules": [
        "overlay-owns-chrome",
        "frontend-no-network",
        "crash-boundary",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your overlay mounts once per app window and survives route changes",
        "It positions and hides itself: bb supplies no chrome, no placement and no visibility",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-experimental_appOverlay",
      "mockupHash": "shell,surface=app-overlay"
    },
    {
      "kind": "slot",
      "id": "navPanel",
      "title": "app.slots.navPanel",
      "groupTitle": null,
      "number": null,
      "summary": "Adds a row to bb's sidebar that opens a page your plugin renders where threads normally appear. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.navPanel({ id, title, icon, path, component, fixedTabs?, experimental_sidebarAccessory?, headerContent? })",
      "propsType": "PluginNavPanelProps",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginNavPanelRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 600,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L600"
        }
      ],
      "reference": [
        {
          "plugin": "tasks",
          "file": "plugins/tasks/app.tsx",
          "note": "Registers a nav panel with a sidebar accessory and a fixed tab, and routes the remaining subPath inside the page."
        },
        {
          "plugin": "github",
          "file": "plugins/github/app.tsx",
          "note": "Uses headerContent to put controls into the shared app title bar."
        }
      ],
      "rules": [
        "frontend-no-network",
        "shims-devdeps",
        "css-scope",
        "icons-hugeicons",
        "crash-boundary",
        "panel-params-untrusted"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "A row with your icon and title appears under the built-in navigation rows and opens /plugins/<plugin-id>/<path>",
        "Deep links into the page work: navigating to /plugins/<plugin-id>/<path>/foo hands your component subPath = \"foo\"",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-navPanel",
      "mockupHash": "shell,surface=nav-panel"
    },
    {
      "kind": "slot",
      "id": "threadPanelAction",
      "title": "app.slots.threadPanelAction",
      "groupTitle": null,
      "number": null,
      "summary": "Adds a tab to the side panel that opens to the right of a thread. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.threadPanelAction({ id, title, icon, component, layout?, run? })",
      "propsType": "PluginThreadPanelProps",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginThreadPanelActionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 684,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L684"
        }
      ],
      "reference": [
        {
          "plugin": "side-chat",
          "file": "plugins/side-chat/app.tsx",
          "note": "Registers the panel action a message action opens, with layout \"flush\"."
        },
        {
          "plugin": "tasks",
          "file": "plugins/tasks/app.tsx",
          "note": "Opens a task panel tab from the thread side panel."
        }
      ],
      "rules": [
        "frontend-no-network",
        "panel-params-untrusted",
        "icons-hugeicons",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your row appears in the thread side panel's new-tab Actions list and opens a tab",
        "Re-opening the tab from persisted params re-fetches by id instead of trusting the stored payload",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-threadPanelAction",
      "mockupHash": "shell,surface=thread-panel"
    },
    {
      "kind": "slot",
      "id": "experimental_newThreadPanelAction",
      "title": "app.slots.experimental_newThreadPanelAction",
      "groupTitle": null,
      "number": null,
      "summary": "Adds a plugin tab to the side panel on the new-thread screen. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_newThreadPanelAction({ id, title, icon, component, layout?, run? })",
      "propsType": "PluginNewThreadPanelProps",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginNewThreadPanelActionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 727,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L727"
        }
      ],
      "reference": [],
      "rules": [
        "frontend-no-network",
        "panel-params-untrusted",
        "icons-hugeicons",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your row appears in the home screen's Actions list after Open browser and Start terminal",
        "run() opens your tab with the params you pass and projectId may be null",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-experimental_newThreadPanelAction",
      "mockupHash": "home,surface=new-thread-panel"
    },
    {
      "kind": "slot",
      "id": "pendingInteraction",
      "title": "app.slots.pendingInteraction",
      "groupTitle": null,
      "number": null,
      "summary": "Asks the person a question in the thread composer and delivers their answer to the agent, even if the original turn has ended. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.pendingInteraction({ id, component }) paired with bb.ui.requestInput({ rendererId: id, … })",
      "propsType": "PluginPendingInteractionProps",
      "slotKind": "replacement",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginUi",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1808,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1808"
        },
        {
          "name": "PluginPendingInteractionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 745,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L745"
        }
      ],
      "reference": [
        {
          "plugin": "ask-user-question",
          "file": "plugins/ask-user-question/src/server.ts",
          "note": "Requests a multiple-choice answer from the backend and renders it with the paired slot."
        },
        {
          "plugin": "secrets",
          "file": "plugins/secrets/src/server.ts",
          "note": "Asks for credentials from a CLI command and reconciles them into a dotenv file."
        }
      ],
      "rules": [
        "interaction-detached",
        "interaction-limits",
        "secrets-agent-reach",
        "frontend-no-network",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "While the interaction is pending your form replaces the composer and submit / cancel resolve the backend promise",
        "Every cancellation reason is handled: user, request-aborted, thread-stopped, thread-deleted, plugin-disposed, server-restarted, timeout",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-pendingInteraction",
      "mockupHash": "shell,surface=pending-interaction"
    },
    {
      "kind": "slot",
      "id": "sidebarFooter",
      "title": "app.experimental_sidebarFooter.register",
      "groupTitle": null,
      "number": null,
      "summary": "Adds a host-rendered icon item to the bottom of bb's sidebar. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.experimental_sidebarFooter.register({ kind: \"action\" | \"disclosure\", id, label, icon, onActivate | component })",
      "propsType": "ExperimentalSidebarFooterDisclosureProps",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "ExperimentalSidebarFooter",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 835,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L835"
        },
        {
          "name": "ExperimentalSidebarFooterItemBase",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 797,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L797"
        },
        {
          "name": "ExperimentalSidebarFooterItemRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 820,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L820"
        },
        {
          "name": "ExperimentalSidebarFooterActionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 806,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L806"
        },
        {
          "name": "ExperimentalSidebarFooterActionContext",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 791,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L791"
        },
        {
          "name": "ExperimentalSidebarFooterDisclosureRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 814,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L814"
        },
        {
          "name": "ExperimentalSidebarFooterDisclosureProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 124,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L124"
        },
        {
          "name": "ExperimentalSidebarFooterDisclosureController",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 825,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L825"
        }
      ],
      "reference": [
        {
          "plugin": "connect",
          "file": "plugins/connect/app.tsx",
          "note": "Registers a footer action that opens the plugin's own details page."
        },
        {
          "plugin": "provider-usage",
          "file": "plugins/provider-usage/app.tsx",
          "note": "Registers a disclosure and keeps the returned controller to open and close it."
        }
      ],
      "rules": [
        "frontend-no-network",
        "shims-devdeps",
        "icons-hugeicons",
        "crash-boundary",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your item appears in the sidebar footer strip between Settings and the bug-report icon",
        "A disclosure opens above the footer row and closes when another plugin's disclosure opens",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-sidebarFooter",
      "mockupHash": "shell,surface=sidebar-footer"
    },
    {
      "kind": "slot",
      "id": "experimental_sidebarNavigation",
      "title": "app.slots.experimental_sidebarNavigation",
      "groupTitle": null,
      "number": null,
      "summary": "Replaces bb's navigation controls above the thread list with a component your plugin renders. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_sidebarNavigation({ id, title, description, component })",
      "propsType": "ExperimentalSidebarNavigationProps",
      "slotKind": "replacement",
      "stability": "experimental",
      "symbols": [
        {
          "name": "ExperimentalSidebarNavigationRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1192,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1192"
        },
        {
          "name": "ExperimentalSidebarNavigationProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 170,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L170"
        },
        {
          "name": "ExperimentalSidebarNavigationItem",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 152,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L152"
        },
        {
          "name": "ExperimentalSidebarNavigationAction",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 136,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L136"
        },
        {
          "name": "ExperimentalSidebarNavigationIcon",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 147,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L147"
        },
        {
          "name": "ExperimentalSidebarNavigationShortcut",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 130,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L130"
        },
        {
          "name": "ExperimentalSidebarNavigationActivationOptions",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 165,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L165"
        }
      ],
      "reference": [
        {
          "plugin": "sidebar-navigation-example",
          "file": "examples/plugins/sidebar-navigation/app.tsx",
          "note": "Replaces the whole navigation block with a compact grid and delegates to experimental_Original when it does not want to own an item."
        }
      ],
      "rules": [
        "frontend-no-network",
        "shims-devdeps",
        "replacement-original",
        "crash-boundary",
        "icons-hugeicons",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Settings → Appearance → Navigation lists your plugin and selecting it replaces the New thread / Search / Plugins / Skills block with your component",
        "Activating an item through experimental_activate opens the same destination bb would have opened, including split placement",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-experimental_sidebarNavigation",
      "mockupHash": "shell,surface=sidebar-navigation"
    },
    {
      "kind": "slot",
      "id": "experimental_threadList",
      "title": "app.slots.experimental_threadList",
      "groupTitle": null,
      "number": null,
      "summary": "Replaces the list of threads in bb's sidebar with a component your plugin renders. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_threadList({ id, title, description, component })",
      "propsType": "PluginThreadListProps",
      "slotKind": "exclusive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginThreadListRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1181,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1181"
        },
        {
          "name": "PluginSidebarThreadsState",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 991,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L991"
        }
      ],
      "reference": [
        {
          "plugin": "replacement-lab-alpha",
          "file": "examples/plugins/replacement-lab-alpha/app.tsx",
          "note": "Shows conditional delegation to Original, the deliberate crash path and how two plugins contend for the same area."
        }
      ],
      "rules": [
        "exclusive-slot",
        "replacement-original",
        "frontend-no-network",
        "thread-list-keyboard",
        "crash-boundary",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Settings → Appearance → Sidebar lets the user pin your list, and the sidebar scroll area renders it",
        "Rows carry data-sidebar-thread-shortcut-target and data-sidebar-thread-id so thread.next / thread.previous keep working",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-experimental_threadList",
      "mockupHash": "shell,surface=thread-list"
    },
    {
      "kind": "slot",
      "id": "experimental_threadHeaderAction",
      "title": "app.slots.experimental_threadHeaderAction",
      "groupTitle": null,
      "number": null,
      "summary": "Adds a control to the header bar at the top of an open thread. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_threadHeaderAction({ id, title, component })",
      "propsType": "PluginThreadHeaderActionProps",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginThreadHeaderActionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1103,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1103"
        }
      ],
      "reference": [],
      "rules": [
        "frontend-no-network",
        "shims-devdeps",
        "header-geometry",
        "split-instances",
        "crash-boundary",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your control renders at the left end of the thread header action row and stays inside the 48px chrome row",
        "With a split layout open, each visible pane renders its own instance and neither shares per-thread state",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-experimental_threadHeaderAction",
      "mockupHash": "shell,surface=thread-header"
    },
    {
      "kind": "slot",
      "id": "experimental_browserToolbarAction",
      "title": "app.slots.experimental_browserToolbarAction",
      "groupTitle": null,
      "number": null,
      "summary": "Adds a plugin control to the toolbar of each open Browser tab. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_browserToolbarAction({ id, title, component })",
      "propsType": "ExperimentalPluginBrowserToolbarActionProps",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "ExperimentalPluginBrowserToolbarActionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1115,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1115"
        },
        {
          "name": "ExperimentalPluginBrowserToolbarActionProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 269,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L269"
        },
        {
          "name": "ExperimentalPluginBrowserPage",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 251,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L251"
        },
        {
          "name": "ExperimentalPluginBrowserPageEvaluateOptions",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 238,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L238"
        },
        {
          "name": "ExperimentalPluginBrowserPageWorld",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 236,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L236"
        }
      ],
      "reference": [
        {
          "plugin": "agent-annotations",
          "file": "plugins/agent-annotations/app.tsx",
          "note": "Adds a control beside the address bar and drives the page through experimental_page.evaluate / onMessage."
        }
      ],
      "rules": [
        "frontend-no-network",
        "browser-page-null",
        "shims-devdeps",
        "crash-boundary",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "The control appears beside the address bar of every built-in Browser tab",
        "Outside the desktop app experimental_page is null and your component still renders without throwing",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-experimental_browserToolbarAction",
      "mockupHash": "shell,surface=browser-toolbar"
    },
    {
      "kind": "slot",
      "id": "fileOpener",
      "title": "app.slots.fileOpener",
      "groupTitle": null,
      "number": null,
      "summary": "Registers a viewer for the file types you name, so bb opens those files there instead of its built-in preview. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.fileOpener({ id, title, extensions, component })",
      "propsType": "PluginFileOpenerProps",
      "slotKind": "replacement",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginFileOpenerRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1212,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1212"
        },
        {
          "name": "PluginFileOpenerProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 303,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L303"
        }
      ],
      "reference": [
        {
          "plugin": "pdf-preview",
          "file": "plugins/pdf-preview/app.tsx",
          "note": "Minimal file viewer: one fileOpener registration and nothing else."
        },
        {
          "plugin": "monaco-editor",
          "file": "plugins/monaco-editor/app.tsx",
          "note": "Full editor, and the reference consumer of experimental_lineRange revealed by object identity."
        }
      ],
      "rules": [
        "replacement-original",
        "file-source-kinds",
        "frontend-no-network",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Opening a file with one of your extensions renders your viewer in the file tab",
        "Settings → File openers lets the user pin another opener and yours steps aside",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-fileOpener",
      "mockupHash": "shell,surface=file-opener"
    },
    {
      "kind": "slot",
      "id": "experimental_sourceCodeRenderer",
      "title": "app.slots.experimental_sourceCodeRenderer",
      "groupTitle": null,
      "number": null,
      "summary": "Replaces bb's source-code or diff renderer everywhere that kind of content appears. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_sourceCodeRenderer({ id, title, description, component })",
      "propsType": "PluginSourceCodeRendererProps",
      "slotKind": "exclusive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginSourceCodeRendererRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1232,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1232"
        },
        {
          "name": "PluginSourceCodeRendererProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 419,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L419"
        },
        {
          "name": "PluginDiffRendererRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1249,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1249"
        },
        {
          "name": "PluginDiffRendererProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 440,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L440"
        }
      ],
      "reference": [],
      "rules": [
        "exclusive-slot",
        "replacement-original",
        "diff-patch-complete",
        "frontend-no-network",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Every place bb draws supplied source text — including plugins that call `experimental_SourceCode` — renders through your component",
        "Delegating to `Original` for unsupported paths produces bb's own rendering",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-experimental_sourceCodeRenderer",
      "mockupHash": "shell,surface=code-renderers"
    },
    {
      "kind": "slot",
      "id": "experimental_diffRenderer",
      "title": "app.slots.experimental_diffRenderer",
      "groupTitle": null,
      "number": null,
      "summary": "Replaces bb's source-code or diff renderer everywhere that kind of content appears. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_diffRenderer({ id, title, description, component })",
      "propsType": "PluginDiffRendererProps",
      "slotKind": "exclusive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginSourceCodeRendererRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1232,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1232"
        },
        {
          "name": "PluginSourceCodeRendererProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 419,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L419"
        },
        {
          "name": "PluginDiffRendererRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1249,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1249"
        },
        {
          "name": "PluginDiffRendererProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 440,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L440"
        }
      ],
      "reference": [],
      "rules": [
        "exclusive-slot",
        "replacement-original",
        "diff-patch-complete",
        "frontend-no-network",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Timeline file diffs, the environment diff panel and every `experimental_Diff` caller render through your component",
        "You verify that `patch` and `experimental_fullFileContents` agree before treating the full contents as complete",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-experimental_diffRenderer",
      "mockupHash": "shell,surface=code-renderers"
    },
    {
      "kind": "slot",
      "id": "messageDirective",
      "title": "app.slots.messageDirective",
      "groupTitle": null,
      "number": null,
      "summary": "Renders your component inside an agent's reply, in place of a marker the agent writes into its message. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.messageDirective({ id, component })",
      "propsType": "PluginMessageDirectiveProps",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginMessageDirectiveRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1264,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1264"
        }
      ],
      "reference": [
        {
          "plugin": "inline-vis",
          "file": "plugins/inline-vis/app.tsx",
          "note": "Smallest directive plugin: renders workspace or thread-storage HTML and Markdown inline in assistant messages."
        },
        {
          "plugin": "workflows",
          "file": "plugins/workflows/src/app.tsx",
          "note": "Emits a ::workflow-preview directive from a tool result and renders it."
        }
      ],
      "rules": [
        "directive-attrs-untrusted",
        "frontend-no-network",
        "css-scope",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "An assistant message containing ::<your-id>{…} renders your component instead of the literal text",
        "Malformed attributes fall back to the original source text rather than throwing",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-messageDirective",
      "mockupHash": "shell,surface=message-directives"
    },
    {
      "kind": "slot",
      "id": "messageAction",
      "title": "app.slots.messageAction",
      "groupTitle": null,
      "number": null,
      "summary": "Adds an action to individual messages in a thread. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.messageAction({ id, title, icon, run })",
      "propsType": "PluginMessageActionContext",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginMessageActionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1327,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1327"
        }
      ],
      "reference": [
        {
          "plugin": "side-chat",
          "file": "plugins/side-chat/app.tsx",
          "note": "Opens a side-chat panel from the message action, passing message.sourceSeqEnd as the fork anchor."
        }
      ],
      "rules": [
        "frontend-no-network",
        "message-reference",
        "icons-hugeicons",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your entry appears last in the hover action row under user and assistant messages",
        "Invoking it from the assistant text-selection menu passes selectedText",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-messageAction",
      "mockupHash": "shell,surface=message-actions"
    },
    {
      "kind": "slot",
      "id": "commands",
      "title": "app.commands.register",
      "groupTitle": null,
      "number": null,
      "summary": "Registers a command with app.commands.register and adds a row under Plugins in bb's quick command palette. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.commands.register({ id, title, defaultShortcut?, isAvailable?, run })",
      "propsType": "PluginCommandContext",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginAppBuilder.commands",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1812,
          "kind": "property",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1812"
        },
        {
          "name": "PluginAppCommands",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1396,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1396"
        },
        {
          "name": "PluginCommandRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1375,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1375"
        },
        {
          "name": "PluginCommandContext",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1341,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1341"
        },
        {
          "name": "PluginCommandShortcut",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1359,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1359"
        }
      ],
      "reference": [
        {
          "plugin": "monaco-editor",
          "file": "plugins/monaco-editor/app.tsx",
          "note": "Registers one command per editor action with isAvailable gating."
        }
      ],
      "rules": [
        "command-shortcuts",
        "command-isavailable",
        "frontend-no-network",
        "engines-gate"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your command appears in the quick palette (Mod+Shift+P) under the Plugins bucket with your plugin's name",
        "A declared defaultShortcut fires, and the user can rebind it under plugin:<plugin-id>/<command-id>",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-commands",
      "mockupHash": "palette,surface=command-palette-actions"
    },
    {
      "kind": "slot",
      "id": "experimental_providerIcon",
      "title": "app.slots.experimental_providerIcon",
      "groupTitle": null,
      "number": null,
      "summary": "Adds an agent to bb's model picker and runs the threads started with it. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_providerIcon({ providerKind, providerId, icon })",
      "propsType": "{ className?: string }",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginProviderDeclaration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1439,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1439"
        },
        {
          "name": "PluginProviderIconRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1415,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1415"
        },
        {
          "name": "ExperimentalPluginProviderEnvContext",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1715,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1715"
        },
        {
          "name": "ExperimentalPluginProviderEnvEntry",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1721,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1721"
        },
        {
          "name": "ExperimentalPluginProviderEnvHealthContext",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1727,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1727"
        },
        {
          "name": "ExperimentalPluginProviderEnvHealth",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1731,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1731"
        }
      ],
      "reference": [
        {
          "plugin": "provider-acp",
          "file": "plugins/provider-acp/src/declaration.ts",
          "note": "Builds one declaration per ACP agent and ships the bridge from the same host artifact."
        },
        {
          "plugin": "echo-provider",
          "file": "examples/plugins/echo-provider/src/provider-bridge.ts",
          "note": "Smallest complete bridge: handshake, a session start and the minimal turn loop."
        }
      ],
      "rules": [
        "icons-hugeicons",
        "frontend-no-network",
        "crash-boundary",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your React mark replaces the masked logo everywhere that provider is drawn",
        "The component renders inline markup only and inherits color from `className`",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-experimental_providerIcon",
      "mockupHash": "composer,surface=provider-picker"
    },
    {
      "kind": "slot",
      "id": "experimental_timelineRenderer",
      "title": "app.slots.experimental_timelineRenderer",
      "groupTitle": null,
      "number": null,
      "summary": "Renders the expanded content of plugin-owned timeline entries while bb keeps each entry's header and controls. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_timelineRenderer({ kind, component })",
      "propsType": "PluginTimelineRendererProps",
      "slotKind": "replacement",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginTimelineRendererRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1504,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1504"
        },
        {
          "name": "PluginTimelineRendererProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1466,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1466"
        }
      ],
      "reference": [],
      "rules": [
        "timeline-kind-ownership",
        "replacement-original",
        "frontend-no-network",
        "crash-boundary",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Rows whose kind is \"<plugin-id>/<name>\" (or \"tool\" for your own provider) render your body while bb keeps the row header",
        "Removing the renderer falls back to the declarative base rendering instead of a blank row",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-experimental_timelineRenderer",
      "mockupHash": "shell,surface=timeline-renderers"
    },
    {
      "kind": "slot",
      "id": "experimental_environmentProviderInputs",
      "title": "app.slots.experimental_environmentProviderInputs",
      "groupTitle": null,
      "number": null,
      "summary": "Offers plugin-provisioned places a thread can run, picked like any environment. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_environmentProviderInputs({ environmentProviderId, component })",
      "propsType": "PluginEnvironmentProviderInputsProps",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginEnvironments",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 401,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L401"
        },
        {
          "name": "PluginEnvironmentProviderDeclaration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 390,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L390"
        },
        {
          "name": "PluginEnvironmentProviderRequirements",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 370,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L370"
        },
        {
          "name": "PluginEnvironmentValidateDecision",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 381,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L381"
        },
        {
          "name": "PluginEnvironmentProviderInputsRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1550,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1550"
        },
        {
          "name": "PluginEnvironmentProviderInputsProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1518,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1518"
        },
        {
          "name": "PluginEnvironmentProviderInputsChange",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1537,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1537"
        },
        {
          "name": "experimental_BranchPicker",
          "path": "packages/plugin-sdk/src/app.ts",
          "line": 62,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app.ts#L62"
        }
      ],
      "reference": [
        {
          "plugin": "environment-personal-workspace",
          "file": "plugins/environment-personal-workspace/server.ts",
          "note": "Tiniest provider: requires projectless, one host call to create and one to remove."
        },
        {
          "plugin": "environment-git-worktree",
          "file": "plugins/environment-git-worktree/server.ts",
          "note": "Per-attempt path keys, existing-path adoption and experimental_claimPath."
        }
      ],
      "rules": [
        "frontend-no-network",
        "env-inputs-public",
        "crash-boundary",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Selecting your environment provider in New Thread renders your control beside it",
        "`onChange({ status: \"blocked\", reason })` prevents submission until the inputs are valid",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-experimental_environmentProviderInputs",
      "mockupHash": "headless,surface=environment-providers"
    },
    {
      "kind": "slot",
      "id": "experimental_machineProviderInputs",
      "title": "app.slots.experimental_machineProviderInputs",
      "groupTitle": null,
      "number": null,
      "summary": "Adds plugin-provisioned machines that compose with environment providers. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.slots.experimental_machineProviderInputs({ machineProviderId, component })",
      "propsType": "PluginMachineProviderInputsProps",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginMachines",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 485,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L485"
        }
      ],
      "reference": [
        {
          "plugin": "environment-modal-sandbox",
          "file": "plugins/environment-modal-sandbox/providers/register.ts",
          "note": "The only first-party machine provider: create → checkpoint → bootstrap, plus suspend / resume / reconcileCleanup."
        }
      ],
      "rules": [
        "frontend-no-network",
        "machine-resource-public",
        "crash-boundary",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Creating a machine with your provider renders your compact inputs control",
        "Only non-secret configuration and credential references are stored — machine inputs are readable by every plugin",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-experimental_machineProviderInputs",
      "mockupHash": "headless,surface=machine-providers"
    },
    {
      "kind": "slot",
      "id": "composer-customize",
      "title": "app.composer.customize",
      "groupTitle": null,
      "number": null,
      "summary": "Adds rows to the menu that opens from the + button beside the prompt box. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.composer.customize({ id, scopes?, actions?, banners?, plusMenu?, richText? })",
      "propsType": "ComposerCustomization",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "ComposerPlusMenuItem",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1906,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1906"
        },
        {
          "name": "ExperimentalComposerSubmitOptions",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 2158,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L2158"
        }
      ],
      "reference": [
        {
          "plugin": "drafts",
          "file": "plugins/drafts/app.tsx",
          "note": "Whole plugin: a plus-menu row that submits with experimental_data and a dispatch hook that keeps it queued."
        },
        {
          "plugin": "scheduled-send",
          "file": "plugins/scheduled-send/app.tsx",
          "note": "Adds a send-menu row that schedules the draft with sendAt."
        }
      ],
      "rules": [
        "composer-scopes",
        "composer-submit-pipeline",
        "composer-inline-cap",
        "frontend-no-network",
        "crash-boundary"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your actions, banners, plus-menu rows and rich-text effects appear only in the scopes you declared",
        "Submitting through `composer.experimental_submit` carries attachments, mentions and picker selections",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-composer-customize",
      "mockupHash": "composer,surface=composer-plus-menu"
    },
    {
      "kind": "slot",
      "id": "contentScripts",
      "title": "app.contentScripts.register",
      "groupTitle": null,
      "number": null,
      "summary": "Runs your code inside the bb window itself, without rendering a UI of its own. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.contentScripts.register({ id, mount(context) })",
      "propsType": "PluginContentScriptContext",
      "slotKind": "additive",
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginContentScriptRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1731,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1731"
        },
        {
          "name": "PluginContentScriptContext",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1700,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1700"
        }
      ],
      "reference": [
        {
          "plugin": "content-script-example",
          "file": "examples/plugins/content-script/app.ts",
          "note": "Cleanup-complete reference: adds a focus ring, releases it on the abort signal and returns a disposer."
        }
      ],
      "rules": [
        "content-script-trust",
        "mount-timeout",
        "frontend-no-network",
        "dispose-lifo"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "The script mounts once per window, tab and remote client, and its disposer plus the abort signal both release everything",
        "A reload of the plugin disposes the previous generation before the new one mounts — no duplicated listeners",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-contentScripts",
      "mockupHash": "shell,surface=content-scripts"
    },
    {
      "kind": "slot",
      "id": "experimental_icons",
      "title": "app.experimental_icons.register",
      "groupTitle": null,
      "number": null,
      "summary": "Renders bb's chat, prompt box, pickers, file content, links, and shared app icons inside plugin pages. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "app",
      "manifestEntry": "bb.app",
      "registration": "app.experimental_icons.register({ name, component })",
      "propsType": "{ className?: string }",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "experimental_Icon",
          "path": "packages/plugin-sdk/src/app.ts",
          "line": 49,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app.ts#L49"
        },
        {
          "name": "experimental_ProviderIcon",
          "path": "packages/plugin-sdk/src/app.ts",
          "line": 50,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app.ts#L50"
        },
        {
          "name": "ExperimentalProviderIconProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1765,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1765"
        },
        {
          "name": "ExperimentalIconProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1754,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1754"
        },
        {
          "name": "ExperimentalIconRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1792,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1792"
        },
        {
          "name": "ExperimentalAppIcons",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1799,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1799"
        },
        {
          "name": "PluginAppBuilder.experimental_icons",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1811,
          "kind": "property",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1811"
        },
        {
          "name": "experimental_NewThreadComposer",
          "path": "packages/plugin-sdk/src/app.ts",
          "line": 56,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app.ts#L56"
        }
      ],
      "reference": [
        {
          "plugin": "side-chat",
          "file": "plugins/side-chat/app.tsx",
          "note": "Embeds ThreadChat in compact mode with its own messageActions."
        },
        {
          "plugin": "thread-chat-demo",
          "file": "examples/plugins/thread-chat-demo/app.tsx",
          "note": "Demonstrates ThreadChat plus targeted fixed tabs."
        }
      ],
      "rules": [
        "icons-hugeicons",
        "frontend-no-network",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your name resolves anywhere bb accepts an icon name, shadowing a built-in of the same name",
        "A duplicate name inside your own plugin rejects setup instead of silently winning",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "slot-experimental_icons",
      "mockupHash": "headless,surface=host-components"
    },
    {
      "kind": "namespace",
      "id": "pluginId",
      "title": "bb.pluginId",
      "groupTitle": null,
      "number": null,
      "summary": "",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.pluginId",
      "propsType": null,
      "slotKind": null,
      "stability": "stable",
      "symbols": [],
      "reference": [],
      "rules": [
        "backend-full-trust"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Self-filtering uses `bb.pluginId` instead of a hardcoded string, so renaming the package cannot silently break it",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-pluginId",
      "mockupHash": null
    },
    {
      "kind": "namespace",
      "id": "log",
      "title": "bb.log",
      "groupTitle": null,
      "number": null,
      "summary": "",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.log.{debug,info,warn,error}(message)",
      "propsType": null,
      "slotKind": null,
      "stability": "stable",
      "symbols": [],
      "reference": [],
      "rules": [
        "backend-full-trust"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "`bb plugin logs <id> -f` shows your lines, prefixed `[plugin:<id>]`",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-log",
      "mockupHash": null
    },
    {
      "kind": "namespace",
      "id": "settings",
      "title": "bb.settings",
      "groupTitle": null,
      "number": null,
      "summary": "Declares the settings your plugin needs as plain data; bb renders the form for them on the plugin's settings page and stores the values. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.settings.define(descriptors) → { get, experimental_set, onChange }",
      "propsType": null,
      "slotKind": null,
      "stability": "mixed",
      "symbols": [
        {
          "name": "PluginSettings",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 162,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L162"
        },
        {
          "name": "PluginSettingsHandle",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 142,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L142"
        },
        {
          "name": "PluginSettingDescriptor",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 72,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L72"
        },
        {
          "name": "PluginSettingsState",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1852,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1852"
        }
      ],
      "reference": [
        {
          "plugin": "custom-instructions",
          "file": "plugins/custom-instructions/server.ts",
          "note": "Defines one setting, migrates a legacy kv value into it with experimental_set and tracks changes with onChange."
        },
        {
          "plugin": "github",
          "file": "plugins/github/server.ts",
          "note": "Validates a string setting with a zod superRefine through experimental_schema."
        }
      ],
      "rules": [
        "settings-load-once",
        "settings-secrets",
        "backend-full-trust",
        "dispose-lifo"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "bb renders your fields on the plugin page and `bb plugin config <id> set <key> <value>` writes them",
        "A `secret: true` value never reaches the frontend",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-settings",
      "mockupHash": "settings,surface=declarative-settings"
    },
    {
      "kind": "namespace",
      "id": "storage",
      "title": "bb.storage",
      "groupTitle": null,
      "number": null,
      "summary": "Stores the plugin's data on the bb server. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.storage.kv.* · bb.storage.database() · bb.storage.migrate(db, statements)",
      "propsType": null,
      "slotKind": null,
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginStorage",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 179,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L179"
        }
      ],
      "reference": [
        {
          "plugin": "memory",
          "file": "plugins/memory/server.ts",
          "note": "Owns a SQLite schema through migrate() and queries it directly."
        },
        {
          "plugin": "side-chat",
          "file": "plugins/side-chat/server.ts",
          "note": "Uses namespaced kv with a prefix sweep instead of a database."
        }
      ],
      "rules": [
        "kv-value-cap",
        "migrations-append-only",
        "storage-not-a-boundary",
        "dispose-lifo"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Data survives reload and restart",
        "A second migration appends a statement instead of editing an applied one",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-storage",
      "mockupHash": "headless,surface=storage"
    },
    {
      "kind": "namespace",
      "id": "http",
      "title": "bb.http",
      "groupTitle": null,
      "number": null,
      "summary": "Connects the plugin's own UI, its server code, and outside services. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.http.route(method, path, handler, { auth }) · bb.http.experimental_websocket(path, handler, { auth })",
      "propsType": null,
      "slotKind": null,
      "stability": "mixed",
      "symbols": [
        {
          "name": "PluginRpc",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 848,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L848"
        },
        {
          "name": "PluginRpcMethodContract",
          "path": "packages/plugin-sdk/src/rpc-contract.ts",
          "line": 68,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/rpc-contract.ts#L68"
        },
        {
          "name": "PluginsArea.experimental_discoverRpc",
          "path": "packages/sdk/src/areas/plugins.ts",
          "line": 226,
          "kind": "method",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/sdk/src/areas/plugins.ts#L226"
        },
        {
          "name": "PluginHttp",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 819,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L819"
        },
        {
          "name": "PluginRealtime",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 867,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L867"
        }
      ],
      "reference": [
        {
          "plugin": "tasks",
          "file": "plugins/tasks/attachments/index.ts",
          "note": "Token-authenticated upload route plus realtime publishes after every write."
        },
        {
          "plugin": "slack-bot",
          "file": "examples/plugins/slack-bot/server.ts",
          "note": "auth: \"none\" webhook that verifies the Slack signature inside the handler."
        }
      ],
      "rules": [
        "http-exact-match",
        "backend-full-trust",
        "secrets-agent-reach"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your route answers at `/api/v1/plugins/<id>/http<path>` with the auth mode you declared",
        "A `token` route rejects a wrong or missing `x-bb-plugin-token`",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-http",
      "mockupHash": "headless,surface=wire"
    },
    {
      "kind": "namespace",
      "id": "rpc",
      "title": "bb.rpc",
      "groupTitle": null,
      "number": null,
      "summary": "Connects the plugin's own UI, its server code, and outside services. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.rpc.register(contract, handlers, { experimental_discoverable?, experimental_description? })",
      "propsType": null,
      "slotKind": null,
      "stability": "mixed",
      "symbols": [
        {
          "name": "PluginRpc",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 848,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L848"
        },
        {
          "name": "PluginRpcMethodContract",
          "path": "packages/plugin-sdk/src/rpc-contract.ts",
          "line": 68,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/rpc-contract.ts#L68"
        },
        {
          "name": "PluginsArea.experimental_discoverRpc",
          "path": "packages/sdk/src/areas/plugins.ts",
          "line": 226,
          "kind": "method",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/sdk/src/areas/plugins.ts#L226"
        },
        {
          "name": "PluginHttp",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 819,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L819"
        },
        {
          "name": "PluginRealtime",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 867,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L867"
        }
      ],
      "reference": [
        {
          "plugin": "tasks",
          "file": "plugins/tasks/attachments/index.ts",
          "note": "Token-authenticated upload route plus realtime publishes after every write."
        },
        {
          "plugin": "slack-bot",
          "file": "examples/plugins/slack-bot/server.ts",
          "note": "auth: \"none\" webhook that verifies the Slack signature inside the handler."
        }
      ],
      "rules": [
        "frontend-no-network",
        "rpc-strict-json",
        "backend-full-trust"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "`useRpc<typeof rpcContract>().call(...)` from `app.tsx` reaches your handler with validated input",
        "Invalid input returns `{ ok: false, error: { code: \"invalid_input\", issues } }` instead of throwing",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-rpc",
      "mockupHash": "headless,surface=wire"
    },
    {
      "kind": "namespace",
      "id": "realtime",
      "title": "bb.realtime",
      "groupTitle": null,
      "number": null,
      "summary": "Connects the plugin's own UI, its server code, and outside services. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.realtime.publish(channel, payload)",
      "propsType": null,
      "slotKind": null,
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginRpc",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 848,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L848"
        },
        {
          "name": "PluginRpcMethodContract",
          "path": "packages/plugin-sdk/src/rpc-contract.ts",
          "line": 68,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/rpc-contract.ts#L68"
        },
        {
          "name": "PluginsArea.experimental_discoverRpc",
          "path": "packages/sdk/src/areas/plugins.ts",
          "line": 226,
          "kind": "method",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/sdk/src/areas/plugins.ts#L226"
        },
        {
          "name": "PluginHttp",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 819,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L819"
        },
        {
          "name": "PluginRealtime",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 867,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L867"
        }
      ],
      "reference": [
        {
          "plugin": "tasks",
          "file": "plugins/tasks/attachments/index.ts",
          "note": "Token-authenticated upload route plus realtime publishes after every write."
        },
        {
          "plugin": "slack-bot",
          "file": "examples/plugins/slack-bot/server.ts",
          "note": "auth: \"none\" webhook that verifies the Slack signature inside the handler."
        }
      ],
      "rules": [
        "realtime-broadcast",
        "frontend-no-network"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Every open window refetches after a write, without polling",
        "Nothing secret rides in the payload — the signal reaches every connected client",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-realtime",
      "mockupHash": "headless,surface=wire"
    },
    {
      "kind": "namespace",
      "id": "background",
      "title": "bb.background",
      "groupTitle": null,
      "number": null,
      "summary": "Runs code on the bb server when no window is open. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.background.service(name, { start(signal) }) · bb.background.schedule(name, cron, fn)",
      "propsType": null,
      "slotKind": null,
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginBackground",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 881,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L881"
        }
      ],
      "reference": [
        {
          "plugin": "automations",
          "file": "plugins/automations/src/server.ts",
          "note": "Startup reconciliation then a loop that sleeps on the abort signal."
        },
        {
          "plugin": "environment-modal-sandbox",
          "file": "plugins/environment-modal-sandbox/server.ts",
          "note": "A one-minute schedule that suspends idle machines."
        }
      ],
      "rules": [
        "service-abort-sleep",
        "schedule-only-while-loaded",
        "needs-configuration-error",
        "dispose-lifo"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "`bb plugin list` shows the service running and the schedule's last status",
        "Reload stops the service inside the 5 s window",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-background",
      "mockupHash": "headless,surface=background"
    },
    {
      "kind": "namespace",
      "id": "cli",
      "title": "bb.cli",
      "groupTitle": null,
      "number": null,
      "summary": "Registers a top-level `bb <name>` command, available in the terminal and to agents. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.cli.register(defineCli({ name, summary, commands }))",
      "propsType": null,
      "slotKind": null,
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginCli",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1033,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1033"
        },
        {
          "name": "PluginCliResult",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 980,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L980"
        },
        {
          "name": "defineCli",
          "path": "packages/plugin-sdk/src/cli-spec.ts",
          "line": 1174,
          "kind": "function",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/cli-spec.ts#L1174"
        },
        {
          "name": "cliCommand",
          "path": "packages/plugin-sdk/src/cli-spec.ts",
          "line": 252,
          "kind": "function",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/cli-spec.ts#L252"
        },
        {
          "name": "PluginCliError",
          "path": "packages/plugin-sdk/src/cli-spec.ts",
          "line": 230,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/cli-spec.ts#L230"
        },
        {
          "name": "PluginCliSpec",
          "path": "packages/plugin-sdk/src/cli-spec.ts",
          "line": 211,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/cli-spec.ts#L211"
        }
      ],
      "reference": [
        {
          "plugin": "concurrency-limit",
          "file": "plugins/concurrency-limit/server.ts",
          "note": "defineCli + cliCommand with typed options and PluginCliError with a hint."
        },
        {
          "plugin": "tasks",
          "file": "plugins/tasks/cli/index.ts",
          "note": "Grouped command paths such as \"project create\"."
        }
      ],
      "rules": [
        "cli-runs-on-server",
        "cli-output-cap",
        "cli-argv"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "`bb <name> --help` renders from metadata without executing plugin code",
        "`bb <name> <command> --json` prints a machine-readable envelope",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-cli",
      "mockupHash": "headless,surface=cli"
    },
    {
      "kind": "namespace",
      "id": "agents",
      "title": "bb.agents",
      "groupTitle": null,
      "number": null,
      "summary": "Adds tools, skills, and instructions to the agent sessions bb runs. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.agents.registerTool(...) · bb.agents.configure(...) · bb.agents.contributeInstructions(...)",
      "propsType": null,
      "slotKind": null,
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginAgents",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1601,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1601"
        }
      ],
      "reference": [
        {
          "plugin": "workflows",
          "file": "plugins/workflows/src/server.ts",
          "note": "Registers two tools with presentation labels and narrows the parameter schema per resolution in configure()."
        },
        {
          "plugin": "ask-user-question",
          "file": "plugins/ask-user-question/src/server.ts",
          "note": "Drops its tool when the provider answers questions natively."
        }
      ],
      "rules": [
        "tools-next-session",
        "tool-name-global",
        "instructions-cap",
        "tool-output-bounded",
        "secrets-agent-reach"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "A fresh thread lists your tool and executes it with validated parameters",
        "Instructions stay under 4096 characters and land in the documented order",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-agents",
      "mockupHash": "headless,surface=agent-tools"
    },
    {
      "kind": "namespace",
      "id": "providers",
      "title": "bb.providers",
      "groupTitle": null,
      "number": null,
      "summary": "Adds an agent to bb's model picker and runs the threads started with it. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.providers.register(declaration) → { dispose } · experimental_contributeEnv(...) · experimental_contributeEnvHealth(...)",
      "propsType": null,
      "slotKind": null,
      "stability": "mixed",
      "symbols": [
        {
          "name": "contextSnapshotSchema",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 21,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L21"
        },
        {
          "name": "ContextSnapshot",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 32,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L32"
        },
        {
          "name": "ContextCategory",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 19,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L19"
        },
        {
          "name": "ContextEntry",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 10,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L10"
        },
        {
          "name": "PluginProviderDeclaration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1439,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1439"
        },
        {
          "name": "PluginProviderIconRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1415,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1415"
        }
      ],
      "reference": [
        {
          "plugin": "provider-acp",
          "file": "plugins/provider-acp/src/declaration.ts",
          "note": "Builds one declaration per ACP agent and ships the bridge from the same host artifact."
        },
        {
          "plugin": "echo-provider",
          "file": "examples/plugins/echo-provider/src/provider-bridge.ts",
          "note": "Smallest complete bridge: handshake, a session start and the minimal turn loop."
        }
      ],
      "rules": [
        "provider-needs-host",
        "provider-id-immutable",
        "provider-derive-sync",
        "bridge-grammar-v3"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your provider appears in the composer picker and a thread runs on it end to end",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-providers",
      "mockupHash": "composer,surface=provider-picker"
    },
    {
      "kind": "namespace",
      "id": "ui",
      "title": "bb.ui",
      "groupTitle": null,
      "number": null,
      "summary": "Asks the person a question in the thread composer and delivers their answer to the agent, even if the original turn has ended. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.ui.requestInput(request, { signal }) · bb.ui.registerMentionProvider(provider)",
      "propsType": null,
      "slotKind": null,
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginUi",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1808,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1808"
        },
        {
          "name": "PluginPendingInteractionRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 745,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L745"
        }
      ],
      "reference": [
        {
          "plugin": "ask-user-question",
          "file": "plugins/ask-user-question/src/server.ts",
          "note": "Requests a multiple-choice answer from the backend and renders it with the paired slot."
        },
        {
          "plugin": "secrets",
          "file": "plugins/secrets/src/server.ts",
          "note": "Asks for credentials from a CLI command and reconciles them into a dotenv file."
        }
      ],
      "rules": [
        "interaction-detached",
        "interaction-limits",
        "mention-search-timebox",
        "mention-resolve-blocks",
        "secrets-agent-reach"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "A pending interaction replaces the composer and resolves your promise on submit or cancel",
        "Your mention section appears under its trigger and `resolve()` attaches context at send time",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-ui",
      "mockupHash": "shell,surface=pending-interaction"
    },
    {
      "kind": "namespace",
      "id": "events",
      "title": "bb.events",
      "groupTitle": null,
      "number": null,
      "summary": "Runs server code when a thread changes state. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.events.on(event, handler) — 14 events",
      "propsType": null,
      "slotKind": null,
      "stability": "mixed",
      "symbols": [
        {
          "name": "PluginEvents",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1832,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1832"
        },
        {
          "name": "PluginThreadEventPayloads",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 267,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L267"
        },
        {
          "name": "PluginTurnFailedEvent",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 214,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L214"
        }
      ],
      "reference": [
        {
          "plugin": "provider-retry",
          "file": "plugins/provider-retry/server.ts",
          "note": "Retries by reference on turn.failed with bb.sdk.threads.retry."
        },
        {
          "plugin": "tasks",
          "file": "plugins/tasks/lifecycle/index.ts",
          "note": "Tracks thread lifecycle transitions to update its own rows."
        }
      ],
      "rules": [
        "events-are-announcements",
        "events-see-everything",
        "message-cancelled-only-signal"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your handler reacts to real transitions and never tries to block them",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-events",
      "mockupHash": "headless,surface=thread-events"
    },
    {
      "kind": "namespace",
      "id": "experimental_hooks",
      "title": "bb.experimental_hooks",
      "groupTitle": null,
      "number": null,
      "summary": "Answers the admission checkpoint for ordinary sends, eligible queued messages, and retries before they reach a provider. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.experimental_hooks.on(\"message.dispatch\", handler) · recheck(hook)",
      "propsType": null,
      "slotKind": null,
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginHooks",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 714,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L714"
        },
        {
          "name": "PluginHookSignatures",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 699,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L699"
        },
        {
          "name": "MessageDispatchHookContext",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 605,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L605"
        },
        {
          "name": "PluginDispatchEnvironmentIntent",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 502,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L502"
        },
        {
          "name": "MessageDispatchHookDecision",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 537,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L537"
        }
      ],
      "reference": [
        {
          "plugin": "drafts",
          "file": "plugins/drafts/server.ts",
          "note": "Shortest complete hook: waits forever on its own submissions until the user sends."
        },
        {
          "plugin": "concurrency-limit",
          "file": "plugins/concurrency-limit/server.ts",
          "note": "Counts running threads, waits with a reason and rechecks when capacity frees up."
        }
      ],
      "rules": [
        "hook-fail-closed",
        "hook-idempotent",
        "hook-no-amendment",
        "send-now-bypass",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "A held message shows your reason on the queued card and clears when you `recheck`",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-experimental_hooks",
      "mockupHash": "headless,surface=dispatch-hook"
    },
    {
      "kind": "namespace",
      "id": "experimental_environments",
      "title": "bb.experimental_environments",
      "groupTitle": null,
      "number": null,
      "summary": "Offers plugin-provisioned places a thread can run, picked like any environment. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.experimental_environments.register(declaration | composition) · recheck()",
      "propsType": null,
      "slotKind": null,
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginEnvironments",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 401,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L401"
        },
        {
          "name": "PluginEnvironmentProviderDeclaration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 390,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L390"
        },
        {
          "name": "PluginEnvironmentProviderRequirements",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 370,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L370"
        },
        {
          "name": "PluginEnvironmentValidateDecision",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 381,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L381"
        },
        {
          "name": "PluginEnvironmentProviderInputsRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1550,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1550"
        },
        {
          "name": "PluginEnvironmentProviderInputsProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1518,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1518"
        }
      ],
      "reference": [
        {
          "plugin": "environment-personal-workspace",
          "file": "plugins/environment-personal-workspace/server.ts",
          "note": "Tiniest provider: requires projectless, one host call to create and one to remove."
        },
        {
          "plugin": "environment-git-worktree",
          "file": "plugins/environment-git-worktree/server.ts",
          "note": "Per-attempt path keys, existing-path adoption and experimental_claimPath."
        }
      ],
      "rules": [
        "env-create-idempotent",
        "env-failure-terminal",
        "env-inputs-public",
        "env-hooks-owned-by-core",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your provider appears in the New Thread environment picker and provisions a workspace",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-experimental_environments",
      "mockupHash": "headless,surface=environment-providers"
    },
    {
      "kind": "namespace",
      "id": "experimental_machines",
      "title": "bb.experimental_machines",
      "groupTitle": null,
      "number": null,
      "summary": "Adds plugin-provisioned machines that compose with environment providers. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.experimental_machines.register(declaration) · bootstrap(request) · getResource(hostId)",
      "propsType": null,
      "slotKind": null,
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginMachines",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 485,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L485"
        }
      ],
      "reference": [
        {
          "plugin": "environment-modal-sandbox",
          "file": "plugins/environment-modal-sandbox/providers/register.ts",
          "note": "The only first-party machine provider: create → checkpoint → bootstrap, plus suspend / resume / reconcileCleanup."
        }
      ],
      "rules": [
        "machine-checkpoint",
        "machine-needs-composition",
        "machine-resource-public",
        "machine-idle-is-yours",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "A created machine enrolls a daemon that connects back to the server",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-experimental_machines",
      "mockupHash": "headless,surface=machine-providers"
    },
    {
      "kind": "namespace",
      "id": "experimental_serverAccess",
      "title": "bb.experimental_serverAccess",
      "groupTitle": null,
      "number": null,
      "summary": "Registers server access for enrolment and ongoing machine runtime requests. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.experimental_serverAccess.register(declaration) · recheck()",
      "propsType": null,
      "slotKind": null,
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginServerAccess",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 475,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L475"
        },
        {
          "name": "ServerAccessProviderDeclaration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 449,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L449"
        },
        {
          "name": "ServerAccessGrant",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 443,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L443"
        }
      ],
      "reference": [
        {
          "plugin": "connect",
          "file": "plugins/connect/src/server-access.ts",
          "note": "The sole implementation: persists the intent before redeeming a code so release works before enrolment."
        }
      ],
      "rules": [
        "access-acquire-idempotent",
        "access-release-null-grant",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "A new machine reaches the server through your grant",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-experimental_serverAccess",
      "mockupHash": "headless,surface=server-access"
    },
    {
      "kind": "namespace",
      "id": "status",
      "title": "bb.status",
      "groupTitle": null,
      "number": null,
      "summary": "Reports that the plugin cannot run until someone configures it, so bb can say so instead of the plugin failing silently. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.status.needsConfiguration(message)",
      "propsType": null,
      "slotKind": null,
      "stability": "stable",
      "symbols": [
        {
          "name": "PluginStatusApi",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1961,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1961"
        }
      ],
      "reference": [
        {
          "plugin": "github",
          "file": "plugins/github/server.ts",
          "note": "Reports needs-configuration when gh auth is missing instead of failing the load."
        },
        {
          "plugin": "workflows",
          "file": "plugins/workflows/src/server.ts",
          "note": "Reports it from the factory so an unconfigured plugin does not crash-loop."
        }
      ],
      "rules": [
        "status-cleared-on-load",
        "needs-configuration-error"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "An unconfigured plugin shows your message instead of failing to load",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-status",
      "mockupHash": "plugins,surface=plugin-status"
    },
    {
      "kind": "namespace",
      "id": "server",
      "title": "bb.server",
      "groupTitle": null,
      "number": null,
      "summary": "Calls bb's own API from the plugin's server code. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.server.loopbackBaseUrl · experimental_appUrl · experimental_dataDir",
      "propsType": null,
      "slotKind": null,
      "stability": "mixed",
      "symbols": [
        {
          "name": "BbPluginApi",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 2002,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L2002"
        },
        {
          "name": "PluginServerApi",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1847,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1847"
        }
      ],
      "reference": [
        {
          "plugin": "side-chat",
          "file": "plugins/side-chat/server.ts",
          "note": "Forks hidden threads and lists them back by originPluginId."
        },
        {
          "plugin": "concurrency-limit",
          "file": "plugins/concurrency-limit/server.ts",
          "note": "Reads threads.listRunning() and subscribes to host changes."
        }
      ],
      "rules": [
        "sdk-bind-gated",
        "backend-full-trust"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "`loopbackBaseUrl` is read from a handler or service, never from the factory in a harness",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-server",
      "mockupHash": "headless,surface=bb-sdk"
    },
    {
      "kind": "namespace",
      "id": "hosts",
      "title": "bb.hosts",
      "groupTitle": null,
      "number": null,
      "summary": "Runs the plugin's code on an enrolled machine, not only on the bb server. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.hosts.experimental_client({ contract, experimental_signals }) · ensureSharedPortTunnel · declareSharedPorts",
      "propsType": null,
      "slotKind": null,
      "stability": "mixed",
      "symbols": [
        {
          "name": "PluginHosts",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1926,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1926"
        },
        {
          "name": "experimental_killProcessesWithCwdUnder",
          "path": "packages/plugin-sdk/src/host.ts",
          "line": 44,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/host.ts#L44"
        },
        {
          "name": "experimental_sanitizeInheritedChildProcessEnv",
          "path": "packages/plugin-sdk/src/host.ts",
          "line": 52,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/host.ts#L52"
        },
        {
          "name": "ExperimentalSanitizeInheritedChildProcessEnvArgs",
          "path": "packages/plugin-sdk/src/host.ts",
          "line": 55,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/host.ts#L55"
        },
        {
          "name": "experimental_spawnPortableOutputProcess",
          "path": "packages/plugin-sdk/src/host.ts",
          "line": 53,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/host.ts#L53"
        }
      ],
      "reference": [
        {
          "plugin": "keep-awake",
          "file": "plugins/keep-awake/server.ts",
          "note": "Calls the host worker to hold a wake lock and reacts to experimental_onWorkerExit."
        },
        {
          "plugin": "environment-git-worktree",
          "file": "plugins/environment-git-worktree/host.ts",
          "note": "Streams progress back with experimental_emitSignal correlated by an operationId."
        }
      ],
      "rules": [
        "host-call-not-in-factory",
        "host-limits",
        "host-worker-exit",
        "host-no-private-imports",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "A call from a handler reaches your `bb.host` worker and returns validated output",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-hosts",
      "mockupHash": "headless,surface=host-workers"
    },
    {
      "kind": "namespace",
      "id": "experimental_aiServices",
      "title": "bb.experimental_aiServices",
      "groupTitle": null,
      "number": null,
      "summary": "Lets a plugin answer bb's own helper-model calls — the short model calls behind thread titles and commit messages, and the microphone button's transcription. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.experimental_aiServices.register({ id, displayName, kinds }) → { dispose }",
      "propsType": null,
      "slotKind": null,
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginAiServices",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1903,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1903"
        },
        {
          "name": "PluginAiServiceDeclaration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1894,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1894"
        }
      ],
      "reference": [
        {
          "plugin": "provider-codex",
          "file": "plugins/provider-codex/server.ts",
          "note": "The only first-party AI service: registers inference and voice from the codex host entry."
        }
      ],
      "rules": [
        "ai-needs-host",
        "ai-failures-returned",
        "ai-reserved-ids",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "`BB_INFERENCE=<id>/<model>` routes bb's helper inference through your host entry",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-experimental_aiServices",
      "mockupHash": "headless,surface=ai-services"
    },
    {
      "kind": "namespace",
      "id": "sdk",
      "title": "bb.sdk",
      "groupTitle": null,
      "number": null,
      "summary": "Calls bb's own API from the plugin's server code. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.sdk.<area>.<method>(...) — threads, projects, environments, hosts, files, terminals, skills, plugins, theme, system",
      "propsType": null,
      "slotKind": null,
      "stability": "mixed",
      "symbols": [
        {
          "name": "BbPluginApi",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 2002,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L2002"
        },
        {
          "name": "PluginServerApi",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1847,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1847"
        }
      ],
      "reference": [
        {
          "plugin": "side-chat",
          "file": "plugins/side-chat/server.ts",
          "note": "Forks hidden threads and lists them back by originPluginId."
        },
        {
          "plugin": "concurrency-limit",
          "file": "plugins/concurrency-limit/server.ts",
          "note": "Reads threads.listRunning() and subscribes to host changes."
        }
      ],
      "rules": [
        "sdk-bind-gated",
        "sdk-hidden-threads",
        "sdk-attribution",
        "backend-full-trust"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Threads you spawn are attributed to your plugin and cleaned up in a `finally`",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-sdk",
      "mockupHash": "headless,surface=bb-sdk"
    },
    {
      "kind": "namespace",
      "id": "onDispose",
      "title": "bb.onDispose",
      "groupTitle": null,
      "number": null,
      "summary": "",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.onDispose(hook) — LIFO",
      "propsType": null,
      "slotKind": null,
      "stability": "stable",
      "symbols": [],
      "reference": [],
      "rules": [
        "dispose-lifo",
        "backend-full-trust"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Reload leaves no timers, sockets or watchers behind; `bb plugin list` never reports `degraded`",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "api-onDispose",
      "mockupHash": null
    },
    {
      "kind": "advanced",
      "id": "providers",
      "title": "Agent provider + provider bridge",
      "groupTitle": null,
      "number": null,
      "summary": "Adds an agent to bb's model picker and runs the threads started with it. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.providers.register(declaration) + experimental_defineProviderBridge in the bb.host artifact",
      "propsType": "PluginProviderDeclaration",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "contextSnapshotSchema",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 21,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L21"
        },
        {
          "name": "ContextSnapshot",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 32,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L32"
        },
        {
          "name": "ContextCategory",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 19,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L19"
        },
        {
          "name": "ContextEntry",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 10,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L10"
        },
        {
          "name": "PluginProviderDeclaration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1439,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1439"
        },
        {
          "name": "PluginProviderIconRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1415,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1415"
        }
      ],
      "reference": [
        {
          "plugin": "provider-acp",
          "file": "plugins/provider-acp/src/declaration.ts",
          "note": "Builds one declaration per ACP agent and ships the bridge from the same host artifact."
        },
        {
          "plugin": "echo-provider",
          "file": "examples/plugins/echo-provider/src/provider-bridge.ts",
          "note": "Smallest complete bridge: handshake, a session start and the minimal turn loop."
        }
      ],
      "rules": [
        "provider-needs-host",
        "provider-id-immutable",
        "bridge-grammar-v3",
        "provider-derive-sync",
        "host-no-private-imports"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "A thread runs end to end on your provider with a correct turn loop",
        "`experimental_runBridgeConformance` passes against your bridge",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "adv-providers",
      "mockupHash": "composer,surface=provider-picker"
    },
    {
      "kind": "advanced",
      "id": "acp",
      "title": "ACP agents and dialects",
      "groupTitle": null,
      "number": null,
      "summary": "Adds an agent to bb's model picker and runs the threads started with it. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.providers.register(acpProviderDeclaration(agent)) with experimental_acpProviderBridge",
      "propsType": "PluginProviderDeclaration",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "contextSnapshotSchema",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 21,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L21"
        },
        {
          "name": "ContextSnapshot",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 32,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L32"
        },
        {
          "name": "ContextCategory",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 19,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L19"
        },
        {
          "name": "ContextEntry",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 10,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L10"
        },
        {
          "name": "PluginProviderDeclaration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1439,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1439"
        },
        {
          "name": "PluginProviderIconRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1415,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1415"
        }
      ],
      "reference": [
        {
          "plugin": "provider-acp",
          "file": "plugins/provider-acp/src/declaration.ts",
          "note": "Builds one declaration per ACP agent and ships the bridge from the same host artifact."
        },
        {
          "plugin": "echo-provider",
          "file": "examples/plugins/echo-provider/src/provider-bridge.ts",
          "note": "Smallest complete bridge: handshake, a session start and the minimal turn loop."
        }
      ],
      "rules": [
        "provider-needs-host",
        "provider-id-immutable",
        "experimental-churn"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your ACP agent appears as its own provider id and launches with its declared launch spec",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "adv-acp",
      "mockupHash": "composer,surface=provider-picker"
    },
    {
      "kind": "advanced",
      "id": "environments",
      "title": "Environment providers",
      "groupTitle": null,
      "number": null,
      "summary": "Offers plugin-provisioned places a thread can run, picked like any environment. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.experimental_environments.register(declaration | composition)",
      "propsType": "PluginEnvironmentProviderDefinition",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginEnvironments",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 401,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L401"
        },
        {
          "name": "PluginEnvironmentProviderDeclaration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 390,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L390"
        },
        {
          "name": "PluginEnvironmentProviderRequirements",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 370,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L370"
        },
        {
          "name": "PluginEnvironmentValidateDecision",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 381,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L381"
        },
        {
          "name": "PluginEnvironmentProviderInputsRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1550,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1550"
        },
        {
          "name": "PluginEnvironmentProviderInputsProps",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1518,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1518"
        },
        {
          "name": "PluginEnvironmentProviderInputsChange",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1537,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1537"
        },
        {
          "name": "experimental_BranchPicker",
          "path": "packages/plugin-sdk/src/app.ts",
          "line": 62,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app.ts#L62"
        }
      ],
      "reference": [
        {
          "plugin": "environment-personal-workspace",
          "file": "plugins/environment-personal-workspace/server.ts",
          "note": "Tiniest provider: requires projectless, one host call to create and one to remove."
        },
        {
          "plugin": "environment-git-worktree",
          "file": "plugins/environment-git-worktree/server.ts",
          "note": "Per-attempt path keys, existing-path adoption and experimental_claimPath."
        }
      ],
      "rules": [
        "env-create-idempotent",
        "env-failure-terminal",
        "env-inputs-public",
        "env-hooks-owned-by-core"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "A thread provisions a workspace through your provider and retires it under core's policy",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "adv-environments",
      "mockupHash": "headless,surface=environment-providers"
    },
    {
      "kind": "advanced",
      "id": "machines",
      "title": "Machine providers, server access and bootstrap",
      "groupTitle": null,
      "number": null,
      "summary": "Adds plugin-provisioned machines that compose with environment providers. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.experimental_machines.register(...) + bb.experimental_machines.bootstrap(...) + bb.experimental_serverAccess.register(...)",
      "propsType": "PluginMachineProviderDefinition",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginMachines",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 485,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L485"
        }
      ],
      "reference": [
        {
          "plugin": "environment-modal-sandbox",
          "file": "plugins/environment-modal-sandbox/providers/register.ts",
          "note": "The only first-party machine provider: create → checkpoint → bootstrap, plus suspend / resume / reconcileCleanup."
        }
      ],
      "rules": [
        "machine-checkpoint",
        "machine-needs-composition",
        "machine-resource-public",
        "access-acquire-idempotent"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Creating a machine enrolls a daemon, and removing it cleans up by durable key",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "adv-machines",
      "mockupHash": "headless,surface=machine-providers"
    },
    {
      "kind": "advanced",
      "id": "host-workers",
      "title": "Host workers",
      "groupTitle": null,
      "number": null,
      "summary": "Runs the plugin's code on an enrolled machine, not only on the bb server. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "host",
      "manifestEntry": "bb.host",
      "registration": "experimental_defineHostEntry({ contract, handlers, dispose }) + bb.hosts.experimental_client(...)",
      "propsType": "ExperimentalHostRpcContext",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginHosts",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1926,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1926"
        },
        {
          "name": "experimental_killProcessesWithCwdUnder",
          "path": "packages/plugin-sdk/src/host.ts",
          "line": 44,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/host.ts#L44"
        },
        {
          "name": "experimental_sanitizeInheritedChildProcessEnv",
          "path": "packages/plugin-sdk/src/host.ts",
          "line": 52,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/host.ts#L52"
        },
        {
          "name": "ExperimentalSanitizeInheritedChildProcessEnvArgs",
          "path": "packages/plugin-sdk/src/host.ts",
          "line": 55,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/host.ts#L55"
        },
        {
          "name": "experimental_spawnPortableOutputProcess",
          "path": "packages/plugin-sdk/src/host.ts",
          "line": 53,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/host.ts#L53"
        }
      ],
      "reference": [
        {
          "plugin": "keep-awake",
          "file": "plugins/keep-awake/server.ts",
          "note": "Calls the host worker to hold a wake lock and reacts to experimental_onWorkerExit."
        },
        {
          "plugin": "environment-git-worktree",
          "file": "plugins/environment-git-worktree/host.ts",
          "note": "Streams progress back with experimental_emitSignal correlated by an operationId."
        }
      ],
      "rules": [
        "host-call-not-in-factory",
        "host-limits",
        "host-worker-exit",
        "host-no-private-imports"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "`dist/host.js` is built, digest-verified and run by the daemon on the target machine",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "adv-host-workers",
      "mockupHash": "headless,surface=host-workers"
    },
    {
      "kind": "advanced",
      "id": "ai-services",
      "title": "AI services",
      "groupTitle": null,
      "number": null,
      "summary": "Lets a plugin answer bb's own helper-model calls — the short model calls behind thread titles and commit messages, and the microphone button's transcription. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "host",
      "manifestEntry": "bb.host",
      "registration": "bb.experimental_aiServices.register({ id, displayName, kinds })",
      "propsType": "PluginAiServiceDeclaration",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginAiServices",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1903,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1903"
        },
        {
          "name": "PluginAiServiceDeclaration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1894,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1894"
        }
      ],
      "reference": [
        {
          "plugin": "provider-codex",
          "file": "plugins/provider-codex/server.ts",
          "note": "The only first-party AI service: registers inference and voice from the codex host entry."
        }
      ],
      "rules": [
        "ai-needs-host",
        "ai-failures-returned",
        "ai-reserved-ids"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "bb's helper inference and transcription run through your host entry",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "adv-ai-services",
      "mockupHash": "headless,surface=ai-services"
    },
    {
      "kind": "advanced",
      "id": "desktop-browsers",
      "title": "Desktop browser control",
      "groupTitle": null,
      "number": null,
      "summary": "Controls a selected desktop window through bb.sdk.experimental_desktopBrowsers. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.sdk.experimental_desktopBrowsers.*",
      "propsType": "ExperimentalDesktopBrowsersArea",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "ExperimentalDesktopBrowsersArea",
          "path": "packages/sdk/src/areas/desktop-browsers.ts",
          "line": 40,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/sdk/src/areas/desktop-browsers.ts#L40"
        },
        {
          "name": "ExperimentalDesktopBrowserScope",
          "path": "packages/server-contract/src/api/desktop-browsers.ts",
          "line": 61,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/server-contract/src/api/desktop-browsers.ts#L61"
        },
        {
          "name": "ExperimentalDesktopBrowserLease",
          "path": "packages/server-contract/src/api/desktop-browsers.ts",
          "line": 83,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/server-contract/src/api/desktop-browsers.ts#L83"
        },
        {
          "name": "ExperimentalDesktopBrowserCreateInput",
          "path": "packages/server-contract/src/api/desktop-browsers.ts",
          "line": 77,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/server-contract/src/api/desktop-browsers.ts#L77"
        },
        {
          "name": "ExperimentalDesktopBrowserAcquireInput",
          "path": "packages/server-contract/src/api/desktop-browsers.ts",
          "line": 80,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/server-contract/src/api/desktop-browsers.ts#L80"
        }
      ],
      "reference": [
        {
          "plugin": "browser-automation",
          "file": "plugins/browser-automation/server.ts",
          "note": "The only consumer: scope → createTab → acquireControl → openConnection, with min() over every expiry."
        }
      ],
      "rules": [
        "browser-lease-ttl",
        "browser-allow-personal",
        "browser-host-match"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your automation drives a real desktop tab and releases control without closing the user's tabs",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "adv-desktop-browsers",
      "mockupHash": "headless,surface=desktop-browsers"
    },
    {
      "kind": "advanced",
      "id": "thread-metadata",
      "title": "Thread plugin metadata",
      "groupTitle": null,
      "number": null,
      "summary": "Stores namespaced plugin JSON for a thread without automatically exposing it to the model. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "bb.sdk.threads.getPluginMetadata / updatePluginMetadata",
      "propsType": "ThreadPluginMetadataResult",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "PluginBbSdk",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1981,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1981"
        },
        {
          "name": "PluginAgentConfigurationContext",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1130,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1130"
        },
        {
          "name": "ReadonlyJsonValue",
          "path": "packages/plugin-sdk/src/json-value.ts",
          "line": 20,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/json-value.ts#L20"
        },
        {
          "name": "BbPluginApi",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 2002,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L2002"
        }
      ],
      "reference": [],
      "rules": [
        "metadata-untrusted",
        "metadata-size",
        "metadata-frozen-in-configure"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Your namespace travels with the thread and is treated as untrusted input when read back",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "adv-thread-metadata",
      "mockupHash": "headless,surface=thread-plugin-metadata"
    },
    {
      "kind": "advanced",
      "id": "record-mode",
      "title": "Bridge record mode",
      "groupTitle": null,
      "number": null,
      "summary": "Adds an agent to bb's model picker and runs the threads started with it. With this, a plugin can:",
      "bullets": [],
      "tagline": null,
      "entry": "server",
      "manifestEntry": "bb.server",
      "registration": "BB_PROVIDER_BRIDGE_RECORD_DIR=<dir> + experimental_recordProviderChildIo(child, { threadId })",
      "propsType": "PluginProviderDeclaration",
      "slotKind": "additive",
      "stability": "experimental",
      "symbols": [
        {
          "name": "contextSnapshotSchema",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 21,
          "kind": "const",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L21"
        },
        {
          "name": "ContextSnapshot",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 32,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L32"
        },
        {
          "name": "ContextCategory",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 19,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L19"
        },
        {
          "name": "ContextEntry",
          "path": "packages/domain/src/context-snapshot.ts",
          "line": 10,
          "kind": "type",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/domain/src/context-snapshot.ts#L10"
        },
        {
          "name": "PluginProviderDeclaration",
          "path": "packages/plugin-sdk/src/backend-contract.ts",
          "line": 1439,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/backend-contract.ts#L1439"
        },
        {
          "name": "PluginProviderIconRegistration",
          "path": "packages/plugin-sdk/src/app-contract.ts",
          "line": 1415,
          "kind": "interface",
          "resolvedBy": "syntax",
          "url": "https://github.com/get-bb/bb/blob/e865697f56bea89f3413dd4cc7fae964850d20a0/packages/plugin-sdk/src/app-contract.ts#L1415"
        }
      ],
      "reference": [
        {
          "plugin": "provider-acp",
          "file": "plugins/provider-acp/src/declaration.ts",
          "note": "Builds one declaration per ACP agent and ships the bridge from the same host artifact."
        },
        {
          "plugin": "echo-provider",
          "file": "examples/plugins/echo-provider/src/provider-bridge.ts",
          "note": "Smallest complete bridge: handshake, a session start and the minimal turn loop."
        }
      ],
      "rules": [
        "bridge-grammar-v3",
        "host-no-private-imports"
      ],
      "doneWhen": [
        "`bb plugin build` completes without errors",
        "`bb plugin install .` then `bb plugin dev` loads the plugin — `bb plugin list` shows it `running`",
        "Both bridge boundaries are recorded as NDJSON you can replay in conformance tests",
        "Disabling the plugin (`bb plugin disable <id>`) returns bb to its previous behaviour with no leftovers"
      ],
      "sourceUrl": null,
      "guideAnchor": "adv-record-mode",
      "mockupHash": "composer,surface=provider-picker"
    }
  ]
}
