@get-bb/plugin-sdk 0.4.15 → 0.4.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { ReactNode, ComponentType } from 'react';
9
9
  import { RenderResult } from '@testing-library/react';
10
- import { PluginHomepageSectionRegistration, PluginSettingsSectionRegistration, PluginNavPanelRegistration, PluginThreadPanelActionRegistration, PluginNewThreadPanelActionRegistration, ComposerCustomization, PluginPendingInteractionRegistration, PluginSidebarFooterActionRegistration, PluginThreadListRegistration, PluginThreadHeaderActionRegistration, PluginFileOpenerRegistration, PluginSourceCodeRendererRegistration, PluginDiffRendererRegistration, PluginMessageDirectiveRegistration, PluginMessageActionRegistration, PluginProviderIconRegistration, PluginContentScriptRegistration, PluginComposerScope, PluginComposerTextEffect, PluginComposerMention, PluginComposerThreadRowStatus, ExperimentalOpenFixedTabOptions, JsonValue, BbNavigate, ExperimentalFileOpenOptions, PluginAppDefinition, PluginRpcContract, StandardSchemaV1InferInput, PluginRpcResult, PluginRealtimeConnectionState, PluginSidebarThreadsState, PluginProvidersState, PluginSidebarPullRequest, PluginSidebarThreadActions } from '@get-bb/plugin-sdk';
10
+ import { PluginHomepageSectionRegistration, PluginSettingsSectionRegistration, PluginNavPanelRegistration, PluginThreadPanelActionRegistration, PluginNewThreadPanelActionRegistration, ComposerCustomization, PluginPendingInteractionRegistration, PluginSidebarFooterActionRegistration, PluginThreadListRegistration, PluginThreadHeaderActionRegistration, PluginFileOpenerRegistration, PluginSourceCodeRendererRegistration, PluginDiffRendererRegistration, PluginMessageDirectiveRegistration, PluginMessageActionRegistration, PluginProviderIconRegistration, PluginTimelineRendererRegistration, PluginContentScriptRegistration, PluginComposerScope, PluginComposerTextEffect, PluginComposerMention, PluginComposerThreadRowStatus, ExperimentalOpenFixedTabOptions, JsonValue, BbNavigate, ExperimentalFileOpenOptions, PluginAppDefinition, PluginRpcContract, StandardSchemaV1InferInput, PluginRpcResult, PluginRealtimeConnectionState, PluginSidebarThreadsState, PluginProvidersState, PluginSidebarPullRequest, PluginSidebarThreadActions } from '@get-bb/plugin-sdk';
11
11
 
12
12
  /**
13
13
  * `@get-bb/plugin-sdk/testing/app` — the frontend plugin test harness. Tests a
@@ -60,7 +60,7 @@ type NavigateCall = {
60
60
  method: "openThreadPanel";
61
61
  options: Parameters<BbNavigate["openThreadPanel"]>[0];
62
62
  } | {
63
- method: "experimental_openUrl";
63
+ method: "openUrl";
64
64
  url: string;
65
65
  } | {
66
66
  method: "experimental_openFilePreview";
@@ -124,6 +124,7 @@ interface CapturedPluginApp {
124
124
  messageDirectives: PluginMessageDirectiveRegistration[];
125
125
  messageActions: PluginMessageActionRegistration[];
126
126
  providerIcons: PluginProviderIconRegistration[];
127
+ timelineRenderers: PluginTimelineRendererRegistration[];
127
128
  contentScripts: PluginContentScriptRegistration[];
128
129
  }
129
130
  type PluginAppModule = {
@@ -213,7 +214,7 @@ interface RenderSlotOptions<Contract extends PluginRpcContract = PluginRpcContra
213
214
  sidebarPullRequests?: Record<string, PluginSidebarPullRequest>;
214
215
  /** Host acceptance for `useBbNavigate().openThreadPanel`. */
215
216
  openThreadPanel?: (options: Parameters<BbNavigate["openThreadPanel"]>[0]) => boolean;
216
- /** Host acceptance for URL intents from the hook or `experimental_UrlLink`. */
217
+ /** Host acceptance for URL intents from the hook or `UrlLink`. */
217
218
  openUrl?: (url: string) => boolean;
218
219
  /** Host acceptance for preview intents from the hook or file link. */
219
220
  openFilePreview?: (options: ExperimentalFileOpenOptions) => boolean;
@@ -5,7 +5,488 @@
5
5
  // Confused by the API, or need a symbol that isn't here? Clone the BB repo
6
6
  // and read the real source: https://github.com/get-bb/bb
7
7
 
