@get-bb/plugin-sdk 0.4.3

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.
@@ -0,0 +1,1519 @@
1
+ // Portable type declarations for `@get-bb/plugin-sdk`. Unpublished BB
2
+ // workspace contracts are flattened; public subpaths may reuse the
3
+ // package root without requiring any other @bb/* package.
4
+ //
5
+ // Confused by the API, or need a symbol that isn't here? Clone the BB repo
6
+ // and read the real source: https://github.com/get-bb/bb
7
+
8
+ import * as react from 'react';
9
+ import { ComponentType, ReactNode } from 'react';
10
+ import { z } from 'zod';
11
+
12
+ /** A JSON-safe path segment reported by a Standard Schema validation issue. */
13
+ type PluginRpcIssuePathSegment = string | number;
14
+ /** Validator-neutral validation detail carried by an RPC error envelope. */
15
+ interface PluginRpcValidationIssue {
16
+ message: string;
17
+ path?: PluginRpcIssuePathSegment[];
18
+ }
19
+ /** Stable wire error categories for plugin RPC. */
20
+ type PluginRpcErrorCode = "invalid_json" | "invalid_input" | "handler_error" | "invalid_output" | "non_json_result" | "unknown_method";
21
+ /** Structured RPC failure returned as `{ ok: false, error }`. */
22
+ interface PluginRpcError {
23
+ code: PluginRpcErrorCode;
24
+ message: string;
25
+ issues?: PluginRpcValidationIssue[];
26
+ }
27
+ /**
28
+ * The validator-neutral subset of Standard Schema v1 used by plugin RPC.
29
+ * Zod 4 schemas implement this interface directly; other validators can do
30
+ * the same without becoming part of BB's public protocol.
31
+ */
32
+ interface StandardSchemaV1<Input = unknown, Output = Input> {
33
+ readonly "~standard": {
34
+ readonly version: 1;
35
+ readonly vendor: string;
36
+ readonly validate: (value: unknown) => StandardSchemaV1Result<Output> | Promise<StandardSchemaV1Result<Output>>;
37
+ readonly types?: {
38
+ readonly input: Input;
39
+ readonly output: Output;
40
+ };
41
+ };
42
+ }
43
+ type StandardSchemaV1Result<Output> = {
44
+ readonly value: Output;
45
+ readonly issues?: undefined;
46
+ } | {
47
+ readonly issues: readonly StandardSchemaV1Issue[];
48
+ };
49
+ interface StandardSchemaV1Issue {
50
+ readonly message: string;
51
+ readonly path?: PropertyKey | readonly (PropertyKey | {
52
+ readonly key: PropertyKey;
53
+ })[];
54
+ }
55
+ type StandardSchemaV1InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema["~standard"]["types"]>["input"];
56
+ type StandardSchemaV1InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema["~standard"]["types"]>["output"];
57
+ interface PluginRpcMethodContract<InputSchema extends StandardSchemaV1 = StandardSchemaV1, OutputSchema extends StandardSchemaV1 = StandardSchemaV1> {
58
+ readonly input: InputSchema;
59
+ readonly output: OutputSchema;
60
+ }
61
+ type PluginRpcContract = Readonly<Record<string, PluginRpcMethodContract>>;
62
+ type PluginRpcHandlers<Contract extends PluginRpcContract> = {
63
+ [Method in keyof Contract]: (input: StandardSchemaV1InferOutput<Contract[Method]["input"]>) => StandardSchemaV1InferInput<Contract[Method]["output"]> | Promise<StandardSchemaV1InferInput<Contract[Method]["output"]>>;
64
+ };
65
+ type PluginRpcCallInput<Method extends PluginRpcMethodContract> = StandardSchemaV1InferInput<Method["input"]>;
66
+ type PluginRpcCallArgs<Method extends PluginRpcMethodContract> = null extends PluginRpcCallInput<Method> ? [input?: PluginRpcCallInput<Method>] : [input: PluginRpcCallInput<Method>];
67
+ type PluginRpcResult<Method extends PluginRpcMethodContract> = StandardSchemaV1InferOutput<Method["output"]>;
68
+
69
+ declare const reasoningLevelSchema: z.ZodEnum<{
70
+ none: "none";
71
+ low: "low";
72
+ medium: "medium";
73
+ high: "high";
74
+ xhigh: "xhigh";
75
+ ultracode: "ultracode";
76
+ max: "max";
77
+ ultra: "ultra";
78
+ }>;
79
+ type ReasoningLevel = z.infer<typeof reasoningLevelSchema>;
80
+ declare const serviceTierSchema: z.ZodEnum<{
81
+ default: "default";
82
+ fast: "fast";
83
+ }>;
84
+ type ServiceTier = z.infer<typeof serviceTierSchema>;
85
+ declare const permissionModeSchema: z.ZodEnum<{
86
+ full: "full";
87
+ auto: "auto";
88
+ "accept-edits": "accept-edits";
89
+ }>;
90
+ type PermissionMode = z.infer<typeof permissionModeSchema>;
91
+ declare const promptInputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
92
+ visibility: z.ZodOptional<z.ZodEnum<{
93
+ "agent-only": "agent-only";
94
+ }>>;
95
+ type: z.ZodLiteral<"text">;
96
+ text: z.ZodString;
97
+ mentions: z.ZodDefault<z.ZodArray<z.ZodObject<{
98
+ start: z.ZodNumber;
99
+ end: z.ZodNumber;
100
+ resource: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodDiscriminatedUnion<[z.ZodObject<{
101
+ kind: z.ZodLiteral<"thread">;
102
+ threadId: z.ZodString;
103
+ projectId: z.ZodOptional<z.ZodString>;
104
+ label: z.ZodString;
105
+ }, z.core.$strip>, z.ZodObject<{
106
+ kind: z.ZodLiteral<"project">;
107
+ projectId: z.ZodString;
108
+ label: z.ZodString;
109
+ }, z.core.$strip>, z.ZodObject<{
110
+ kind: z.ZodLiteral<"section">;
111
+ sectionId: z.ZodString;
112
+ label: z.ZodString;
113
+ }, z.core.$strip>, z.ZodObject<{
114
+ kind: z.ZodLiteral<"path">;
115
+ source: z.ZodEnum<{
116
+ workspace: "workspace";
117
+ "thread-storage": "thread-storage";
118
+ }>;
119
+ entryKind: z.ZodEnum<{
120
+ file: "file";
121
+ directory: "directory";
122
+ }>;
123
+ path: z.ZodString;
124
+ label: z.ZodString;
125
+ }, z.core.$strip>, z.ZodObject<{
126
+ kind: z.ZodLiteral<"command">;
127
+ trigger: z.ZodEnum<{
128
+ "/": "/";
129
+ }>;
130
+ name: z.ZodString;
131
+ source: z.ZodEnum<{
132
+ command: "command";
133
+ skill: "skill";
134
+ }>;
135
+ origin: z.ZodEnum<{
136
+ user: "user";
137
+ project: "project";
138
+ builtin: "builtin";
139
+ }>;
140
+ label: z.ZodString;
141
+ argumentHint: z.ZodNullable<z.ZodString>;
142
+ }, z.core.$strip>, z.ZodObject<{
143
+ kind: z.ZodLiteral<"plugin">;
144
+ pluginId: z.ZodString;
145
+ icon: z.ZodOptional<z.ZodNullable<z.ZodString>>;
146
+ itemId: z.ZodString;
147
+ label: z.ZodString;
148
+ }, z.core.$strip>], "kind">>;
149
+ }, z.core.$strip>>>;
150
+ }, z.core.$strip>, z.ZodObject<{
151
+ visibility: z.ZodOptional<z.ZodEnum<{
152
+ "agent-only": "agent-only";
153
+ }>>;
154
+ type: z.ZodLiteral<"image">;
155
+ url: z.ZodString;
156
+ }, z.core.$strip>, z.ZodObject<{
157
+ visibility: z.ZodOptional<z.ZodEnum<{
158
+ "agent-only": "agent-only";
159
+ }>>;
160
+ type: z.ZodLiteral<"localImage">;
161
+ path: z.ZodString;
162
+ }, z.core.$strip>, z.ZodObject<{
163
+ visibility: z.ZodOptional<z.ZodEnum<{
164
+ "agent-only": "agent-only";
165
+ }>>;
166
+ type: z.ZodLiteral<"localFile">;
167
+ path: z.ZodString;
168
+ name: z.ZodOptional<z.ZodString>;
169
+ sizeBytes: z.ZodOptional<z.ZodNumber>;
170
+ mimeType: z.ZodOptional<z.ZodString>;
171
+ }, z.core.$strip>], "type">;
172
+ type PromptInput = z.infer<typeof promptInputSchema>;
173
+
174
+ declare const createThreadEnvironmentArgsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
175
+ type: z.ZodLiteral<"reuse">;
176
+ environmentId: z.ZodString;
177
+ }, z.core.$strip>, z.ZodObject<{
178
+ type: z.ZodLiteral<"host">;
179
+ hostId: z.ZodOptional<z.ZodString>;
180
+ workspace: z.ZodDiscriminatedUnion<[z.ZodObject<{
181
+ type: z.ZodLiteral<"unmanaged">;
182
+ path: z.ZodNullable<z.ZodString>;
183
+ branch: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
184
+ kind: z.ZodLiteral<"existing">;
185
+ name: z.ZodString;
186
+ }, z.core.$strict>, z.ZodObject<{
187
+ kind: z.ZodLiteral<"new">;
188
+ baseBranch: z.ZodString;
189
+ }, z.core.$strict>], "kind">>;
190
+ }, z.core.$strip>, z.ZodObject<{
191
+ type: z.ZodLiteral<"managed-worktree">;
192
+ baseBranch: z.ZodDiscriminatedUnion<[z.ZodObject<{
193
+ kind: z.ZodLiteral<"named">;
194
+ name: z.ZodString;
195
+ }, z.core.$strip>, z.ZodObject<{
196
+ kind: z.ZodLiteral<"default">;
197
+ }, z.core.$strip>], "kind">;
198
+ }, z.core.$strip>, z.ZodObject<{
199
+ type: z.ZodLiteral<"personal">;
200
+ }, z.core.$strip>], "type">;
201
+ }, z.core.$strip>, z.ZodObject<{
202
+ type: z.ZodLiteral<"project-default">;
203
+ }, z.core.$strip>], "type">;
204
+ type CreateThreadEnvironmentArgs = z.infer<typeof createThreadEnvironmentArgsSchema>;
205
+
206
+ declare const createExecutionInputSourcesSchema: z.ZodObject<{
207
+ providerId: z.ZodOptional<z.ZodEnum<{
208
+ explicit: "explicit";
209
+ "client-preference": "client-preference";
210
+ }>>;
211
+ model: z.ZodOptional<z.ZodEnum<{
212
+ explicit: "explicit";
213
+ "client-preference": "client-preference";
214
+ }>>;
215
+ serviceTier: z.ZodOptional<z.ZodEnum<{
216
+ explicit: "explicit";
217
+ "client-preference": "client-preference";
218
+ }>>;
219
+ reasoningLevel: z.ZodOptional<z.ZodEnum<{
220
+ explicit: "explicit";
221
+ "client-preference": "client-preference";
222
+ }>>;
223
+ permissionMode: z.ZodOptional<z.ZodEnum<{
224
+ explicit: "explicit";
225
+ "client-preference": "client-preference";
226
+ }>>;
227
+ }, z.core.$strict>;
228
+ type CreateExecutionInputSources = z.infer<typeof createExecutionInputSourcesSchema>;
229
+
230
+ /**
231
+ * A value that survives a JSON round trip without coercion or data loss.
232
+ *
233
+ * Host boundaries still validate values at runtime because TypeScript cannot
234
+ * exclude non-finite numbers and plugin bundles can bypass static types.
235
+ */
236
+ type JsonValue = string | number | boolean | null | JsonValue[] | {
237
+ [key: string]: JsonValue;
238
+ };
239
+
240
+ /**
241
+ * The `@get-bb/plugin-sdk/app` contract (plugin design §5.2) — pure types with no
242
+ * side effects. The BB app imports these to keep its real implementation in
243
+ * sync (`satisfies PluginSdkApp`). Plugin authors import the same shapes through
244
+ * `@get-bb/plugin-sdk/app`.
245
+ *
246
+ * Per-slot props are versioned contracts: additive-only within an SDK major.
247
+ */
248
+ /** Props passed to a `homepageSection` component. */
249
+ interface PluginHomepageSectionProps {
250
+ /** Project in view on the compose surface; null when none is selected. */
251
+ projectId: string | null;
252
+ }
253
+ /**
254
+ * Props passed to a `settingsSection` component.
255
+ *
256
+ * Deliberately empty in V1; versioned additive like the other slot props.
257
+ */
258
+ interface PluginSettingsSectionProps {
259
+ }
260
+ /** Props passed to a `navPanel` component (it owns its whole route). */
261
+ interface PluginNavPanelProps {
262
+ /**
263
+ * The route remainder after the panel root, "" at the root. The panel's
264
+ * route is `/plugins/<pluginId>/<path>/*`, so a deep link like
265
+ * `/plugins/notes/notes/work/ideas.md` renders the panel with
266
+ * `subPath: "work/ideas.md"`. Navigate within the panel via
267
+ * `useBbNavigate().toPluginPanel(path, { subPath })` — browser
268
+ * back/forward then walks panel-internal history.
269
+ */
270
+ subPath: string;
271
+ }
272
+ /**
273
+ * Props passed to a panel tab opened by a `threadPanelAction`.
274
+ *
275
+ * This slot is rendered only for an existing thread. Use
276
+ * `experimental_newThreadPanelAction` for the root New thread screen.
277
+ */
278
+ interface PluginThreadPanelProps {
279
+ threadId: string;
280
+ /**
281
+ * The JSON value the action's `openPanel` call passed (round-tripped
282
+ * through persistence, so the tab restores across reloads); null when the
283
+ * action opened the panel without params.
284
+ */
285
+ params: JsonValue | null;
286
+ }
287
+ /** Props passed to a panel tab opened by `experimental_newThreadPanelAction`. */
288
+ interface PluginNewThreadPanelProps {
289
+ /** Project selected in the root composer; null in projectless compose. */
290
+ projectId: string | null;
291
+ /**
292
+ * The JSON value the action's `openPanel` call passed (round-tripped
293
+ * through persistence, so the tab restores across reloads); null when the
294
+ * action opened the panel without params.
295
+ */
296
+ params: JsonValue | null;
297
+ }
298
+ interface PluginPendingInteractionView {
299
+ id: string;
300
+ threadId: string;
301
+ title: string;
302
+ payload: JsonValue;
303
+ createdAt: number;
304
+ expiresAt: number | null;
305
+ }
306
+ interface PluginPendingInteractionProps {
307
+ interaction: PluginPendingInteractionView;
308
+ submit(value: JsonValue): Promise<void>;
309
+ cancel(): Promise<void>;
310
+ }
311
+ /**
312
+ * Props for a `sidebarFooterAction` — host-rendered (no plugin component).
313
+ * Deliberately empty; the registration's `run` carries the behavior.
314
+ */
315
+ interface PluginSidebarFooterActionProps {
316
+ }
317
+ /**
318
+ * Props passed to an `experimental_threadList` component — the sidebar's
319
+ * scrolling thread area, replaced wholesale by one plugin.
320
+ */
321
+ interface PluginThreadListProps {
322
+ /** The thread the route currently shows; null on non-thread routes. */
323
+ activeThreadId: string | null;
324
+ /** The project the route currently shows; null when none is selected. */
325
+ activeProjectId: string | null;
326
+ /** True on phone-width viewports and coarse pointers. */
327
+ isCompactViewport: boolean;
328
+ /**
329
+ * Call after the user opens a thread. It closes the mobile sidebar drawer,
330
+ * and it clears the host search field on every viewport. Always call it, or
331
+ * the sidebar stays in search mode after the thread opens.
332
+ */
333
+ onNavigate: () => void;
334
+ /**
335
+ * The host search field's current text, or "" when the field is closed.
336
+ * The host owns that field, so a plugin list filters by this rather than
337
+ * shipping a second search box.
338
+ */
339
+ searchQuery: string;
340
+ }
341
+ /**
342
+ * Props passed to an `experimental_threadHeaderAction` component, rendered in
343
+ * the thread header's action row.
344
+ */
345
+ interface PluginThreadHeaderActionProps {
346
+ /**
347
+ * The thread this header belongs to. Never null: the slot is not rendered
348
+ * on the compose screen or other non-thread routes. A split layout renders
349
+ * one header per pane, so the component mounts once per visible thread,
350
+ * each with its own id — keep per-thread state in the component, never in a
351
+ * module-level singleton.
352
+ */
353
+ threadId: string;
354
+ projectId: string;
355
+ /**
356
+ * True on phone-width viewports and coarse pointers. Collapse to an
357
+ * icon-sized control when it is true — the row is short.
358
+ */
359
+ isCompactViewport: boolean;
360
+ }
361
+ /**
362
+ * Where a file being opened by a `fileOpener` lives. `path` semantics follow
363
+ * the source: workspace paths are relative to the environment's worktree,
364
+ * thread-storage paths are relative to the thread's storage root, host paths
365
+ * are absolute on the thread's host.
366
+ */
367
+ interface PluginFileOpenerSource {
368
+ kind: "workspace" | "host" | "thread-storage";
369
+ threadId: string | null;
370
+ environmentId: string | null;
371
+ projectId: string | null;
372
+ }
373
+ /** Props passed to a `fileOpener` component (rendered as a panel file tab). */
374
+ interface PluginFileOpenerProps {
375
+ path: string;
376
+ source: PluginFileOpenerSource;
377
+ }
378
+ /**
379
+ * Message context passed to a `messageDirective` component — the assistant
380
+ * (or nested agent) message that contained the directive.
381
+ */
382
+ interface PluginMessageDirectiveMessage {
383
+ id: string;
384
+ threadId: string;
385
+ turnId: string | null;
386
+ projectId: string | null;
387
+ }
388
+ /**
389
+ * Open a worktree-relative file in the host's workspace file viewer. Returns
390
+ * true when the host accepted the path; false when the path is invalid or the
391
+ * viewer declined it.
392
+ */
393
+ type PluginMessageDirectiveOpenWorkspaceFile = (path: string) => boolean;
394
+ /**
395
+ * Props passed to a `messageDirective` component. Attributes are untrusted
396
+ * strings parsed from the directive; the plugin validates its own fields.
397
+ */
398
+ interface PluginMessageDirectiveProps {
399
+ /** Parsed, untrusted directive attributes (e.g. `{ file: "demo.html" }`). */
400
+ attributes: Readonly<Record<string, string>>;
401
+ /** Original directive source text (useful for diagnostics / crash fallback). */
402
+ source: string;
403
+ message: PluginMessageDirectiveMessage;
404
+ /**
405
+ * Opens a worktree-relative file in the host's workspace file viewer. Null
406
+ * when the message surface has no workspace viewer available.
407
+ */
408
+ openWorkspaceFile: PluginMessageDirectiveOpenWorkspaceFile | null;
409
+ }
410
+ interface PluginHomepageSectionRegistration {
411
+ /** Unique within the plugin; letters, digits, `-`, `_`. */
412
+ id: string;
413
+ title: string;
414
+ component: ComponentType<PluginHomepageSectionProps>;
415
+ }
416
+ interface PluginSettingsSectionRegistration {
417
+ /** Unique within the plugin; letters, digits, `-`, `_`. */
418
+ id: string;
419
+ /** Optional host-rendered section heading. */
420
+ title?: string;
421
+ /**
422
+ * Optional one-line host-rendered subheading under `title`, in the built-in
423
+ * SettingsSection idiom (ignored when `title` is absent).
424
+ */
425
+ description?: string;
426
+ component: ComponentType<PluginSettingsSectionProps>;
427
+ }
428
+ interface PluginNavPanelRegistration {
429
+ /** Unique within the plugin; letters, digits, `-`, `_`. */
430
+ id: string;
431
+ title: string;
432
+ /** Icon hint (BB icon name); unknown names fall back to a generic icon. */
433
+ icon: string;
434
+ /** URL segment under `/plugins/<pluginId>/`; letters, digits, `-`, `_`. */
435
+ path: string;
436
+ component: ComponentType<PluginNavPanelProps>;
437
+ /**
438
+ * Optional presentational component rendered at the trailing edge of this
439
+ * panel's sidebar row. It receives no props so it can own a narrow live
440
+ * value through the ordinary SDK hooks without coupling that state to the
441
+ * host sidebar. The host does not mount it on compact viewports and clips it
442
+ * to a small, single-line box on wider viewports. It shares the trailing
443
+ * action column, fading out for the host's options button on hover or focus;
444
+ * do not render controls or rely on unbounded content here.
445
+ *
446
+ * Experimental: see docs/api_to_audit.md.
447
+ */
448
+ experimental_sidebarAccessory?: ComponentType;
449
+ /**
450
+ * Optional component rendered on the right side of the shared title bar
451
+ * (e.g. a sync button or a count). Contained separately from the body: a
452
+ * throwing headerContent is hidden without breaking the title bar.
453
+ */
454
+ headerContent?: ComponentType<PluginNavPanelProps>;
455
+ }
456
+ /**
457
+ * Context handed to a `threadPanelAction`'s `run`.
458
+ *
459
+ * The action is thread-only and is never offered on the root New thread
460
+ * screen, so `threadId` is always present.
461
+ */
462
+ interface PluginThreadPanelActionContext {
463
+ /** The thread whose panel launcher invoked the action. */
464
+ threadId: string;
465
+ /**
466
+ * Open a tab in the thread's side panel rendering this action's
467
+ * `component`. `title` labels the tab (default: the action's `title`);
468
+ * `params` must be JSON-serializable — it is persisted with the tab and
469
+ * reaches the component as its `params` prop. Opening with params
470
+ * identical to an already-open tab of this action focuses that tab
471
+ * (updating its title) instead of duplicating it. May be called more than
472
+ * once (different params ⇒ multiple tabs) or not at all.
473
+ */
474
+ openPanel(options?: {
475
+ title?: string;
476
+ params?: JsonValue;
477
+ }): void;
478
+ }
479
+ interface PluginThreadPanelActionRegistration {
480
+ /** Unique within the plugin; letters, digits, `-`, `_`. */
481
+ id: string;
482
+ /** Label of the action row in the panel's new-tab launcher. */
483
+ title: string;
484
+ /**
485
+ * Icon hint (BB icon name) used when the plugin ships no logo; the
486
+ * launcher row and opened tabs prefer the plugin's logo.
487
+ */
488
+ icon?: string;
489
+ /** Rendered inside every panel tab this action opens. */
490
+ component: ComponentType<PluginThreadPanelProps>;
491
+ /**
492
+ * How the host frames the tab content. "padded" (default) wraps the
493
+ * component in the panel's scroll container with standard padding —
494
+ * right for document-like content. "flush" gives the component the full
495
+ * tab area (no padding, definite height, no host scrolling) — right for
496
+ * app-like content that manages its own layout, such as
497
+ * `ThreadChat`.
498
+ */
499
+ layout?: "padded" | "flush";
500
+ /**
501
+ * Runs when the user activates the action: call your RPC methods, show a
502
+ * toast, and/or open panel tabs via `context.openPanel`. Omitted =
503
+ * immediately open a panel tab with defaults. Errors (sync or async) are
504
+ * contained and logged; they never break the launcher.
505
+ */
506
+ run?(context: PluginThreadPanelActionContext): void | Promise<void>;
507
+ }
508
+ /** Context handed to an `experimental_newThreadPanelAction`'s `run`. */
509
+ interface PluginNewThreadPanelActionContext {
510
+ /** Project selected in the root composer; null in projectless compose. */
511
+ projectId: string | null;
512
+ /**
513
+ * Open a tab in the root New thread screen's side panel rendering this
514
+ * action's `component`. The title, params, deduplication, and error
515
+ * semantics match `threadPanelAction`.
516
+ */
517
+ openPanel(options?: {
518
+ title?: string;
519
+ params?: JsonValue;
520
+ }): void;
521
+ }
522
+ /** Registration for the root New thread screen's panel Actions list. */
523
+ interface PluginNewThreadPanelActionRegistration {
524
+ /** Unique within this slot for the plugin; letters, digits, `-`, `_`. */
525
+ id: string;
526
+ /** Label of the action row in the panel's new-tab launcher. */
527
+ title: string;
528
+ /** Icon hint (BB icon name) used when the plugin ships no logo. */
529
+ icon?: string;
530
+ /** Rendered inside every panel tab this action opens. */
531
+ component: ComponentType<PluginNewThreadPanelProps>;
532
+ /** Host framing; matches `threadPanelAction`. */
533
+ layout?: "padded" | "flush";
534
+ /**
535
+ * Runs when the user activates the action. Omitted = immediately open a
536
+ * panel tab with defaults. Errors are contained and logged.
537
+ */
538
+ run?(context: PluginNewThreadPanelActionContext): void | Promise<void>;
539
+ }
540
+ interface PluginPendingInteractionRegistration {
541
+ /** Matches `rendererId` passed to `bb.ui.requestInput`. */
542
+ id: string;
543
+ component: ComponentType<PluginPendingInteractionProps>;
544
+ }
545
+ /** Context handed to a `sidebarFooterAction`'s `run`. */
546
+ interface PluginSidebarFooterActionContext {
547
+ /**
548
+ * Navigate to this plugin's detail page in Tools, where declarative settings
549
+ * and `settingsSection` slots render.
550
+ */
551
+ openSettings(): void;
552
+ }
553
+ /**
554
+ * An icon button in the app sidebar footer (next to Settings / bug report).
555
+ * Host-rendered for consistent chrome — plugins supply icon, label, and
556
+ * `run` behavior only.
557
+ */
558
+ interface PluginSidebarFooterActionRegistration {
559
+ /** Unique within the plugin; letters, digits, `-`, `_`. */
560
+ id: string;
561
+ /** Tooltip and accessible label for the icon button. */
562
+ title: string;
563
+ /** Icon hint (BB icon name); unknown names fall back to a generic icon. */
564
+ icon: string;
565
+ /**
566
+ * Runs when the user activates the action (e.g. call `openSettings()`,
567
+ * open a panel via other surfaces, toast). Errors (sync or async) are
568
+ * contained and logged; they never break the sidebar.
569
+ */
570
+ run(context: PluginSidebarFooterActionContext): void | Promise<void>;
571
+ }
572
+ /**
573
+ * The one status bb would paint for a thread, already resolved through the
574
+ * host's precedence (attention before work; plan and goal before the generic
575
+ * spinner). Draw your own glyph for it — the SDK ships no status component.
576
+ *
577
+ * Treat an unrecognized value as "none": bb adds kinds over time, and an
578
+ * older plugin must degrade to drawing nothing rather than throwing.
579
+ *
580
+ * "draft" and "working-draft" are never reported here: an unsubmitted composer
581
+ * draft is per-client state the host reads per row, which an array-wide view
582
+ * cannot. A thread holding a draft reports whatever it would report without
583
+ * one.
584
+ */
585
+ type PluginSidebarThreadIndicator = "unread-error" | "waiting-for-input" | "working-draft" | "workflow" | "background-agent" | "background-command" | "plan-mode" | "goal" | "runtime" | "draft" | "unread-success" | "none";
586
+ /**
587
+ * How a thread's environment presents its workspace: a worktree bb manages,
588
+ * a worktree the user manages, or anything else (a plain checkout).
589
+ */
590
+ type PluginSidebarWorkspaceKind = "managed-worktree" | "unmanaged-worktree" | "other";
591
+ /** Live work counts on a thread. All zero means nothing is running. */
592
+ interface PluginSidebarThreadActivity {
593
+ workflows: number;
594
+ backgroundAgents: number;
595
+ backgroundCommands: number;
596
+ planMode: number;
597
+ goals: number;
598
+ }
599
+ /**
600
+ * One thread in the sidebar's live view.
601
+ *
602
+ * A deliberate copy of the fields a sidebar needs — not a re-export of the
603
+ * host's internal thread row type, which changes whenever the app needs a
604
+ * field. Timestamps are epoch milliseconds.
605
+ */
606
+ interface PluginSidebarThread {
607
+ id: string;
608
+ projectId: string;
609
+ /** Null while a thread is still unnamed; pair with `titleFallback`. */
610
+ title: string | null;
611
+ titleFallback: string | null;
612
+ /** The thread this one was forked from or spawned under; null at the root. */
613
+ parentThreadId: string | null;
614
+ sectionId: string | null;
615
+ /** How this thread came to exist under its parent; null for root threads. */
616
+ originKind: "fork" | null;
617
+ /** The plugin that spawned it, or null for non-plugin origins. */
618
+ originPluginId: string | null;
619
+ /** The agent provider this thread runs on, e.g. "codex", "claude-code". */
620
+ providerId: string;
621
+ /** The agent is blocked on the user: an approval or a question. */
622
+ hasPendingInteraction: boolean;
623
+ activity: PluginSidebarThreadActivity;
624
+ indicator: PluginSidebarThreadIndicator;
625
+ /**
626
+ * The host's accessible label for `indicator`, e.g. "Thread needs user
627
+ * input"; null when the indicator is "none". Use it for `aria-label` so
628
+ * screen-reader text stays consistent across sidebars.
629
+ */
630
+ indicatorLabel: string | null;
631
+ isUnread: boolean;
632
+ isPinned: boolean;
633
+ isArchived: boolean;
634
+ environment: {
635
+ id: string | null;
636
+ name: string | null;
637
+ branchName: string | null;
638
+ workspaceDisplayKind: PluginSidebarWorkspaceKind;
639
+ } | null;
640
+ /**
641
+ * The machine this thread's work runs on, with the name resolved for you.
642
+ * Null when the thread has no environment yet, or when its host is not in
643
+ * the known-hosts list. Useful where a thread has no branch to show — a
644
+ * personal-project thread has a machine but no worktree.
645
+ */
646
+ host: {
647
+ id: string;
648
+ name: string;
649
+ } | null;
650
+ createdAt: number;
651
+ updatedAt: number;
652
+ lastReadAt: number | null;
653
+ latestAttentionAt: number;
654
+ }
655
+ /**
656
+ * The pull request for a thread's branch, narrowed to what a sidebar row
657
+ * needs. `attention` is bb's rolled-up "does this need you" signal, so a row
658
+ * can colour a badge without reading checks, review, and mergeability itself.
659
+ */
660
+ interface PluginSidebarPullRequest {
661
+ number: number;
662
+ title: string;
663
+ url: string;
664
+ state: "draft" | "open" | "merged" | "closed";
665
+ attention: "checks_failed" | "checks_pending" | "changes_requested" | "review_requested" | "conflicts" | "blocked" | "draft" | "ready_to_merge" | "merged" | "closed" | "none";
666
+ }
667
+ interface PluginSidebarThreadPullRequestState {
668
+ /** True while the first lookup for this thread's environment is in flight. */
669
+ isLoading: boolean;
670
+ /**
671
+ * The pull request, or null when the branch has none, the thread has no
672
+ * environment, or the lookup could not run (a git-host hiccup). A row should
673
+ * treat null as "nothing to show", never as an error.
674
+ */
675
+ pullRequest: PluginSidebarPullRequest | null;
676
+ }
677
+ /** One project in the sidebar's live view. */
678
+ interface PluginSidebarProject {
679
+ id: string;
680
+ name: string;
681
+ /** True for the implicit personal project. */
682
+ isPersonal: boolean;
683
+ }
684
+ interface PluginSidebarThreadsState {
685
+ status: "loading" | "ready" | "error";
686
+ threads: readonly PluginSidebarThread[];
687
+ projects: readonly PluginSidebarProject[];
688
+ }
689
+ /**
690
+ * Act on threads from a plugin surface. Every method routes to the host's own
691
+ * flow, so optimistic updates, toasts, dialogs, pane closing, and route repair
692
+ * behave exactly as they do in the built-in sidebar. Unknown thread ids are
693
+ * ignored by `open` and rejected by the rest.
694
+ */
695
+ interface PluginSidebarThreadActions {
696
+ /**
697
+ * Navigate to a thread. `split: true` applies bb's split placement rules —
698
+ * a right split by default, focus when the thread is already open, replace
699
+ * at the pane cap — and falls back to plain navigation where splits are off.
700
+ */
701
+ open(threadId: string, options?: {
702
+ split?: boolean;
703
+ }): void;
704
+ /**
705
+ * Go to the new-thread screen. Passing `projectId` also makes that project
706
+ * the composer's selection, so the thread is created where you asked.
707
+ */
708
+ openNewThread(options?: {
709
+ projectId?: string;
710
+ focusPrompt?: boolean;
711
+ }): void;
712
+ setPinned(threadId: string, pinned: boolean): Promise<void>;
713
+ setRead(threadId: string, read: boolean): Promise<void>;
714
+ /** Silent rename — no dialog. For inline editing in your own row. */
715
+ rename(threadId: string, title: string): Promise<void>;
716
+ /** Archives the thread AND its children, closing any panes showing them. */
717
+ archive(threadId: string): void;
718
+ /**
719
+ * Opens bb's delete confirmation, which counts child threads first. Deletion
720
+ * is destructive and recursive, so the host owns the confirmation: there is
721
+ * deliberately no silent `delete`.
722
+ */
723
+ requestDelete(threadId: string): void;
724
+ }
725
+ /**
726
+ * Render a plugin component in the thread header's action row.
727
+ *
728
+ * The frontend sibling of the backend `bb.ui.registerThreadAction`, which
729
+ * renders a host-owned button and runs server-side. Use that one for "do a
730
+ * thing"; use this one when the control must draw live state.
731
+ *
732
+ * The host places it at the left end of the action row, before the workspace
733
+ * button, git actions, the panel toggle, maximize, and close. That row is a
734
+ * 48px chrome row with 28px controls: render one inline control that fits, and
735
+ * put anything taller in a portalled popover.
736
+ */
737
+ interface PluginThreadHeaderActionRegistration {
738
+ /** Unique within the plugin; letters, digits, `-`, `_`. */
739
+ id: string;
740
+ /**
741
+ * Names the region the host wraps around your component (a labelled group).
742
+ * It does NOT label your control: an icon-only button still needs its own
743
+ * accessible name.
744
+ */
745
+ title: string;
746
+ component: ComponentType<PluginThreadHeaderActionProps>;
747
+ }
748
+ /** One pane's place in the split layout, as fractions of the split area. */
749
+ interface PluginSidebarSplitPane {
750
+ paneId: string;
751
+ rect: {
752
+ x: number;
753
+ y: number;
754
+ width: number;
755
+ height: number;
756
+ };
757
+ /** This pane holds the thread the row represents. */
758
+ isMe: boolean;
759
+ isFocused: boolean;
760
+ }
761
+ /**
762
+ * Drag-to-split support for one row, plus where that thread currently sits in
763
+ * the split layout.
764
+ */
765
+ interface PluginSidebarThreadSplit {
766
+ /**
767
+ * Spread onto the row's interactive element. Carries the pointer handler
768
+ * that starts a split drag; empty when splits are unavailable, so spreading
769
+ * it is always safe.
770
+ *
771
+ * The host owns every rule: the gesture engages only once the pointer leaves
772
+ * the sidebar toward the main area (so a list with its own drag-to-reorder
773
+ * keeps working), an edge drop splits, a center drop replaces, an
774
+ * already-open thread focuses its pane, and the pane cap coerces a split
775
+ * into a replace.
776
+ */
777
+ splitProps: {
778
+ onPointerDown?: (event: react.PointerEvent<HTMLElement>) => void;
779
+ };
780
+ /**
781
+ * False on compact viewports, when the user disabled splits, and for an
782
+ * unknown thread id. Gate any "open in split" affordance you draw on it.
783
+ */
784
+ isAvailable: boolean;
785
+ /**
786
+ * Where this thread sits in the split layout, or null when it is not open in
787
+ * one (including single-pane layouts). Draw a mini-map, a tint, or nothing.
788
+ */
789
+ layout: {
790
+ panes: readonly PluginSidebarSplitPane[];
791
+ } | null;
792
+ }
793
+ /**
794
+ * Replace the sidebar's thread list with a plugin component.
795
+ *
796
+ * Unlike every other slot, this one is EXCLUSIVE: two lists cannot share one
797
+ * scroll area. The built-in list stays the default; the user picks a provider
798
+ * in Settings → Appearance, stored per client. A provider that is uninstalled,
799
+ * disabled, or crashing falls back to the built-in list rather than leaving
800
+ * the user with no sidebar.
801
+ *
802
+ * The plugin gets the scrolling list and nothing else. The New-thread button,
803
+ * the search field, the plugin nav rows, and the footer stay host-rendered in
804
+ * every sidebar — they are shared surfaces (other plugins live in two of
805
+ * them), and a replaced list must not be able to remove them.
806
+ */
807
+ interface PluginThreadListRegistration {
808
+ /** Unique within the plugin; letters, digits, `-`, `_`. */
809
+ id: string;
810
+ /** Label in the Settings → Appearance → Sidebar picker. */
811
+ title: string;
812
+ /** Optional one-line description under the title in that picker. */
813
+ description?: string;
814
+ component: ComponentType<PluginThreadListProps>;
815
+ }
816
+ /**
817
+ * Register this plugin as a viewer/editor for file extensions. The user
818
+ * picks (and can set as default) an opener per extension via the file tab's
819
+ * "Open with" menu; matching files opened in the panel then render
820
+ * `component` in a plugin tab instead of the built-in preview. Applies to
821
+ * working-tree, host, and thread-storage files — never to git-ref snapshots
822
+ * (diff views always use the built-in preview). The built-in preview stays
823
+ * one menu click away, and a missing/disabled opener falls back to it.
824
+ */
825
+ interface PluginFileOpenerRegistration {
826
+ /** Unique within the plugin; letters, digits, `-`, `_`. */
827
+ id: string;
828
+ /** Label in the "Open with" menu (e.g. "Notes editor"). */
829
+ title: string;
830
+ /** Lowercase extensions without the dot (e.g. ["md", "mdx"]). */
831
+ extensions: readonly string[];
832
+ component: ComponentType<PluginFileOpenerProps>;
833
+ }
834
+ /**
835
+ * Register a leaf message directive rendered inside assistant (and nested
836
+ * agent) message Markdown. `id` is the directive name: `inline-vis` matches
837
+ * `::inline-vis{file="demo.html"}`.
838
+ */
839
+ interface PluginMessageDirectiveRegistration {
840
+ /**
841
+ * The directive name. Lowercase kebab-case beginning with a letter.
842
+ */
843
+ id: string;
844
+ component: ComponentType<PluginMessageDirectiveProps>;
845
+ }
846
+ /**
847
+ * A narrow, stable reference to one rendered chat message — NOT an internal
848
+ * timeline row. `sourceSeqEnd` is the last source event sequence the message
849
+ * covers, the anchor the server accepts for provider-history forks.
850
+ */
851
+ interface ThreadChatMessageReference {
852
+ id: string;
853
+ threadId: string;
854
+ role: "user" | "assistant";
855
+ /** Visible text of the message. */
856
+ text: string;
857
+ sourceSeqEnd: number;
858
+ }
859
+ interface PluginMessageActionThreadPanelOptions {
860
+ /** A `threadPanelAction` id registered by this same plugin. */
861
+ actionId: string;
862
+ title?: string;
863
+ params?: JsonValue;
864
+ }
865
+ /** Context handed to a `messageAction`'s `run`. */
866
+ interface PluginMessageActionContext {
867
+ /** The thread whose timeline surfaced the action. */
868
+ threadId: string;
869
+ message: ThreadChatMessageReference;
870
+ /**
871
+ * Present only when the action was invoked from the text-selection menu;
872
+ * the exact text the user highlighted inside `message`.
873
+ */
874
+ selectedText?: string;
875
+ /**
876
+ * Open one of this plugin's `threadPanelAction` components in the current
877
+ * thread's side panel — the registration-callback equivalent of
878
+ * `useBbNavigate().openThreadPanel`. Returns true when the host
879
+ * accepted (the action id exists and the surface has a panel); false
880
+ * otherwise.
881
+ */
882
+ openPanel(options: PluginMessageActionThreadPanelOptions): boolean;
883
+ }
884
+ /**
885
+ * An action on chat messages: an icon button in the per-message action bar
886
+ * (user and assistant messages) and an entry in the assistant-message
887
+ * text-selection menu. Host-rendered chrome — the plugin supplies title,
888
+ * icon hint, and `run` behavior only.
889
+ */
890
+ interface PluginMessageActionRegistration {
891
+ /** Unique within the plugin; letters, digits, `-`, `_`. */
892
+ id: string;
893
+ /** Tooltip / menu label for the action. */
894
+ title: string;
895
+ /** Icon hint (BB icon name); unknown names fall back to a generic icon. */
896
+ icon?: string;
897
+ /**
898
+ * Runs when the user activates the action. Errors (sync or async) are
899
+ * contained and logged; they never break the timeline.
900
+ */
901
+ run(context: PluginMessageActionContext): void | Promise<void>;
902
+ }
903
+ interface PluginAppSlots {
904
+ homepageSection(registration: PluginHomepageSectionRegistration): void;
905
+ settingsSection(registration: PluginSettingsSectionRegistration): void;
906
+ navPanel(registration: PluginNavPanelRegistration): void;
907
+ /**
908
+ * Add an action to an existing thread's panel launcher. This slot is
909
+ * thread-only; use `experimental_newThreadPanelAction` for root compose.
910
+ */
911
+ threadPanelAction(registration: PluginThreadPanelActionRegistration): void;
912
+ /**
913
+ * Add an action to the root New thread screen's panel launcher (see
914
+ * {@link PluginNewThreadPanelActionRegistration}). Experimental: see
915
+ * docs/api_to_audit.md.
916
+ */
917
+ experimental_newThreadPanelAction(registration: PluginNewThreadPanelActionRegistration): void;
918
+ pendingInteraction(registration: PluginPendingInteractionRegistration): void;
919
+ sidebarFooterAction(registration: PluginSidebarFooterActionRegistration): void;
920
+ /**
921
+ * Replace the sidebar's thread list (see
922
+ * {@link PluginThreadListRegistration}). Experimental: see
923
+ * docs/api_to_audit.md for what to audit before the prefix drops.
924
+ */
925
+ experimental_threadList(registration: PluginThreadListRegistration): void;
926
+ /**
927
+ * Render a component in the thread header's action row (see
928
+ * {@link PluginThreadHeaderActionRegistration}). Experimental: see
929
+ * docs/api_to_audit.md.
930
+ */
931
+ experimental_threadHeaderAction(registration: PluginThreadHeaderActionRegistration): void;
932
+ fileOpener(registration: PluginFileOpenerRegistration): void;
933
+ messageDirective(registration: PluginMessageDirectiveRegistration): void;
934
+ messageAction(registration: PluginMessageActionRegistration): void;
935
+ }
936
+ interface PluginAppComposer {
937
+ customize(registration: ComposerCustomization): void;
938
+ }
939
+ /** Stable lifecycle values for one content-script instance in one bb client. */
940
+ interface PluginContentScriptContext {
941
+ /** The id of the plugin that owns this script. */
942
+ readonly pluginId: string;
943
+ /** Monotonic per-client generation, starting at 1. */
944
+ readonly generation: number;
945
+ /** Aborted before cleanup begins on replacement, deactivation, or teardown. */
946
+ readonly signal: AbortSignal;
947
+ /**
948
+ * Persistently decorate any thread row for this plugin generation.
949
+ *
950
+ * The status is owned by the frontend generation and therefore survives
951
+ * route changes. Passing `null` clears the plugin's status for that thread.
952
+ * The host clears every remaining status when the frontend generation
953
+ * deactivates.
954
+ *
955
+ * Optional so bundles can feature-detect support while this experimental
956
+ * surface rolls out across 0.x clients.
957
+ */
958
+ readonly experimental_setThreadRowStatus?: (threadId: string, status: PluginComposerThreadRowStatus | null) => void;
959
+ }
960
+ /** Cleanup returned by a frontend content script. */
961
+ type PluginContentScriptDisposer = () => void | Promise<void>;
962
+ /**
963
+ * Trusted same-origin JavaScript/TypeScript mounted once per active frontend
964
+ * generation in each bb app window or browser tab.
965
+ */
966
+ interface PluginContentScriptRegistration {
967
+ /** Unique within the plugin; letters, digits, `-`, `_`. */
968
+ id: string;
969
+ /**
970
+ * Install behavior into the bb app shell. The host awaits a returned
971
+ * promise, contains failures, and calls the returned disposer exactly once.
972
+ */
973
+ mount(context: PluginContentScriptContext): void | PluginContentScriptDisposer | Promise<void | PluginContentScriptDisposer>;
974
+ }
975
+ /** Lifecycle surface for trusted frontend content scripts. */
976
+ interface PluginAppContentScripts {
977
+ register(registration: PluginContentScriptRegistration): void;
978
+ }
979
+ interface PluginAppBuilder {
980
+ slots: PluginAppSlots;
981
+ composer: PluginAppComposer;
982
+ contentScripts: PluginAppContentScripts;
983
+ }
984
+ type PluginAppSetup = (app: PluginAppBuilder) => void;
985
+ /**
986
+ * The opaque product of `definePluginApp` — a plugin's `app.tsx` default
987
+ * export. The host re-runs `setup` against a fresh collector on every
988
+ * (re)interpretation, replacing that plugin's registrations wholesale.
989
+ */
990
+ interface PluginAppDefinition {
991
+ /** Brand the host checks before interpreting a bundle's default export. */
992
+ readonly __bbPluginApp: true;
993
+ readonly setup: PluginAppSetup;
994
+ }
995
+ interface PluginRpcClient<Contract extends PluginRpcContract = PluginRpcContract> {
996
+ /**
997
+ * Invoke one of the plugin's `bb.rpc` methods (POST
998
+ * /api/v1/plugins/&lt;id&gt;/rpc/&lt;method&gt;). Resolves with the method's
999
+ * inferred output; rejects with an `Error` carrying the server's message,
1000
+ * stable `code`, and validation `issues` when present.
1001
+ */
1002
+ call<Method extends Extract<keyof Contract, string>>(method: Method, ...args: PluginRpcCallArgs<Contract[Method]>): Promise<PluginRpcResult<Contract[Method]>>;
1003
+ }
1004
+ interface PluginSettingsState {
1005
+ /**
1006
+ * Effective non-secret setting values (secret settings are excluded —
1007
+ * read them server-side). Undefined while loading or unavailable.
1008
+ */
1009
+ values: Record<string, string | boolean> | undefined;
1010
+ isLoading: boolean;
1011
+ }
1012
+ /** State of the app's shared realtime connection to the bb server. */
1013
+ type PluginRealtimeConnectionState = "connecting" | "connected" | "reconnecting";
1014
+ /** Where `useComposer()` writes. */
1015
+ type PluginComposerScope = {
1016
+ kind: "thread";
1017
+ threadId: string;
1018
+ } | {
1019
+ kind: "queued-message";
1020
+ threadId: string;
1021
+ queuedMessageId: string;
1022
+ } | {
1023
+ kind: "side-chat";
1024
+ projectId: string;
1025
+ parentThreadId: string;
1026
+ tabId: string;
1027
+ childThreadId: string | null;
1028
+ } | {
1029
+ kind: "new-thread";
1030
+ /** Root compose's effective selected project; null only while unresolved. */
1031
+ projectId: string | null;
1032
+ };
1033
+ /** One plugin-owned composer customization registration. */
1034
+ interface ComposerCustomization {
1035
+ /** Unique within the plugin; letters, digits, `-`, `_`. */
1036
+ id: string;
1037
+ /** Composer kinds where this customization is active; omit for all kinds. */
1038
+ scopes?: readonly PluginComposerScope["kind"][];
1039
+ actions?: readonly {
1040
+ id: string;
1041
+ component: ComponentType;
1042
+ }[];
1043
+ banners?: readonly {
1044
+ id: string;
1045
+ /** Host chrome around the banner. Defaults to `"card"`. */
1046
+ chrome?: "card" | "bare";
1047
+ component: ComponentType;
1048
+ }[];
1049
+ plusMenu?: readonly ComposerPlusMenuItem[];
1050
+ richText?: ComposerRichTextSpec;
1051
+ }
1052
+ /** Host-rendered menu row in the composer's `+` menu. */
1053
+ interface ComposerPlusMenuItem {
1054
+ id: string;
1055
+ label: string;
1056
+ /** BB icon name; unknown names fall back to the generic plugin icon. */
1057
+ icon?: string;
1058
+ /** Accessible description for the host-rendered row. */
1059
+ description?: string;
1060
+ disabled?: boolean | ((view: ComposerView) => boolean);
1061
+ run(context: {
1062
+ composer: PluginComposerApi;
1063
+ view: ComposerView;
1064
+ }): void | Promise<void>;
1065
+ }
1066
+ /** Reactive read-side of the composer a plugin surface is mounted in. */
1067
+ interface ComposerView {
1068
+ scope: PluginComposerScope;
1069
+ layout: "expanded" | "compact" | "zen";
1070
+ draft: {
1071
+ text: string;
1072
+ isEmpty: boolean;
1073
+ attachmentCount: number;
1074
+ };
1075
+ run: {
1076
+ isRunning: boolean;
1077
+ isSubmitting: boolean;
1078
+ };
1079
+ }
1080
+ interface ComposerRichTextSpec {
1081
+ /** Content-derived paint: match ranges receive `className`; text is never mutated. */
1082
+ effects?: readonly {
1083
+ id: string;
1084
+ /** Plain-text offsets into the current structured draft. */
1085
+ match(text: string): readonly {
1086
+ from: number;
1087
+ to: number;
1088
+ }[];
1089
+ className: string;
1090
+ }[];
1091
+ /** Debounced, read-only observation of the structured draft. */
1092
+ onDraftChange?(draft: ComposerStructuredDraft, view: ComposerView): void;
1093
+ }
1094
+ interface ComposerStructuredDraft {
1095
+ text: string;
1096
+ mentions: readonly {
1097
+ from: number;
1098
+ to: number;
1099
+ provider: string;
1100
+ id: string;
1101
+ label: string;
1102
+ }[];
1103
+ }
1104
+ /** Host-rendered paint applied to the editable composer text. */
1105
+ interface PluginComposerTextEffect {
1106
+ className: string;
1107
+ }
1108
+ /** Host-rendered status that temporarily replaces a thread's draft glyph. */
1109
+ interface PluginComposerThreadRowStatus {
1110
+ /** BB icon-name hint; unknown names fall back to the generic plugin icon. */
1111
+ icon: string;
1112
+ /** Accessible label for the status glyph. */
1113
+ label: string;
1114
+ /**
1115
+ * Semantic host treatment for the status glyph. `running` automatically
1116
+ * shimmers; terminal `success` and `error` tones are static. Defaults to the
1117
+ * neutral tone.
1118
+ */
1119
+ tone?: "default" | "running" | "success" | "error";
1120
+ }
1121
+ /** An @-mention pill bound to one of the calling plugin's mention providers. */
1122
+ interface PluginComposerMention {
1123
+ /** Mention provider id registered by THIS plugin via `bb.ui.registerMentionProvider`. */
1124
+ provider: string;
1125
+ /** Item id your provider's `resolve` will receive at send time. */
1126
+ id: string;
1127
+ /** Pill text shown in the composer. */
1128
+ label: string;
1129
+ }
1130
+ /**
1131
+ * Programmatic access to the chat composer draft — the same shared draft the
1132
+ * built-in "Add to chat" affordances (file preview, diff, terminal selections)
1133
+ * write to. While a queued message is being edited, writes land in that
1134
+ * message's inline editor. In a side chat, writes land in the visible side-chat
1135
+ * draft. Otherwise, inside a thread context writes land in that thread's draft;
1136
+ * anywhere else (nav panel, homepage section) they seed the new-thread composer
1137
+ * draft, which persists until the user sends or clears it.
1138
+ */
1139
+ interface PluginComposerApi {
1140
+ scope: PluginComposerScope;
1141
+ /** Current plain text for this composer scope. */
1142
+ readonly text: string;
1143
+ /**
1144
+ * Replace the draft's plain text. Attachments are preserved. Inline mentions
1145
+ * outside the changed range are preserved and rebased; mentions overlapped
1146
+ * by the replacement are removed because their text representation changed.
1147
+ */
1148
+ setText(next: string): void;
1149
+ /**
1150
+ * Replace the draft's plain text from the latest committed value. Uses the
1151
+ * same structured-state reconciliation as `setText`.
1152
+ */
1153
+ updateText(updater: (current: string) => string): void;
1154
+ /** Clear plain text without clearing independently attached files. */
1155
+ clear(): void;
1156
+ /**
1157
+ * Apply a host-rendered effect to this composer's editable text, or clear it.
1158
+ * Effects are scoped to the calling plugin and automatically clear when the
1159
+ * slot unmounts or its composer scope changes.
1160
+ */
1161
+ setTextEffect(effect: PluginComposerTextEffect | null): void;
1162
+ /**
1163
+ * Lock or unlock editing for this composer. Locks are scoped to the calling
1164
+ * plugin and automatically release when the slot unmounts or its composer
1165
+ * scope changes.
1166
+ */
1167
+ setInputLock(locked: boolean): void;
1168
+ /**
1169
+ * Append text to the draft as a `> ` blockquote block and focus the
1170
+ * composer. Blank text is a no-op. This is the "reference this selection
1171
+ * in chat" primitive.
1172
+ */
1173
+ addQuote(text: string): void;
1174
+ /**
1175
+ * Insert an @-mention pill that resolves through this plugin's mention
1176
+ * provider at send time — the durable way to reference an entity whose
1177
+ * content should be fetched fresh when the message is sent.
1178
+ */
1179
+ insertMention(mention: PluginComposerMention): void;
1180
+ /** Focus the composer caret at the end of the draft. */
1181
+ focus(): void;
1182
+ }
1183
+ /**
1184
+ * A consumer-supplied action on the messages of one `ThreadChat` instance,
1185
+ * rendered in the embedded timeline's per-message action bar alongside the
1186
+ * native and slot-registered actions. Unlike the `messageAction` slot this is
1187
+ * scoped to the rendering component, not registered globally.
1188
+ */
1189
+ interface ThreadChatMessageAction {
1190
+ /** Unique within this ThreadChat instance; letters, digits, `-`, `_`. */
1191
+ id: string;
1192
+ /** Tooltip / menu label for the action. */
1193
+ title: string;
1194
+ /** Icon hint (BB icon name); unknown names fall back to a generic icon. */
1195
+ icon?: string;
1196
+ /**
1197
+ * Message roles the action applies to. Omitted = both user and assistant
1198
+ * messages.
1199
+ */
1200
+ roles?: readonly ("user" | "assistant")[];
1201
+ /**
1202
+ * Runs when the user activates the action. Errors (sync or async) are
1203
+ * contained and logged; they never break the timeline.
1204
+ */
1205
+ run(message: ThreadChatMessageReference): void | Promise<void>;
1206
+ }
1207
+ /**
1208
+ * Props of the host-owned `ThreadChat` component — one thread's chat
1209
+ * (timeline, and for the composer variants the full send/queue/draft
1210
+ * engine), rendered by the BB app inside a plugin slot. This is the
1211
+ * deliberate exception to the no-host-components rule (§5.5): a stable
1212
+ * product capability, not a UI kit. Versioned additive like slot props;
1213
+ * internal timeline rows, query hooks, and prompt-box configuration are
1214
+ * deliberately not exposed.
1215
+ */
1216
+ interface ThreadChatProps {
1217
+ threadId: string;
1218
+ /**
1219
+ * "full" (default) is the page presentation (centered reading width);
1220
+ * "compact" is the side-panel presentation; "timeline" renders the
1221
+ * transcript without a composer.
1222
+ */
1223
+ variant?: "full" | "compact" | "timeline";
1224
+ /**
1225
+ * "contained" (default) fills and scrolls inside a bounded parent;
1226
+ * "document" grows with its content and defers scrolling to the page.
1227
+ */
1228
+ layout?: "contained" | "document";
1229
+ /** Bump to focus the composer (ignored by `variant: "timeline"`). */
1230
+ focusRequest?: number;
1231
+ /**
1232
+ * Who controls the permission mode sends run with. "inherit" (default)
1233
+ * pins every send to the thread's own resolved default and renders the
1234
+ * picker as a dimmed label — a plugin surface can never widen it.
1235
+ * "editable" gives this chat its own picker, so the user can raise or
1236
+ * lower permissions for this thread independently of the thread it was
1237
+ * forked from. Ignored by `variant: "timeline"` (no composer).
1238
+ */
1239
+ permissionPolicy?: "inherit" | "editable";
1240
+ className?: string;
1241
+ /** Rendered above the conversation, scrolling with it. */
1242
+ leadingContent?: ReactNode;
1243
+ /**
1244
+ * Actions rendered in this instance's per-message action bar (see
1245
+ * {@link ThreadChatMessageAction}).
1246
+ */
1247
+ messageActions?: readonly ThreadChatMessageAction[];
1248
+ }
1249
+ /**
1250
+ * Every selection the composer resolved, JSON-serializable so a plugin can
1251
+ * forward it to its own backend rpc verbatim and hand it straight to
1252
+ * `bb.sdk.threads.spawn`.
1253
+ *
1254
+ * The split is deliberate: the composer owns *user selections*, the plugin
1255
+ * owns *filing and attribution*. `bb.sdk.threads.spawn` auto-fills
1256
+ * `origin: "plugin"` and `originPluginId`, so a thread created this way stays
1257
+ * attributed to the plugin — which it would not be if the component created
1258
+ * the thread itself. The plugin adds `sectionId`, `parentThreadId`, `title`,
1259
+ * and `visibility` to the request on its own; they are deliberately not
1260
+ * composer props.
1261
+ */
1262
+ interface NewThreadRequest {
1263
+ projectId: string;
1264
+ providerId: string;
1265
+ model: string;
1266
+ reasoningLevel: ReasoningLevel;
1267
+ permissionMode: PermissionMode;
1268
+ /** Omitted when the selected provider has no service tiers. */
1269
+ serviceTier?: ServiceTier;
1270
+ /**
1271
+ * Per-field provenance (caller-explicit vs. default) for the execution
1272
+ * options above, forwarded to `spawn` so the server records what the user
1273
+ * actually chose.
1274
+ */
1275
+ executionInputSources: CreateExecutionInputSources;
1276
+ environment: CreateThreadEnvironmentArgs;
1277
+ input: PromptInput[];
1278
+ }
1279
+ /**
1280
+ * Props of the host-owned `experimental_NewThreadComposer` component — bb's
1281
+ * full new-thread compose surface (prompt editor with @-mentions and expand,
1282
+ * attachments, provider/model/reasoning picker, voice, submit, and the row
1283
+ * beneath with project, environment, branch-from, and permission mode),
1284
+ * rendered by the BB app inside a plugin slot.
1285
+ *
1286
+ * It is the create-side counterpart to `ThreadChat`: same deliberate
1287
+ * exception to the no-host-components rule (§5.5), same additive versioning.
1288
+ */
1289
+ interface NewThreadComposerProps {
1290
+ /** Seeds the project picker. The user can change it. */
1291
+ defaultProjectId?: string;
1292
+ /**
1293
+ * Seeds the provider picker. Like every `default*` prop this is a SEED, not
1294
+ * a controlled value: the composer stays uncontrolled, the user can change
1295
+ * it, and when omitted the composer falls back to the project's remembered
1296
+ * execution defaults exactly as before. When provided it takes precedence
1297
+ * over those project defaults.
1298
+ *
1299
+ * Re-seeding: the `default*` props are value-compared each render. When any
1300
+ * of them changes after mount, the composer re-seeds EVERY execution and
1301
+ * environment selection from the new props — including selections the user
1302
+ * had already touched — so switching between two saved records in the same
1303
+ * mounted composer reloads that record's values (the same rule
1304
+ * `defaultProjectId` already follows).
1305
+ *
1306
+ * Every seeded field is reported as caller-explicit in the submitted
1307
+ * request's `executionInputSources`. That is what makes the seed survive
1308
+ * `threads.spawn`: the server drops a requested `providerId`/`model` that
1309
+ * carries no provenance source and re-derives it from the project's stored
1310
+ * defaults, which would silently undo the seed.
1311
+ */
1312
+ defaultProviderId?: string;
1313
+ /** Seeds the model picker. Same seed semantics as {@link defaultProviderId}. */
1314
+ defaultModel?: string;
1315
+ /**
1316
+ * Seeds the reasoning-level picker. Same seed semantics as
1317
+ * {@link defaultProviderId}. If the seeded model does not support this
1318
+ * level, the composer reconciles to the closest supported one.
1319
+ */
1320
+ defaultReasoningLevel?: ReasoningLevel;
1321
+ /**
1322
+ * Seeds the service-tier picker. Same seed semantics as
1323
+ * {@link defaultProviderId}. Ignored (and omitted from the submitted
1324
+ * request) when the selected provider has no service tiers.
1325
+ */
1326
+ defaultServiceTier?: ServiceTier;
1327
+ /** Seeds the permission-mode picker. Same seed semantics as {@link defaultProviderId}. */
1328
+ defaultPermissionMode?: PermissionMode;
1329
+ /**
1330
+ * Seeds the environment and branch pickers from a previously submitted
1331
+ * `NewThreadRequest.environment`. Same seed semantics as
1332
+ * {@link defaultProviderId}: a seed the user can change, taking precedence
1333
+ * over the composer's own environment default when provided.
1334
+ *
1335
+ * Round trip: feeding a submitted request's `environment` back in and
1336
+ * resubmitting untouched reproduces an equivalent environment, with these
1337
+ * documented limits — the composer cannot represent every args variant:
1338
+ *
1339
+ * - `{ type: "project-default" }` seeds nothing; the composer resolves its
1340
+ * own default and submits that concrete environment instead.
1341
+ * - A `host` environment whose host no longer exists (or whose project has
1342
+ * no source on it) falls back to the composer's default host, exactly as
1343
+ * the primary compose surface would.
1344
+ * - A `reuse` environment whose worktree no longer has unarchived threads
1345
+ * falls back the same way.
1346
+ * - An `unmanaged` workspace's `path` has no composer control; the seeded
1347
+ * selection submits `path: null` (the host's configured checkout). The
1348
+ * composer itself never produces a non-null `path`, so real round trips
1349
+ * are unaffected.
1350
+ * - A `managed-worktree` with `baseBranch: { kind: "default" }` leaves the
1351
+ * branch picker on its default, which may resolve to a named base branch
1352
+ * when the project configures a dedicated worktree base — the same branch
1353
+ * the original `default` submission would have created from.
1354
+ */
1355
+ defaultEnvironment?: CreateThreadEnvironmentArgs;
1356
+ /** Seeds the draft, only while the draft is still empty. */
1357
+ initialPrompt?: string;
1358
+ placeholder?: string;
1359
+ /**
1360
+ * "contained" (default) fills and scrolls inside a bounded parent;
1361
+ * "document" grows with its content and defers scrolling to the page.
1362
+ */
1363
+ layout?: "contained" | "document";
1364
+ /** Bump to focus the editor. */
1365
+ focusRequest?: number;
1366
+ className?: string;
1367
+ /**
1368
+ * Where the draft persists. Drafts survive reloads and are shared by every
1369
+ * composer using the same key; defaults to a key scoped to this plugin.
1370
+ */
1371
+ draftKey?: string;
1372
+ /**
1373
+ * Fires on submit with every selection resolved. The draft clears when this
1374
+ * resolves and is KEPT if it throws, so a failed create never loses what the
1375
+ * user typed.
1376
+ */
1377
+ onSubmit: (request: NewThreadRequest) => void | Promise<void>;
1378
+ }
1379
+ /**
1380
+ * Props of the host-owned `Markdown` component — bb's chat message renderer
1381
+ * (the same typography, spacing, and code styling as timeline messages).
1382
+ * Use it wherever plugin UI quotes or previews message content so it reads
1383
+ * like the rest of the chat. Like `ThreadChat`, this is a stable product
1384
+ * capability, not a UI kit; renderer internals stay private.
1385
+ */
1386
+ interface MarkdownProps {
1387
+ /** Markdown source, rendered exactly like a chat message body. */
1388
+ content: string;
1389
+ className?: string;
1390
+ }
1391
+ /** Current app selection, derived from the route. */
1392
+ interface BbContext {
1393
+ projectId: string | null;
1394
+ threadId: string | null;
1395
+ }
1396
+ interface BbNavigate {
1397
+ toThread(threadId: string): void;
1398
+ toProject(projectId: string): void;
1399
+ /**
1400
+ * Navigate to one of this plugin's own nav panels by its `path`.
1401
+ * `subPath` targets a location inside the panel (the component's
1402
+ * `subPath` prop); `replace` swaps the current history entry instead of
1403
+ * pushing — use it for redirects so back does not bounce.
1404
+ */
1405
+ toPluginPanel(path: string, options?: {
1406
+ subPath?: string;
1407
+ replace?: boolean;
1408
+ }): void;
1409
+ /**
1410
+ * Navigate to the root compose surface (the new-thread screen). Pass
1411
+ * `initialPrompt` to seed the composer draft and `focusPrompt` to focus the
1412
+ * composer on arrival — the pairing behind "Create via chat" style entry
1413
+ * points that drop the user into chat with a prefilled prompt.
1414
+ */
1415
+ toCompose(options?: {
1416
+ initialPrompt?: string;
1417
+ focusPrompt?: boolean;
1418
+ }): void;
1419
+ /**
1420
+ * Open one of this plugin's registered thread-panel actions in the current
1421
+ * thread surface. Returns false when the surface has no thread side panel or
1422
+ * the action is unavailable.
1423
+ */
1424
+ openThreadPanel(options: {
1425
+ actionId: string;
1426
+ title?: string;
1427
+ params?: JsonValue;
1428
+ }): boolean;
1429
+ }
1430
+ /**
1431
+ * Everything `@get-bb/plugin-sdk/app` resolves to at runtime. The BB app builds
1432
+ * the real implementation and `satisfies` this interface; `bb plugin build`
1433
+ * shims the specifier to that object on `globalThis.__bbPluginRuntime`.
1434
+ */
1435
+ interface PluginSdkApp {
1436
+ definePluginApp(setup: PluginAppSetup): PluginAppDefinition;
1437
+ useRpc<Contract extends PluginRpcContract = PluginRpcContract>(): PluginRpcClient<Contract>;
1438
+ useRealtime(channel: string, handler: (payload: unknown) => void): void;
1439
+ /**
1440
+ * Observe the same shared connection that delivers `useRealtime` signals.
1441
+ * Use a subsequent transition to `connected` to reconcile server state that
1442
+ * may have changed while ephemeral signals could not be delivered. The first
1443
+ * connection can transition from `connecting` and is not a reconnection.
1444
+ */
1445
+ useRealtimeConnectionState(): PluginRealtimeConnectionState;
1446
+ useSettings(): PluginSettingsState;
1447
+ useBbContext(): BbContext;
1448
+ useBbNavigate(): BbNavigate;
1449
+ useComposer(): PluginComposerApi;
1450
+ /**
1451
+ * The sidebar's live thread view (see {@link PluginSidebarThreadsState}).
1452
+ * Reads the host's own cache and realtime subscriptions, so it costs no
1453
+ * extra request and updates exactly when the built-in sidebar does.
1454
+ * Experimental: see docs/api_to_audit.md.
1455
+ */
1456
+ experimental_useSidebarThreads(): PluginSidebarThreadsState;
1457
+ /**
1458
+ * Thread actions bound to the host's mutations (see
1459
+ * {@link PluginSidebarThreadActions}). Experimental: see
1460
+ * docs/api_to_audit.md.
1461
+ */
1462
+ experimental_useSidebarThreadActions(): PluginSidebarThreadActions;
1463
+ /**
1464
+ * The pull request for one thread's branch (see
1465
+ * {@link PluginSidebarThreadPullRequestState}).
1466
+ *
1467
+ * Per row and opt-in, because it costs a git-host lookup: it is NOT on the
1468
+ * thread payload every sidebar loads. Threads sharing an environment share
1469
+ * one query, and the host owns the polling and staleness rules — an open PR
1470
+ * with pending checks refreshes, a merged one does not.
1471
+ *
1472
+ * Experimental: see docs/api_to_audit.md.
1473
+ */
1474
+ experimental_useSidebarThreadPullRequest(threadId: string): PluginSidebarThreadPullRequestState;
1475
+ /**
1476
+ * Per-row drag-to-split support (see {@link PluginSidebarThreadSplit}).
1477
+ * Call it once per rendered row, like the built-in sidebar does.
1478
+ * Experimental: see docs/api_to_audit.md.
1479
+ */
1480
+ experimental_useSidebarThreadSplit(threadId: string): PluginSidebarThreadSplit;
1481
+ /**
1482
+ * The host-owned chat component (see {@link ThreadChatProps}). Together
1483
+ * with `Markdown`, the only components the SDK ships — everything else
1484
+ * stays vendored per §5.5.
1485
+ */
1486
+ ThreadChat: ComponentType<ThreadChatProps>;
1487
+ /**
1488
+ * The host-owned chat-message markdown renderer (see
1489
+ * {@link MarkdownProps}).
1490
+ */
1491
+ Markdown: ComponentType<MarkdownProps>;
1492
+ /**
1493
+ * The host-owned new-thread compose surface (see
1494
+ * {@link NewThreadComposerProps}). Experimental: see
1495
+ * docs/api_to_audit.md for what to audit before the prefix drops.
1496
+ */
1497
+ experimental_NewThreadComposer: ComponentType<NewThreadComposerProps>;
1498
+ useComposerView(): ComposerView;
1499
+ }
1500
+
1501
+ declare const definePluginApp: (setup: PluginAppSetup) => PluginAppDefinition;
1502
+ declare const ThreadChat: react.ComponentType<ThreadChatProps>;
1503
+ declare const Markdown: react.ComponentType<MarkdownProps>;
1504
+ declare const experimental_NewThreadComposer: react.ComponentType<NewThreadComposerProps>;
1505
+ declare const useRpc: <Contract extends PluginRpcContract = Readonly<Record<string, PluginRpcMethodContract<StandardSchemaV1<unknown, unknown>, StandardSchemaV1<unknown, unknown>>>>>() => PluginRpcClient<Contract>;
1506
+ declare const useRealtime: (channel: string, handler: (payload: unknown) => void) => void;
1507
+ declare const useRealtimeConnectionState: () => PluginRealtimeConnectionState;
1508
+ declare const useSettings: () => PluginSettingsState;
1509
+ declare const useBbContext: () => BbContext;
1510
+ declare const useBbNavigate: () => BbNavigate;
1511
+ declare const useComposer: () => PluginComposerApi;
1512
+ declare const useComposerView: () => ComposerView;
1513
+ declare const experimental_useSidebarThreads: () => PluginSidebarThreadsState;
1514
+ declare const experimental_useSidebarThreadActions: () => PluginSidebarThreadActions;
1515
+ declare const experimental_useSidebarThreadPullRequest: (threadId: string) => PluginSidebarThreadPullRequestState;
1516
+ declare const experimental_useSidebarThreadSplit: (threadId: string) => PluginSidebarThreadSplit;
1517
+
1518
+ export { Markdown, ThreadChat, definePluginApp, experimental_NewThreadComposer, experimental_useSidebarThreadActions, experimental_useSidebarThreadPullRequest, experimental_useSidebarThreadSplit, experimental_useSidebarThreads, useBbContext, useBbNavigate, useComposer, useComposerView, useRealtime, useRealtimeConnectionState, useRpc, useSettings };
1519
+ export type { BbContext, BbNavigate, ComposerCustomization, ComposerPlusMenuItem, ComposerRichTextSpec, ComposerStructuredDraft, ComposerView, JsonValue, MarkdownProps, NewThreadComposerProps, NewThreadRequest, PluginAppBuilder, PluginAppComposer, PluginAppContentScripts, PluginAppDefinition, PluginAppSetup, PluginAppSlots, PluginComposerApi, PluginComposerMention, PluginComposerScope, PluginComposerTextEffect, PluginComposerThreadRowStatus, PluginContentScriptContext, PluginContentScriptDisposer, PluginContentScriptRegistration, PluginFileOpenerProps, PluginFileOpenerRegistration, PluginFileOpenerSource, PluginHomepageSectionProps, PluginHomepageSectionRegistration, PluginMessageActionContext, PluginMessageActionRegistration, PluginMessageActionThreadPanelOptions, PluginMessageDirectiveMessage, PluginMessageDirectiveOpenWorkspaceFile, PluginMessageDirectiveProps, PluginMessageDirectiveRegistration, PluginNavPanelProps, PluginNavPanelRegistration, PluginNewThreadPanelActionContext, PluginNewThreadPanelActionRegistration, PluginNewThreadPanelProps, PluginPendingInteractionProps, PluginPendingInteractionRegistration, PluginPendingInteractionView, PluginRealtimeConnectionState, PluginRpcCallArgs, PluginRpcClient, PluginRpcContract, PluginRpcError, PluginRpcErrorCode, PluginRpcHandlers, PluginRpcIssuePathSegment, PluginRpcMethodContract, PluginRpcResult, PluginRpcValidationIssue, PluginSdkApp, PluginSettingsSectionProps, PluginSettingsSectionRegistration, PluginSettingsState, PluginSidebarFooterActionContext, PluginSidebarFooterActionProps, PluginSidebarFooterActionRegistration, PluginSidebarProject, PluginSidebarPullRequest, PluginSidebarSplitPane, PluginSidebarThread, PluginSidebarThreadActions, PluginSidebarThreadActivity, PluginSidebarThreadIndicator, PluginSidebarThreadPullRequestState, PluginSidebarThreadSplit, PluginSidebarThreadsState, PluginSidebarWorkspaceKind, PluginThreadHeaderActionProps, PluginThreadHeaderActionRegistration, PluginThreadListProps, PluginThreadListRegistration, PluginThreadPanelActionContext, PluginThreadPanelActionRegistration, PluginThreadPanelProps, StandardSchemaV1, StandardSchemaV1InferInput, StandardSchemaV1InferOutput, StandardSchemaV1Issue, StandardSchemaV1Result, ThreadChatMessageAction, ThreadChatMessageReference, ThreadChatProps };