8
- import { BbPluginApi, PluginSettingValue, PluginSharedPortTunnelIdentity, PluginAgentToolExperimentalStatusLabels, PluginAgentToolContext, PluginAgentToolResult, PluginCliCommandInfo, PluginCliContext, PluginCliResult, PluginHttpAuthMode, PluginHttpHandler, PluginMentionTrigger, PluginMentionSearchContext, PluginMentionItem, JsonValue, PluginCliExecutionResult, PluginThreadEventName, PluginThreadEventPayloads, PluginAgentConfigurationContext, PluginSettingDescriptors, PluginAgentConfiguration, PluginProviderDeclaration, PluginInteractionRequest } from '@get-bb/plugin-sdk';
8
+ import { z } from 'zod';
9
+ import { BbPluginApi, PluginSettingValue as PluginSettingValue$1, PluginSharedPortTunnelIdentity, PluginAgentToolPresentation, PluginAgentToolContext, PluginAgentToolResult, PluginCliCommandInfo, PluginCliContext, PluginCliResult, PluginHttpAuthMode, PluginHttpHandler, PluginMentionTrigger, PluginMentionSearchContext, PluginMentionItem, JsonValue as JsonValue$1, PluginCliExecutionResult, PluginThreadEventName, PluginThreadEventPayloads, PluginAgentConfigurationContext, PluginSettingDescriptors, PluginAgentConfiguration, PluginAiServiceDeclaration, PluginInteractionRequest } from '@get-bb/plugin-sdk';
10
+
11
+ /** Input-form entry: a path, or a path with options. */
12
+ declare const providerNativeRootInputSchema: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
13
+ ancestors: z.ZodOptional<z.ZodBoolean>;
14
+ namePrefix: z.ZodOptional<z.ZodString>;
15
+ path: z.ZodString;
16
+ recursive: z.ZodOptional<z.ZodBoolean>;
17
+ skipIfManifest: z.ZodOptional<z.ZodString>;
18
+ }, z.core.$strict>]>;
19
+ /**
20
+ * One provider-native root as a plugin declares it: a path, or a path with
21
+ * options. `recursive`: the agent scans nested skill directories. `ancestors`
22
+ * (project roots only): scan the same relative directory in every ancestor of
23
+ * the workspace up to the repository root. `namePrefix`: prepended to every
24
+ * name under the root, a vendor plugin's `plugin-name:`; a prefixed root is
25
+ * listed as a plugin root. `skipIfManifest`: a vendor-plugin marker file to
26
+ * skip by.
27
+ */
28
+ type ProviderNativeRootInput = z.infer<typeof providerNativeRootInputSchema>;
29
+ /**
30
+ * Normalized roots: relative to the host home (`user`) or to the workspace
31
+ * (`project`). The daemon parses this off the wire; the server produces it
32
+ * from a declaration.
33
+ */
34
+ declare const providerNativeRootsSchema: z.ZodObject<{
35
+ project: z.ZodArray<z.ZodObject<{
36
+ ancestors: z.ZodBoolean;
37
+ namePrefix: z.ZodString;
38
+ path: z.ZodString;
39
+ recursive: z.ZodBoolean;
40
+ skipIfManifest: z.ZodOptional<z.ZodString>;
41
+ }, z.core.$strict>>;
42
+ user: z.ZodArray<z.ZodObject<{
43
+ ancestors: z.ZodBoolean;
44
+ namePrefix: z.ZodString;
45
+ path: z.ZodString;
46
+ recursive: z.ZodBoolean;
47
+ skipIfManifest: z.ZodOptional<z.ZodString>;
48
+ }, z.core.$strict>>;
49
+ }, z.core.$strict>;
50
+ type ProviderNativeRoots = z.infer<typeof providerNativeRootsSchema>;
51
+ /**
52
+ * Provider-native roots as a plugin's frozen declaration holds them: relative
53
+ * to the target host's home (`user`) or to the workspace (`project`). Paths
54
+ * are relative without dot segments, unique per side, at most 32 per side. A
55
+ * root only one host can name — a moved config directory, a settings entry —
56
+ * is the resolver's answer (`resolveNativeRoots`), never a declared root.
57
+ */
58
+ interface ProviderNativeRootsInputLike {
59
+ readonly user?: readonly ProviderNativeRootInput[];
60
+ readonly project?: readonly ProviderNativeRootInput[];
61
+ }
62
+
63
+ /**
64
+ * How completely a provider can clone one of its sessions — the single
65
+ * vocabulary shared by the provider declaration
66
+ * (`bb.providers.register`), the server→daemon
67
+ * `bridgeLaunch`, and the bridge's `initialize` handshake.
68
+ *
69
+ * - `"none"`: sessions cannot be cloned at all.
70
+ * - `"tip"`: only the current end of a session can be cloned (ACP
71
+ * `session/fork`), so thread fork works but edit-past-message rewind
72
+ * cannot.
73
+ * - `"checkpoint"`: a session can be recreated at an earlier point, which is
74
+ * what edit-past-message rewind needs.
75
+ *
76
+ * The values are ordered least to most capable: a declaration is a ceiling
77
+ * the handshake may narrow but never widen.
78
+ */
79
+ declare const PROVIDER_FORK_VALUES: readonly ["none", "tip", "checkpoint"];
80
+ type ProviderFork = (typeof PROVIDER_FORK_VALUES)[number];
81
+
82
+ /**
83
+ * A value that survives a JSON round trip without coercion or data loss.
84
+ *
85
+ * Host boundaries still validate values at runtime because TypeScript cannot
86
+ * exclude non-finite numbers and plugin bundles can bypass static types.
87
+ */
88
+ type JsonValue = string | number | boolean | null | JsonValue[] | {
89
+ [key: string]: JsonValue;
90
+ };
91
+
92
+ /**
93
+ * The validator-neutral subset of Standard Schema v1 used by plugin RPC.
94
+ * Zod 4 schemas implement this interface directly; other validators can do
95
+ * the same without becoming part of BB's public protocol.
96
+ */
97
+ interface StandardSchemaV1<Input = unknown, Output = Input> {
98
+ readonly "~standard": {
99
+ readonly version: 1;
100
+ readonly vendor: string;
101
+ readonly validate: (value: unknown) => StandardSchemaV1Result<Output> | Promise<StandardSchemaV1Result<Output>>;
102
+ readonly types?: {
103
+ readonly input: Input;
104
+ readonly output: Output;
105
+ };
106
+ };
107
+ }
108
+ type StandardSchemaV1Result<Output> = {
109
+ readonly value: Output;
110
+ readonly issues?: undefined;
111
+ } | {
112
+ readonly issues: readonly StandardSchemaV1Issue[];
113
+ };
114
+ interface StandardSchemaV1Issue {
115
+ readonly message: string;
116
+ readonly path?: PropertyKey | readonly (PropertyKey | {
117
+ readonly key: PropertyKey;
118
+ })[];
119
+ }
120
+
121
+ type PluginSettingValue = string | boolean;
122
+ /**
123
+ * Permission modes a provider can run a session in — BB's own permission
124
+ * vocabulary, ordered least ("accept-edits") to most ("full") privileged.
125
+ */
126
+ type PluginProviderPermissionMode = "accept-edits" | "auto" | "full";
127
+ /**
128
+ * Coarse reasoning-effort ladder entries, ordered lowest to highest. The
129
+ * declared ladder is a fallback only: precise per-model reasoning sets come
130
+ * from the provider's model list at runtime.
131
+ */
132
+ type PluginProviderReasoningLevel = "high" | "low" | "max" | "medium" | "none" | "ultra" | "ultracode" | "xhigh";
133
+ /**
134
+ * Composer actions a provider supports, by name only. The skills
135
+ * slash-command typeahead is universal — BB injects skills into every
136
+ * provider — so it is implicit and never declared, and the composer owns the
137
+ * trigger syntax (`/plan `, `/goal `) rather than each declaration repeating
138
+ * it.
139
+ */
140
+ type PluginProviderComposerAction = "goal" | "plan";
141
+ /**
142
+ * Pre-session capability facts about a provider. A capability earns a field
143
+ * here only when it passes BOTH tests: (1) a consumer outside the provider's
144
+ * own plugin needs the fact, and (2) the fact is needed before / without a
145
+ * live session (picker rendering, route gating, cross-plugin tool
146
+ * composition — including with the host offline). Every boolean is a
147
+ * provider-native fact — the provider implements the feature; the flag only
148
+ * tells external consumers it exists. Session-behavior facts remain handshake
149
+ * capabilities reported by the running bridge. Sessionless maintenance
150
+ * methods are declared here so callers can decide whether to probe without
151
+ * starting the bridge first.
152
+ */
153
+ interface PluginProviderCapabilities {
154
+ /** The provider accepts a fast/priority service-tier choice — shows the
155
+ * service-tier toggle in the picker. */
156
+ supportsServiceTier: boolean;
157
+ /** The provider ships its own native ask-user-question tool — the
158
+ * ask-user-question plugin skips registering its duplicate. */
159
+ supportsNativeUserQuestion: boolean;
160
+ /**
161
+ * How completely the provider can clone a session: `"none"` (not at all),
162
+ * `"tip"` (only the current end, so thread fork works but edit-past-message
163
+ * rewind cannot), or `"checkpoint"` (recreate the session at an earlier
164
+ * point, which rewind needs). Gates the fork and edit-past-message
165
+ * affordances. The bridge reports the same fact at `initialize`, where it
166
+ * may narrow this declaration but never widen it.
167
+ */
168
+ fork: ProviderFork;
169
+ /** The provider accepts an explicit context-compaction request — gates the
170
+ * compact affordance. */
171
+ supportsManualCompaction: boolean;
172
+ /** The provider keeps its own thread archive, so BB mirrors archive and
173
+ * unarchive onto it instead of tracking the state only in bb's own rows. */
174
+ supportsThreadArchive: boolean;
175
+ /** The provider stores a thread name of its own, so BB forwards renames to
176
+ * it. */
177
+ supportsThreadRename: boolean;
178
+ /** Permission modes the provider can actually run in. Non-empty, no
179
+ * duplicates. */
180
+ permissionModes: readonly PluginProviderPermissionMode[];
181
+ /** The provider's coarse fallback reasoning ladder (see
182
+ * {@link PluginProviderReasoningLevel}). Non-empty, no duplicates. */
183
+ reasoningLevels: readonly PluginProviderReasoningLevel[];
184
+ }
185
+ /**
186
+ * Provider copy core surfaces render from per-provider tables today (usage
187
+ * banners, sign-in hints, the mobile picker, the agent guide). Declared once
188
+ * here so no core surface keys copy on a provider id. Mirrors
189
+ * `ProviderStrings` in `@bb/domain`, which is the client projection.
190
+ */
191
+ interface PluginProviderStrings {
192
+ /** How to sign in on the host ("Run `claude` on the machine to sign in."). */
193
+ signInHint: string;
194
+ /** Shown when a session's credentials expired. */
195
+ expiredHint: string;
196
+ /** Where to install the agent. */
197
+ installUrl: string;
198
+ /** Brand prefix stripped from model display names ("Claude "). */
199
+ brandPrefix?: string;
200
+ /** Plan-mode banner copy for providers that declare the `plan` action. */
201
+ planModeCopy?: string;
202
+ /** Per-theme tint for the provider icon. */
203
+ iconTint?: {
204
+ light: string;
205
+ dark: string;
206
+ };
207
+ }
208
+ /**
209
+ * One selectable option for a picker — a service tier or a reasoning level.
210
+ * `id` is the wire value the bridge receives; `label` is what the picker
211
+ * shows. Declared lists are the cold-cache fallback; `model/list` is precise
212
+ * per model.
213
+ */
214
+ interface PluginProviderOptionDescriptor {
215
+ id: string;
216
+ label: string;
217
+ description?: string;
218
+ }
219
+ /**
220
+ * Payload schemas for one extension kind this provider emits, keyed by the
221
+ * kind's local name (the server prefixes the plugin id to form the
222
+ * namespaced `"<pluginId>/<name>"`). `item` validates `item.open` payloads
223
+ * with `type: "extension"`, `state` validates `extension.state` payloads;
224
+ * each is optional so a kind can be item-only or state-only. Schemas are
225
+ * Standard Schema v1 validators (zod 4 schemas qualify).
226
+ */
227
+ interface PluginProviderExtensionKindDeclaration {
228
+ item?: StandardSchemaV1;
229
+ state?: StandardSchemaV1;
230
+ }
231
+ /**
232
+ * Per-command context handed to
233
+ * {@link PluginProviderDeclaration.deriveProviderOptions}. The
234
+ * server builds one for every session and turn command it dispatches on a
235
+ * thread of this provider.
236
+ */
237
+ interface PluginProviderOptionsContext {
238
+ threadId: string;
239
+ projectId: string;
240
+ /** The resolved model id for this command. */
241
+ model: string;
242
+ /** BB's permission mode for this command (already clamped to the host). */
243
+ permissionMode: PluginProviderPermissionMode;
244
+ /**
245
+ * `"plan"` when the prompt entered plan mode through this provider's
246
+ * declared `plan` composer action. Absent for an ordinary prompt — plan
247
+ * mode is a BB prompt mode, so the bridge maps it onto whatever the agent
248
+ * calls it natively.
249
+ */
250
+ promptMode?: "plan";
251
+ /**
252
+ * This plugin's own settings values (`bb.settings.define`), read at call
253
+ * time. Secret settings are omitted — provider options ride the daemon
254
+ * wire and are persisted with the session, so a secret must never be
255
+ * derived into them.
256
+ */
257
+ settings: Readonly<Record<string, PluginSettingValue | undefined>>;
258
+ }
259
+ /** See {@link PluginProviderDeclaration.models}. */
260
+ type PluginProviderModelCatalogScope = "host" | "workspace";
261
+ /**
262
+ * One cold-cache fallback model. The provider's live `model/list` result is
263
+ * the only real model source; this list stands in only while no probe has
264
+ * completed, or when a probe fails transiently, so the picker is not empty.
265
+ * `id` is the wire model id the bridge receives.
266
+ */
267
+ interface PluginProviderFallbackModel {
268
+ id: string;
269
+ /** Picker display name ("Opus 5 (1M)"). */
270
+ displayName: string;
271
+ description: string;
272
+ /** Reasoning levels this model supports, lowest to highest. Non-empty. */
273
+ supportedReasoningEfforts: readonly {
274
+ reasoningEffort: PluginProviderReasoningLevel;
275
+ description: string;
276
+ }[];
277
+ /** Must be one of `supportedReasoningEfforts`. */
278
+ defaultReasoningEffort: PluginProviderReasoningLevel;
279
+ /** Exactly one entry in the list is the default. */
280
+ isDefault: boolean;
281
+ }
282
+ /**
283
+ * Which sessionless maintenance requests a provider bridge implements. The
284
+ * server skips the requests a provider does not declare, and clients omit
285
+ * the matching surfaces, without starting the bridge first.
286
+ */
287
+ interface PluginProviderMaintenance {
288
+ /** `provider/health`: host-local readiness, never a network health check. */
289
+ health?: boolean;
290
+ /** `provider/usage`: subscription usage windows. False means usage settings
291
+ * omit the provider. A shared bridge that declares true may still report
292
+ * usage unavailable for one provider id or return no windows. */
293
+ usage?: boolean;
294
+ /** `provider/installation/status` and `provider/installation/run`:
295
+ * host-local installation management. */
296
+ installation?: boolean;
297
+ }
298
+ /** Provider-native roots as a plugin declares them, one list per side. */
299
+ type PluginProviderNativeRoots = ProviderNativeRootsInputLike;
300
+ /**
301
+ * One provider this plugin contributes to BB's provider registry.
302
+ *
303
+ * Ids are stable public identifiers — thread rows and routes reference them —
304
+ * and are collision-rejected: a declaration whose id matches another plugin's
305
+ * live registration is refused; the first registration wins and no id is
306
+ * reserved ahead of time. Registrations are replaced wholesale on plugin
307
+ * reload, like every other plugin surface.
308
+ *
309
+ * A declaration owns the provider's static metadata and bridge options. The
310
+ * executable implementation is the plugin's own provider bridge: the
311
+ * `experimental_providerBridge` export of the `bb.host` artifact the manifest
312
+ * names (`PROVIDER_BRIDGE_EXPORT_NAME` in the bridge kit), built into the
313
+ * artifact BB ships to hosts. Declaring a provider in a plugin with no
314
+ * `bb.host` entry is refused, because the picker entry would exist and no
315
+ * turn on it could ever run; a `bb.host` entry whose artifact failed to
316
+ * build still stages the declaration so the provider is listed as
317
+ * unavailable.
318
+ */
319
+ interface PluginProviderDeclaration {
320
+ /** Stable provider id: 2–64 characters of lowercase letters, digits, and
321
+ * "-", starting with a letter or digit. Existing ids must never change —
322
+ * threads persist them. */
323
+ id: string;
324
+ /** Picker display name: 1–80 characters, non-blank. */
325
+ displayName: string;
326
+ /**
327
+ * Optional grouping key (same grammar as `id`) for providers that share a
328
+ * family — the ACP agents, for example — so clients can group them without
329
+ * parsing a prefix out of the id. Grouping only: no policy keys on it.
330
+ */
331
+ family?: string;
332
+ /**
333
+ * Optional picker icon: a named host glyph (`"Zap"`) or a plugin-relative
334
+ * path starting with `"./"` (`"./icons/agent.svg"`) — the two forms
335
+ * `bb.branding.icon` takes — or, unlike `bb.branding.icon`, one of this
336
+ * plugin's declared icons by its namespaced glyph (`"<pluginId>/<name>"`,
337
+ * an entry of the manifest's `bb.branding.experimental_icons` map; the
338
+ * plugin id must be this plugin's and the name must be declared, else the
339
+ * plugin fails to load). Paths follow the manifest entry-path escape rules
340
+ * — no leading "/", no ".." segments, no backslashes.
341
+ */
342
+ icon?: string;
343
+ /**
344
+ * Provider-owned static options passed opaquely to this plugin's bridge on
345
+ * every sessionless and session request. Core validates that the value is
346
+ * JSON, but does not interpret its keys. This is intended for immutable
347
+ * launch metadata shared by every host (for example an ACP command spec),
348
+ * not user or machine configuration.
349
+ */
350
+ experimental_bridgeOptions?: Readonly<Record<string, JsonValue>>;
351
+ /**
352
+ * Whether the provider is always listed or only listed on hosts where its
353
+ * bridge reports it installed. Defaults to `"always"`.
354
+ */
355
+ experimental_visibility?: "always" | "installed";
356
+ /**
357
+ * The sessionless maintenance requests the provider's bridge implements
358
+ * (docs/provider-plugin-api.md §1). Each defaults to false when omitted.
359
+ */
360
+ maintenance?: PluginProviderMaintenance;
361
+ /** Pre-session capability facts (see the declaration tests on
362
+ * {@link PluginProviderCapabilities}). */
363
+ capabilities: PluginProviderCapabilities;
364
+ /** Composer actions this provider supports. No duplicates; may be empty
365
+ * (the universal skills typeahead is implicit). */
366
+ composerActions: readonly PluginProviderComposerAction[];
367
+ /** Provider copy for core surfaces ({@link PluginProviderStrings}). */
368
+ strings?: PluginProviderStrings;
369
+ /** Service tiers this provider accepts, as picker options. Non-empty when
370
+ * present, unique ids. The coarse `capabilities.supportsServiceTier` stays
371
+ * until WS2a stabilizes. */
372
+ serviceTiers?: readonly PluginProviderOptionDescriptor[];
373
+ /** Reasoning levels as picker options with labels, beside the coarse
374
+ * `capabilities.reasoningLevels` ladder (ids only). Non-empty when present,
375
+ * unique ids. WS2a merges the two. */
376
+ reasoningLevels?: readonly PluginProviderOptionDescriptor[];
377
+ /** Extension kinds this provider's bridge may emit, keyed by local name
378
+ * (`[a-z0-9-]+`). The server validates extension payloads against these
379
+ * schemas at ingest and persists a `provider/unhandled` on a miss. */
380
+ extensionKinds?: Readonly<Record<string, PluginProviderExtensionKindDeclaration>>;
381
+ /**
382
+ * Cold-cache fallback models ({@link PluginProviderFallbackModel}). The
383
+ * server offers them only while a model probe has not completed or failed
384
+ * transiently; the live `model/list` result always replaces them. Ids must
385
+ * be unique and exactly one entry must be the default.
386
+ */
387
+ models?: {
388
+ /**
389
+ * Optional: a provider that only declares a catalog `scope` needs no
390
+ * fallback list, and an omitted list reads as no fallbacks at all.
391
+ */
392
+ fallback?: readonly PluginProviderFallbackModel[];
393
+ /**
394
+ * How far one `model/list` answer travels. `"host"` means the catalog is
395
+ * the same everywhere on a machine — the bridge answers from account or
396
+ * agent state and ignores the workspace path — so bb probes once per host
397
+ * and reuses the answer for every environment on it. `"workspace"` (the
398
+ * default) means project configuration can change the answer, so bb
399
+ * probes per workspace and sends the path.
400
+ *
401
+ * Declaring `"host"` wrongly is a stale catalog in a workspace that
402
+ * configured its own models; declaring `"workspace"` wrongly costs a
403
+ * redundant probe. The default is therefore the safe one.
404
+ */
405
+ scope?: PluginProviderModelCatalogScope;
406
+ };
407
+ /**
408
+ * Daemon environment variables this provider's bridge may read. Provider
409
+ * processes are spawned with every inherited `BB_*` variable stripped, so a
410
+ * bridge that honors an operator override (a CLI path, say) names it here
411
+ * and the daemon forwards exactly those variables. Names are
412
+ * `[A-Z_][A-Z0-9_]*`, at most 32.
413
+ */
414
+ env?: {
415
+ passthrough: readonly string[];
416
+ };
417
+ /**
418
+ * Directories this provider's agent reads its own skills from, relative to
419
+ * the target host's home directory (`user`) or to the workspace
420
+ * (`project`). An agent with skills of its own — an ACP agent pointed at
421
+ * `.cursor/skills`, say — names them here so bb can list them beside its
422
+ * own; core never guesses a provider's skill layout. Paths are relative
423
+ * and may not contain dot segments; each side holds at most 32 roots. One
424
+ * declaration is global, so a directory only one host can name (an agent's
425
+ * settings-configured skills directory, say) is not declared here but
426
+ * resolved on that host (`experimental_resolvesNativeRoots`).
427
+ */
428
+ experimental_nativeSkillRoots?: PluginProviderNativeRoots;
429
+ /**
430
+ * Directories this provider's agent reads its own slash commands from —
431
+ * flat directories of `*.md` prompt files (`.claude/commands`, say) — in
432
+ * the same two-sided shape as `experimental_nativeSkillRoots`. bb offers
433
+ * them in the composer beside the agent's skills.
434
+ */
435
+ experimental_nativeCommandRoots?: PluginProviderNativeRoots;
436
+ /**
437
+ * This plugin's `bb.host` entry implements
438
+ * `experimental_nativeRootsHostContract` (`@get-bb/plugin-sdk/host`): core
439
+ * calls `resolveNativeRoots({ cwd })` on the workspace host when it lists
440
+ * commands or skills, and scans what comes back beside the declared roots.
441
+ * This is where a provider's host-only knowledge goes — a config-moved
442
+ * directory, an installed vendor plugin, a config-file entry — including
443
+ * project-scoped entries, which a global declaration cannot carry.
444
+ */
445
+ experimental_resolvesNativeRoots?: boolean;
446
+ /**
447
+ * Derive this provider's opaque per-command options. Called synchronously
448
+ * by the server for every session and turn command on a thread of this
449
+ * provider, with the command's {@link PluginProviderOptionsContext}; the
450
+ * returned JSON object reaches this plugin's bridge as
451
+ * `options.providerOptions`, merged over `experimental_bridgeOptions`. Core
452
+ * never interprets its keys — this is where a provider's own knobs (memory,
453
+ * native subagents, a native plan flag) travel instead of on the shared
454
+ * execution contract. A throw fails the command with the plugin named, so
455
+ * a buggy hook cannot silently run a turn with default knobs. Must be fast:
456
+ * it sits on the turn-submit path.
457
+ */
458
+ deriveProviderOptions?: (context: PluginProviderOptionsContext) => Readonly<Record<string, JsonValue>>;
459
+ }
460
+
461
+ /**
462
+ * Validate one `bb.providers.register` declaration. Plugin
463
+ * sources are untyped at runtime, so every field is checked; the production
464
+ * host and the fake host both call this, so they accept and reject provider
465
+ * declarations identically. Throws a descriptive error on the first problem;
466
+ * returns a normalized, deeply frozen copy carrying only contract fields.
467
+ */
468
+ /**
469
+ * A declaration that has been through {@link validatePluginProviderDeclaration}.
470
+ *
471
+ * The validator fills the defaults it owns, so a consumer reads one explicit
472
+ * value rather than re-deciding what an absent field means. Only the fields
473
+ * the validator GUARANTEES are narrowed here; everything else keeps the
474
+ * author-facing shape.
475
+ */
476
+ type NormalizedPluginProviderDeclaration = Omit<PluginProviderDeclaration, "experimental_nativeCommandRoots" | "experimental_nativeSkillRoots" | "experimental_resolvesNativeRoots"> & {
477
+ readonly experimental_nativeSkillRoots?: ProviderNativeRoots;
478
+ readonly experimental_nativeCommandRoots?: ProviderNativeRoots;
479
+ readonly experimental_resolvesNativeRoots: boolean;
480
+ readonly maintenance: {
481
+ readonly health: boolean;
482
+ readonly usage: boolean;
483
+ readonly installation: boolean;
484
+ };
485
+ readonly models: {
486
+ readonly fallback?: readonly PluginProviderFallbackModel[];
487
+ readonly scope: PluginProviderModelCatalogScope;
488
+ };
489
+ };
9
490
 
10
491
  type BbSdk = BbPluginApi["sdk"];
11
492
  /**
@@ -108,8 +589,14 @@ interface FakeCliRecord {
108
589
  interface FakeAgentToolRecord {
109
590
  name: string;
110
591
  description: string;
111
- experimentalStatusLabels: PluginAgentToolExperimentalStatusLabels | null;
112
592
  instructions: string | null;
593
+ /**
594
+ * The plugin's declared row presentation, null when it declared none.
595
+ * Parsed by the shared `parsePluginAgentToolPresentation`, so the record
596
+ * holds exactly what the production host stores and a presentation bb
597
+ * rejects is rejected here with the same message.
598
+ */
599
+ presentation: PluginAgentToolPresentation | null;
113
600
  /** JSON-schema object the host would send providers. */
114
601
  inputSchema: unknown;
115
602
  parse(input: unknown): {
@@ -161,9 +648,12 @@ interface FakePluginRegistrations {
161
648
  }) => string | null) | null;
162
649
  threadEventHandlers: Record<PluginThreadEventName, number>;
163
650
  mentionProviders: FakeMentionProviderRecord[];
164
- /** Live provider registrations from `experimental_registerProvider`
651
+ /** Live provider registrations from `bb.providers.register`
165
652
  * (normalized declarations, registration order; dispose removes). */
166
- providerRegistrations: PluginProviderDeclaration[];
653
+ providerRegistrations: NormalizedPluginProviderDeclaration[];
654
+ /** Live AI-service registrations from `experimental_aiServices.register`
655
+ * (normalized declarations, registration order; dispose removes). */
656
+ aiServiceRegistrations: PluginAiServiceDeclaration[];
167
657
  }
168
658
  /** Read-only state for assertions after a plugin registers or handles work. */
169
659
  interface FakePluginInspectionState {
@@ -193,7 +683,7 @@ interface FakePluginBehaviorDrivers {
193
683
  experimental_emitHostWorkerExit(hostId: string): Promise<void>;
194
684
  /** Deliver a host signal through its registered payload schema. */
195
685
  experimental_emitHostSignal(hostId: string, signal: string, payload: unknown): Promise<void>;
196
- submitInteraction(id: string, value: JsonValue): void;
686
+ submitInteraction(id: string, value: JsonValue$1): void;
197
687
  cancelInteraction(id: string): void;
198
688
  /**
199
689
  * Apply a settings update the way the host's settings save does:
@@ -201,7 +691,7 @@ interface FakePluginBehaviorDrivers {
201
691
  * fire `onChange` listeners when effective values changed. Throws on
202
692
  * unknown keys or wrong value types.
203
693
  */
204
- setSettings(values: Record<string, PluginSettingValue | null>): Promise<void>;
694
+ setSettings(values: Record<string, PluginSettingValue$1 | null>): Promise<void>;
205
695
  /**
206
696
  * Invoke a registered rpc method with host semantics: input/output schemas,
207
697
  * strict JSON result normalization, and structured failure codes. Rejects
@@ -291,19 +781,42 @@ interface CreateFakePluginHostOptions {
291
781
  * `bb.sdk`). Defaults to "http://127.0.0.1:38886".
292
782
  */
293
783
  loopbackBaseUrl?: string;
784
+ /**
785
+ * Value served by `bb.server.experimental_dataDir`. Defaults to
786
+ * "/tmp/bb-fake-data-dir".
787
+ */
788
+ dataDir?: string;
294
789
  /**
295
790
  * Pre-seeded stored settings values (as if saved before this load) —
296
791
  * including secret ones, which the fake keeps in memory instead of
297
792
  * files. Values with the wrong type for their descriptor fall back to
298
793
  * the descriptor default on read, like the host.
299
794
  */
300
- settings?: Record<string, PluginSettingValue>;
795
+ settings?: Record<string, PluginSettingValue$1>;
301
796
  /** Initial `bb.sdk` stubs; extend later via `harness.sdk.stub`. */
302
797
  sdk?: FakeSdkOverrides;
303
798
  /** Static manifest skill ids available to configure() in this fake host. */
304
799
  agentSkillIds?: readonly string[];
305
800
  /** Read-only identities returned by bb.hosts.ensureSharedPortTunnel. */
306
801
  sharedPortTunnelIdentities?: Record<string, PluginSharedPortTunnelIdentity>;
802
+ /**
803
+ * Whether the plugin's manifest declares a `bb.host` entry. Production
804
+ * refuses `bb.providers.register` (the provider would have no bridge to
805
+ * run on) and `experimental_aiServices.register` (the service would have
806
+ * nothing to run on) without one; the fake applies the same rules.
807
+ * Defaults to true.
808
+ */
809
+ experimental_hostEntry?: boolean;
810
+ /**
811
+ * The icon names the plugin's manifest declares under
812
+ * `bb.branding.experimental_icons`. Production refuses a provider `icon`
813
+ * or a tool `presentation.icon.glyph` that is a namespaced glyph
814
+ * (`"<pluginId>/<name>"`) naming another plugin or a name not declared
815
+ * there; the fake applies the same rule against this list. Defaults to
816
+ * none declared, so every namespaced glyph is refused until the test
817
+ * names the icons the manifest would.
818
+ */
819
+ experimental_declaredIconNames?: readonly string[];
307
820
  /** Deterministic stand-in for the targeted daemon host entry. */
308
821
  experimental_callHostRpc?: (call: ExperimentalFakeHostRpcCall) => unknown | Promise<unknown>;
309
822
  }
@@ -322,5 +835,34 @@ type ThreadResponse = PluginThreadEventPayloads["thread.created"]["thread"];
322
835
  */
323
836
  declare function makeThreadResponse(overrides?: Partial<ThreadResponse>): ThreadResponse;
324
837
 
325
- export { PluginContextStaleError, createFakePluginHost, createFakeSdk, makeThreadResponse };
326
- export type { CreateFakePluginHostOptions, FakeAgentToolRecord, FakeCliRecord, FakeHttpRouteRecord, FakeLogEntry, FakeLogLevel, FakeMentionProviderRecord, FakePluginBehaviorDrivers, FakePluginHarness, FakePluginHost, FakePluginInspectionState, FakePluginLifecycleControls, FakePluginRegistrations, FakeRealtimeSignal, FakeScheduleRecord, FakeSdkCall, FakeSdkHarness, FakeSdkOverrides, FakeServiceRecord };
838
+ interface PublicSdkOnlyScanOptions {
839
+ /**
840
+ * Public packages the plugin depends on beyond the SDK (a config-file
841
+ * parser, say), matched against the whole specifier. Plugin code and tests
842
+ * alike may import them.
843
+ */
844
+ allow?: readonly RegExp[];
845
+ }
846
+ interface PublicSdkOnlyViolation {
847
+ /** The importing file, relative to the package root. */
848
+ file: string;
849
+ /** The import specifier, or the argument text of a dynamic one. */
850
+ specifier: string;
851
+ reason: "dynamic-specifier" | "outside-allowlist" | "outside-package" | "private-package";
852
+ }
853
+ interface PublicSdkOnlyScan {
854
+ /** Every source file scanned, relative to the package root, in walk order. */
855
+ files: string[];
856
+ violations: PublicSdkOnlyViolation[];
857
+ /** `@bb/*` names in the package.json dependencies and devDependencies. */
858
+ privateDependencies: string[];
859
+ }
860
+ /**
861
+ * Scan a plugin package for imports outside the public SDK. `packageRoot`
862
+ * is the directory holding its package.json; every `.ts`/`.tsx`/`.js`
863
+ * file below it except `node_modules` and `dist` is read.
864
+ */
865
+ declare function scanPublicSdkOnly(packageRoot: string, options?: PublicSdkOnlyScanOptions): PublicSdkOnlyScan;
866
+
867
+ export { PluginContextStaleError, createFakePluginHost, createFakeSdk, scanPublicSdkOnly as experimental_scanPublicSdkOnly, makeThreadResponse };
868
+ export type { CreateFakePluginHostOptions, FakeAgentToolRecord, FakeCliRecord, FakeHttpRouteRecord, FakeLogEntry, FakeLogLevel, FakeMentionProviderRecord, FakePluginBehaviorDrivers, FakePluginHarness, FakePluginHost, FakePluginInspectionState, FakePluginLifecycleControls, FakePluginRegistrations, FakeRealtimeSignal, FakeScheduleRecord, FakeSdkCall, FakeSdkHarness, FakeSdkOverrides, FakeServiceRecord, PublicSdkOnlyScan, PublicSdkOnlyScanOptions, PublicSdkOnlyViolation };