@get-bb/plugin-sdk 0.5.31 → 0.6.5

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,457 @@
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 { z } from 'zod';
9
+ import { PluginComposerApi as PluginComposerApi$1, PluginComposerScope as PluginComposerScope$1, ComposerDraftSnapshot as ComposerDraftSnapshot$1, ComposerSelection as ComposerSelection$1, ComposerDraftReplacement as ComposerDraftReplacement$1, ComposerDraft as ComposerDraft$1, ComposerSubmitOptions as ComposerSubmitOptions$1, JsonValue as JsonValue$2, ComposerMention as ComposerMention$1, PluginComposerTextEffect as PluginComposerTextEffect$1 } from '@get-bb/plugin-sdk';
10
+
11
+ interface JsonObject {
12
+ [key: string]: JsonValue$1;
13
+ }
14
+ type JsonValue$1 = string | number | boolean | null | JsonValue$1[] | JsonObject;
15
+
16
+ declare const reasoningLevelSchema: z.ZodEnum<{
17
+ high: "high";
18
+ low: "low";
19
+ max: "max";
20
+ medium: "medium";
21
+ none: "none";
22
+ ultra: "ultra";
23
+ ultracode: "ultracode";
24
+ xhigh: "xhigh";
25
+ }>;
26
+ type ReasoningLevel = z.infer<typeof reasoningLevelSchema>;
27
+ declare const serviceTierSchema: z.ZodEnum<{
28
+ default: "default";
29
+ fast: "fast";
30
+ }>;
31
+ type ServiceTier = z.infer<typeof serviceTierSchema>;
32
+ declare const permissionModeSchema: z.ZodEnum<{
33
+ "accept-edits": "accept-edits";
34
+ auto: "auto";
35
+ full: "full";
36
+ }>;
37
+ type PermissionMode = z.infer<typeof permissionModeSchema>;
38
+
39
+ declare const createThreadEnvironmentArgsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
40
+ environmentId: z.ZodString;
41
+ type: z.ZodLiteral<"reuse">;
42
+ }, z.core.$strip>, z.ZodObject<{
43
+ hostId: z.ZodOptional<z.ZodString>;
44
+ type: z.ZodLiteral<"host">;
45
+ workspace: z.ZodDiscriminatedUnion<[z.ZodObject<{
46
+ branch: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
47
+ kind: z.ZodLiteral<"existing">;
48
+ name: z.ZodString;
49
+ }, z.core.$strict>, z.ZodObject<{
50
+ baseBranch: z.ZodString;
51
+ kind: z.ZodLiteral<"new">;
52
+ }, z.core.$strict>], "kind">>;
53
+ path: z.ZodNullable<z.ZodString>;
54
+ type: z.ZodLiteral<"unmanaged">;
55
+ }, z.core.$strip>, z.ZodObject<{
56
+ baseBranch: z.ZodDiscriminatedUnion<[z.ZodObject<{
57
+ kind: z.ZodLiteral<"named">;
58
+ name: z.ZodString;
59
+ }, z.core.$strip>, z.ZodObject<{
60
+ kind: z.ZodLiteral<"default">;
61
+ }, z.core.$strip>], "kind">;
62
+ type: z.ZodLiteral<"managed-worktree">;
63
+ }, z.core.$strip>, z.ZodObject<{
64
+ type: z.ZodLiteral<"personal">;
65
+ }, z.core.$strip>], "type">;
66
+ }, z.core.$strip>, z.ZodObject<{
67
+ type: z.ZodLiteral<"project-default">;
68
+ }, z.core.$strip>, z.ZodObject<{
69
+ environmentProviderId: z.ZodString;
70
+ inputs: z.ZodDefault<z.ZodNullable<z.ZodType<JsonValue$1, unknown, z.core.$ZodTypeInternals<JsonValue$1, unknown>>>>;
71
+ machine: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
72
+ hostId: z.ZodString;
73
+ type: z.ZodLiteral<"existing">;
74
+ }, z.core.$strip>, z.ZodObject<{
75
+ inputs: z.ZodDefault<z.ZodNullable<z.ZodType<JsonValue$1, unknown, z.core.$ZodTypeInternals<JsonValue$1, unknown>>>>;
76
+ machineProviderId: z.ZodString;
77
+ type: z.ZodLiteral<"new">;
78
+ }, z.core.$strip>], "type">>;
79
+ type: z.ZodLiteral<"provider">;
80
+ }, z.core.$strip>], "type">;
81
+ type CreateThreadEnvironmentArgs = z.infer<typeof createThreadEnvironmentArgsSchema>;
82
+
83
+ declare const uploadedPromptAttachmentSchema: z.ZodObject<{
84
+ mimeType: z.ZodOptional<z.ZodString>;
85
+ name: z.ZodString;
86
+ path: z.ZodString;
87
+ sizeBytes: z.ZodNumber;
88
+ type: z.ZodEnum<{
89
+ localFile: "localFile";
90
+ localImage: "localImage";
91
+ }>;
92
+ }, z.core.$strip>;
93
+ type UploadedPromptAttachment = z.infer<typeof uploadedPromptAttachmentSchema>;
94
+
95
+ /**
96
+ * A value that survives a JSON round trip without coercion or data loss.
97
+ *
98
+ * Host boundaries still validate values at runtime because TypeScript cannot
99
+ * exclude non-finite numbers and plugin bundles can bypass static types.
100
+ */
101
+ type JsonValue = string | number | boolean | null | JsonValue[] | {
102
+ [key: string]: JsonValue;
103
+ };
104
+
105
+ /** Where `useComposer()` writes. */
106
+ type PluginComposerScope = {
107
+ kind: "thread";
108
+ threadId: string;
109
+ } | {
110
+ kind: "queued-message";
111
+ threadId: string;
112
+ queuedMessageId: string;
113
+ } | {
114
+ kind: "new-thread";
115
+ /** Root compose's effective selected project; null only while unresolved. */
116
+ projectId: string | null;
117
+ };
118
+ /** The composer's text and the @-mention pills in it. */
119
+ interface ComposerDraft {
120
+ text: string;
121
+ mentions: readonly ComposerMention[];
122
+ }
123
+ /** An already uploaded attachment; paths retain their original project or thread ownership. */
124
+ type ComposerAttachment = UploadedPromptAttachment;
125
+ /** The complete current draft. Snapshots and their entries are immutable. */
126
+ interface ComposerDraftSnapshot extends ComposerDraft {
127
+ readonly attachments: readonly ComposerAttachment[];
128
+ }
129
+ /** Atomic text and mention replacement, optionally replacing uploaded attachments too. */
130
+ interface ComposerDraftReplacement extends ComposerDraft {
131
+ /** Omit to preserve attachments; supply an empty array to remove them all. Does not upload or copy files. */
132
+ attachments?: readonly ComposerAttachment[];
133
+ }
134
+ /**
135
+ * One @-mention pill: its range in `ComposerDraft.text` plus everything the
136
+ * host needs to recreate it, so a mention read from `draft` can be passed
137
+ * back to `insert` unchanged.
138
+ */
139
+ type ComposerMention = {
140
+ from: number;
141
+ to: number;
142
+ label: string;
143
+ } & ({
144
+ kind: "thread";
145
+ threadId: string;
146
+ projectId?: string;
147
+ } | {
148
+ kind: "project";
149
+ projectId: string;
150
+ } | {
151
+ kind: "section";
152
+ sectionId: string;
153
+ } | {
154
+ kind: "path";
155
+ path: string;
156
+ source: "thread-storage" | "workspace";
157
+ entryKind: "directory" | "file";
158
+ } | {
159
+ kind: "command";
160
+ trigger: "$" | "/";
161
+ name: string;
162
+ source: "command" | "skill";
163
+ origin: "builtin" | "project" | "user";
164
+ argumentHint: string | null;
165
+ } | {
166
+ kind: "plugin";
167
+ /** The plugin that owns the pill. */
168
+ pluginId: string;
169
+ /** The mention provider id that plugin registered. */
170
+ provider: string;
171
+ /** The item id the provider's `resolve` receives at send time. */
172
+ id: string;
173
+ icon?: string | null;
174
+ });
175
+ /**
176
+ * What `insert` accepts: text, a mention read from `draft` (any kind), or one
177
+ * of the calling plugin's own mentions (`{ provider, id, label }`, no `kind`).
178
+ */
179
+ type ComposerInsertPart = string | ComposerMention | PluginComposerMention;
180
+ /** Where `insert` puts its content. */
181
+ interface ComposerInsertOptions {
182
+ /**
183
+ * `"cursor"` (default) inserts at the editor's selection, replacing any
184
+ * selected text; the selection is kept while focus is elsewhere, and the
185
+ * composer must be on screen. `"end"` inserts after the last character and
186
+ * also works for a composer that is not on screen.
187
+ */
188
+ at?: "cursor" | "end";
189
+ /** Put the content on its own paragraph, adding a paragraph break before and after only where text is adjacent. */
190
+ block?: boolean;
191
+ }
192
+ /** Host-rendered paint applied to the editable composer text. */
193
+ interface PluginComposerTextEffect {
194
+ className: string;
195
+ }
196
+ /** An @-mention pill bound to one of the calling plugin's mention providers. */
197
+ interface PluginComposerMention {
198
+ /** Mention provider id registered by THIS plugin via `bb.ui.registerMentionProvider`. */
199
+ provider: string;
200
+ /** Item id your provider's `resolve` will receive at send time. */
201
+ id: string;
202
+ /** Pill text shown in the composer. */
203
+ label: string;
204
+ }
205
+ /**
206
+ * One composer: its state and the writes a plugin can make. `useComposer()`
207
+ * returns the composer the calling surface belongs to — inside a composer
208
+ * slot, that composer; in a thread's panels, that thread's composer;
209
+ * elsewhere, the current route's draft (the thread in view, or the new-thread
210
+ * draft).
211
+ *
212
+ * The handle is stable: the same composer returns the same object across
213
+ * renders, and its methods always act on the current draft, so it is safe to
214
+ * keep in effects, callbacks and async work. Its reactive fields (`text`,
215
+ * `draft`, `selection`, `isEmpty`, …) re-render the calling component when they change;
216
+ * depend on those fields, not on the handle, in memo dependency lists.
217
+ *
218
+ * A handle always writes to its own composer's draft. Thread and new-thread
219
+ * drafts persist, so a write after the composer left the screen still lands
220
+ * in that draft. When the draft no longer exists (a queued-message or
221
+ * sent-message editor that closed), `insert`, `replace`, `removeMention`, `submit` and
222
+ * `setSelection` throw "This composer is no longer available"; the older
223
+ * text methods log a warning and do nothing.
224
+ */
225
+ interface PluginComposerApi {
226
+ scope: PluginComposerScope;
227
+ /**
228
+ * Stable identity for this composer's draft: the same across remounts and
229
+ * reloads for thread and new-thread composers (including a `ThreadChat`),
230
+ * and per editing session for queued-message and sent-message editors.
231
+ */
232
+ readonly key: string;
233
+ /** `"compact"` in the collapsed single-line layout, otherwise `"expanded"`. */
234
+ readonly layout: "compact" | "expanded";
235
+ /** The thread's agent is running or stopping. Always false in a new-thread composer. */
236
+ readonly isRunning: boolean;
237
+ /** The composer is submitting right now. */
238
+ readonly isSubmitting: boolean;
239
+ /**
240
+ * Pressing Enter would not submit right now: the same decision as the
241
+ * host's send button (empty draft, uploads in progress, loading, a
242
+ * selection or setup missing, a pending interaction, voice input, …).
243
+ */
244
+ readonly isSubmittingBlocked: boolean;
245
+ /** The host's message for why submitting is blocked, or null when it is not. */
246
+ readonly submittingBlockedReason: string | null;
247
+ /** No text (ignoring whitespace), no mentions and no attachments. */
248
+ readonly isEmpty: boolean;
249
+ /** Attachments that have finished uploading. */
250
+ readonly attachmentCount: number;
251
+ /** Current plain text for this composer scope. */
252
+ readonly text: string;
253
+ /** The complete current draft, including uploaded attachments. Stable until the draft changes. */
254
+ readonly draft: ComposerDraftSnapshot;
255
+ /** Current picker values, or null when this composer has no pickers. Stable until a picker value changes. */
256
+ readonly selection: ComposerSelection | null;
257
+ /**
258
+ * Replace text and mentions together in one committed change. An updater
259
+ * receives the latest immutable snapshot, including attachments, and must
260
+ * return its result synchronously. Returning that same snapshot is a no-op.
261
+ * Omitted attachments are preserved; an explicit list replaces them, and
262
+ * an empty list clears them. Does not infer or rebase mention ranges.
263
+ * Ranges are non-overlapping UTF-16 offsets into the supplied text.
264
+ * Invalid results, throwing updaters, and unavailable editors leave the
265
+ * draft unchanged. Does not focus, submit, upload, or copy files between
266
+ * projects. Use `insert` for insertion at the editor's cursor.
267
+ */
268
+ replace(next: ComposerDraftReplacement | ((current: ComposerDraftSnapshot) => ComposerDraftReplacement)): void;
269
+ /**
270
+ * Insert text and mentions. See {@link ComposerInsertOptions} for placement.
271
+ * Mentions read from `draft` are recreated exactly; the calling plugin's
272
+ * own `{ provider, id, label }` resolves through its mention provider.
273
+ * Throws for another plugin's mention without `kind`, and for
274
+ * `at: "cursor"` when the composer is not on screen.
275
+ */
276
+ insert(parts: ComposerInsertPart | readonly ComposerInsertPart[], options?: ComposerInsertOptions): void;
277
+ /**
278
+ * Apply a host-rendered effect to this composer's editable text, or clear it.
279
+ * Effects are scoped to the calling plugin and automatically clear when the
280
+ * slot unmounts or its composer scope changes.
281
+ */
282
+ setTextEffect(effect: PluginComposerTextEffect | null): void;
283
+ /**
284
+ * Lock or unlock editing for this composer. Locks are scoped to the calling
285
+ * plugin and automatically release when the slot unmounts or its composer
286
+ * scope changes.
287
+ */
288
+ setInputLock(locked: boolean): void;
289
+ /** Subscribe to successful local submissions in this composer scope, including accepted queued messages. Failed sends and draft clearing do not notify. Dispose on unmount. */
290
+ onSubmitted(listener: () => void): () => void;
291
+ /** Focus the composer caret at the end of the draft. */
292
+ focus(): void;
293
+ /**
294
+ * Submit this composer's draft exactly as pressing Enter would: the same
295
+ * checks and the same action (send, or queue while the thread is busy; a
296
+ * provider handoff creates a new thread). The draft's attachments and
297
+ * @-mentions, and — in the new-thread composer — the provider, model,
298
+ * reasoning level, service tier, permission mode and environment the user
299
+ * has selected on screen, all travel with it.
300
+ *
301
+ * While attachments are uploading it waits for them, then checks again; it
302
+ * rejects if an upload fails. Otherwise, when submitting is blocked it
303
+ * rejects with `submittingBlockedReason`, a message safe to show to the
304
+ * user. The queued-message and sent-message editors save edits and reject.
305
+ *
306
+ * `sendAt` queues the submission until that time. `experimental_data` is
307
+ * opaque JSON delivered to dispatch hooks together with the calling plugin's
308
+ * id on this initial attempt. Hooks run before operational core waits. If a
309
+ * hook queues the message, its existing plugin wait identifies the owner on
310
+ * later attempts; core does not persist or interpret the opaque data.
311
+ *
312
+ * Resolves once the host has accepted the submission and cleared the draft.
313
+ * Failures of the underlying request are reported by bb's own submit error
314
+ * handling and restore the draft, exactly as an interactive failure does.
315
+ */
316
+ submit(options: ComposerSubmitOptions): Promise<void>;
317
+ /**
318
+ * Set this composer's pickers as if each value had been picked by hand.
319
+ *
320
+ * Every field is optional. An omitted field is left alone. A field this
321
+ * composer has no picker for is ignored rather than rejected: a thread
322
+ * composer has no project or environment; a provider without service tiers
323
+ * has no tier; a fork draft locks its project, provider and environment.
324
+ * Values travel through the same paths the pickers use, so in the
325
+ * new-thread composer they become the remembered defaults for the next
326
+ * thread and are reported as the user's explicit choices, and in a thread
327
+ * composer a provider change starts the same handoff the picker starts:
328
+ * the handoff block is prepended to the draft and the next send creates a
329
+ * new thread. A same-provider model change in a thread does not start a
330
+ * handoff, exactly like the picker.
331
+ *
332
+ * In the new-thread composer the project is switched first and awaited
333
+ * (attachments are copied to the new project), then the environment and
334
+ * machine are applied to the new project, then provider, model, reasoning
335
+ * level, service tier and permission mode. A provider change reloads the
336
+ * model catalog before the model and reasoning level are applied to it.
337
+ * Because the project switch remounts plugin surfaces, the returned
338
+ * promise is owned by the composer and still resolves after the calling
339
+ * component has unmounted.
340
+ *
341
+ * Resolves with the composer's own selection once it has settled: the
342
+ * applied values have committed and the model catalog for the selected
343
+ * provider and machine has finished loading, so model, reasoning level and
344
+ * permission mode have reconciled against it. The catalog wait is bounded;
345
+ * if it has not finished after 15 seconds the promise resolves with the
346
+ * selection as it stands. The result carries only the fields this composer
347
+ * has, so a missing key means "no such picker here" and a value that
348
+ * differs from the one passed was reconciled (a reasoning level the model
349
+ * does not support, a permission mode above the machine's ceiling, a model
350
+ * the provider does not list). A provider the composer does not list is
351
+ * ignored together with the model and reasoning level meant for it, so the
352
+ * stored provider preference never names something the picker could not
353
+ * have chosen. `environment` is absent while the composer
354
+ * has no submittable environment; `providerId` and `model` are absent
355
+ * while nothing is selected; `serviceTier` is present only when the
356
+ * selected provider has tiers and one is chosen.
357
+ *
358
+ * Rejects, with a message safe to show to the user, in a composer with no
359
+ * pickers at all (a queued-message editor, a side chat, a plugin surface
360
+ * mounted outside any composer), when the calling surface is no longer
361
+ * active, and when a value is not a known reasoning level, service tier or
362
+ * permission mode.
363
+ */
364
+ setSelection(selection: ComposerSelection): Promise<ComposerSelection>;
365
+ }
366
+ /**
367
+ * Current picker values in `selection`, input for `setSelection`, and the shape it resolves with. Field names match `NewThreadRequest` and the `default*` props of
368
+ * `experimental_NewThreadComposer`, so one routed decision can feed the
369
+ * composer, the embedded composer and `bb.sdk.threads.spawn` alike.
370
+ */
371
+ interface ComposerSelection {
372
+ /** New-thread composers only. BB's personal-project id means "Don't work in a project". */
373
+ projectId?: string;
374
+ /**
375
+ * New-thread composers only. `{ type: "project-default" }` and a `host`
376
+ * environment without a `hostId` seed nothing and are ignored. Provider
377
+ * `inputs` are not applied; the provider's own inputs control keeps its
378
+ * value, and the result reports what the composer would submit.
379
+ */
380
+ environment?: CreateThreadEnvironmentArgs;
381
+ /** A provider the composer does not list is ignored, and `model` and `reasoningLevel` with it. */
382
+ providerId?: string;
383
+ /** Applied only when the composer ends up on the requested provider (or none was requested). */
384
+ model?: string;
385
+ /** Applied only when the composer ends up on the requested provider (or none was requested). */
386
+ reasoningLevel?: ReasoningLevel;
387
+ /** Ignored by a provider with no service tiers. */
388
+ serviceTier?: ServiceTier;
389
+ permissionMode?: PermissionMode;
390
+ }
391
+ /**
392
+ * What `submit` does differently from pressing Enter.
393
+ *
394
+ * `experimental_data` is opaque JSON delivered to dispatch hooks. The runtime
395
+ * associates it with the calling plugin automatically for the initial
396
+ * dispatch attempt.
397
+ */
398
+ type ComposerSubmitOptions = {
399
+ sendAt: number;
400
+ experimental_data?: JsonValue;
401
+ } | {
402
+ experimental_data: JsonValue;
403
+ sendAt?: never;
404
+ };
405
+
406
+ declare function reconcileComposerMentions(currentText: string, nextText: string, mentions: readonly ComposerMention[]): ComposerMention[];
407
+
408
+ type DraftTarget = Pick<ComposerHandleTarget, "focus" | "getDraft" | "isAvailable" | "setDraft">;
409
+ declare function createComposerDraftActions(target: () => DraftTarget): Pick<PluginComposerApi, "draft" | "focus" | "replace">;
410
+
411
+ interface ComposerEditorState {
412
+ layout: "compact" | "expanded";
413
+ isRunning: boolean;
414
+ isSubmitting: boolean;
415
+ isSubmittingBlocked: boolean;
416
+ submittingBlockedReason: string | null;
417
+ isAttaching: boolean;
418
+ attachmentError: string | null;
419
+ }
420
+ interface ComposerHandleTarget {
421
+ key: string;
422
+ scope: PluginComposerScope$1;
423
+ getDraft(): ComposerDraftSnapshot$1;
424
+ getAttachmentCount(): number;
425
+ getSelection(): ComposerSelection$1 | null;
426
+ setDraft(next: ComposerDraftReplacement$1): void;
427
+ addQuote(text: string): void;
428
+ getEditorState(): ComposerEditorState;
429
+ subscribeEditorState(listener: () => void): () => void;
430
+ insertAtCursor(value: ComposerDraft$1, block: boolean): boolean;
431
+ isAvailable(): boolean;
432
+ focus(): void;
433
+ submit?(options: ComposerSubmitOptions$1, pluginSubmission: {
434
+ pluginId: string;
435
+ data: JsonValue$2;
436
+ } | undefined): Promise<void>;
437
+ setSelection?(selection: ComposerSelection$1): Promise<ComposerSelection$1>;
438
+ }
439
+ interface ComposerHandleController {
440
+ pluginId: string;
441
+ target: ComposerHandleTarget;
442
+ mentionText(mention: ComposerMention$1): string;
443
+ setTextEffect(effect: PluginComposerTextEffect$1 | null): void;
444
+ setInputLock(locked: boolean): void;
445
+ onSubmitted(listener: () => void): () => void;
446
+ }
447
+ interface ComposerHandleBinding {
448
+ key: string;
449
+ handle: PluginComposerApi$1;
450
+ update(controller: ComposerHandleController): void;
451
+ }
452
+ declare const OFF_SCREEN_EDITOR_STATE: ComposerEditorState;
453
+ declare function appendComposerDraft(current: ComposerDraft$1, value: ComposerDraft$1, block: boolean): ComposerDraft$1;
454
+ declare function createComposerHandleBinding(key: string, initial: ComposerHandleController): ComposerHandleBinding;
455
+
456
+ export { OFF_SCREEN_EDITOR_STATE, appendComposerDraft, createComposerDraftActions, createComposerHandleBinding, reconcileComposerMentions };
457
+ export type { ComposerEditorState, ComposerHandleBinding, ComposerHandleController, ComposerHandleTarget };
@@ -795,6 +795,22 @@ interface PluginRpcMethodContract<InputSchema extends StandardSchemaV1 = Standar
795
795
  readonly input: InputSchema;
796
796
  readonly output: OutputSchema;
797
797
  }
798
+ /**
799
+ * Who invoked an rpc method. `plugin` means another loaded plugin (or this
800
+ * one) called through its own `bb.sdk.plugins.callRpc`; the host verifies
801
+ * that with a per-load token only the server knows. Every other caller — the
802
+ * app, the `bb` CLI, agents, and bb itself — is `client`.
803
+ */
804
+ type ExperimentalPluginRpcCaller = {
805
+ readonly kind: "plugin";
806
+ readonly pluginId: string;
807
+ } | {
808
+ readonly kind: "client";
809
+ };
810
+ /** Second argument of every rpc handler. */
811
+ interface ExperimentalPluginRpcHandlerContext {
812
+ readonly experimental_caller: ExperimentalPluginRpcCaller;
813
+ }
798
814
 
799
815
  type PluginSettingDescriptor = {
800
816
  type: "string";
@@ -1070,7 +1086,7 @@ interface MessageDispatchHookContext {
1070
1086
  queuedMessages: ThreadQueuedMessage[];
1071
1087
  /**
1072
1088
  * Opaque JSON supplied by a plugin through the composer's
1073
- * `experimental_submit`, paired with that plugin's id. Null for ordinary
1089
+ * `submit`, paired with that plugin's id. Null for ordinary
1074
1090
  * submissions and queued re-attempts. Core does not persist or interpret
1075
1091
  * the data.
1076
1092
  */
@@ -1644,8 +1660,9 @@ interface PluginMentionItem {
1644
1660
  subtitle?: string;
1645
1661
  /**
1646
1662
  * BB icon name: a built-in name, or a name the plugin's app bundle
1647
- * registered with `app.experimental_icons.register()`. The row prefers the
1648
- * plugin's own branding icon when it ships one; unknown names fall back to
1663
+ * registered with `app.experimental_icons.register()`. Resolved names take
1664
+ * precedence over plugin branding in menu rows, composer pills, and sent
1665
+ * messages. Omitted or unknown names fall back to plugin branding, then
1649
1666
  * the generic plugin icon.
1650
1667
  */
1651
1668
  icon?: string;
@@ -2009,7 +2026,7 @@ type RpcRegistrationRecord = {
2009
2026
  publication: ReturnType<typeof publishRpcMethod>;
2010
2027
  inputSchema: StandardSchemaV1;
2011
2028
  outputSchema: StandardSchemaV1;
2012
- handler: (input: unknown) => unknown;
2029
+ handler: (input: unknown, context: ExperimentalPluginRpcHandlerContext) => unknown;
2013
2030
  };
2014
2031
  declare function normalizeRpcRegistration(contract: unknown, handlers: unknown, registered: ReadonlyMap<string, unknown>, options: unknown): Array<[string, RpcRegistrationRecord]>;
2015
2032
  declare function normalizeRealtimePayload(channel: string, payload: unknown): unknown;
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { ReactNode, ComponentType } from 'react';
9
9
  import { RenderResult } from '@testing-library/react';
10
- import { ExperimentalSidebarFooterItemRegistration, PluginProviderIconRegistration, PluginHomepageSectionRegistration, PluginSettingsSectionRegistration, ExperimentalAppOverlayRegistration, PluginNavPanelRegistration, PluginThreadPanelActionRegistration, PluginNewThreadPanelActionRegistration, ComposerCustomization, PluginPendingInteractionRegistration, PluginSidebarFooterActionRegistration, ExperimentalSidebarNavigationRegistration, ExperimentalSidebarHeaderRegistration, PluginThreadListRegistration, PluginThreadHeaderActionRegistration, ExperimentalPluginBrowserToolbarActionRegistration, PluginFileOpenerRegistration, PluginSourceCodeRendererRegistration, PluginDiffRendererRegistration, PluginMessageDirectiveRegistration, PluginMessageActionRegistration, ExperimentalIconRegistration, PluginTimelineRendererRegistration, PluginEnvironmentProviderInputsRegistration, PluginMachineProviderInputsRegistration, PluginContentScriptRegistration, PluginComposerScope, PluginComposerTextEffect, PluginComposerMention, ExperimentalComposerSubmitOptions, ExperimentalComposerSelection, PluginComposerThreadRowStatus, ExperimentalOpenFixedTabOptions, JsonValue, BbNavigate, ExperimentalFileOpenOptions, PluginAppDefinition, PluginRpcContract, StandardSchemaV1InferInput, PluginRpcResult, PluginBrowserBbSdk, PluginRealtimeConnectionState, PluginSidebarThreadsState, PluginProvidersState, PluginCodeThemeState, BranchesState, CheckoutState, PluginSidebarPullRequest, PluginSidebarThreadRowStatus, PluginSidebarThreadShortcut, PluginSidebarSplitLayout, ExperimentalSidebarNavigationItem, PluginEnvironmentProvidersState, PluginSidebarThreadActions, ExperimentalSidebarNavigationActions } from '@get-bb/plugin-sdk';
10
+ import { ExperimentalSidebarFooterItemRegistration, PluginProviderIconRegistration, PluginHomepageSectionRegistration, PluginSettingsSectionRegistration, ExperimentalAppOverlayRegistration, PluginNavPanelRegistration, PluginThreadPanelActionRegistration, PluginNewThreadPanelActionRegistration, ComposerCustomization, PluginPendingInteractionRegistration, PluginSidebarFooterActionRegistration, ExperimentalSidebarNavigationRegistration, ExperimentalSidebarHeaderRegistration, PluginThreadListRegistration, PluginThreadHeaderActionRegistration, ExperimentalPluginBrowserToolbarActionRegistration, PluginFileOpenerRegistration, PluginSourceCodeRendererRegistration, PluginDiffRendererRegistration, PluginMessageDirectiveRegistration, PluginMessageActionRegistration, ExperimentalIconRegistration, PluginTimelineRendererRegistration, PluginEnvironmentProviderInputsRegistration, PluginMachineProviderInputsRegistration, PluginContentScriptRegistration, ComposerDraftSnapshot, PluginComposerScope, ComposerSelection, PluginComposerTextEffect, PluginComposerMention, ComposerSubmitOptions, PluginComposerThreadRowStatus, ExperimentalOpenFixedTabOptions, JsonValue, BbNavigate, ExperimentalFileOpenOptions, PluginAppDefinition, PluginRpcContract, StandardSchemaV1InferInput, PluginRpcResult, PluginBrowserBbSdk, PluginRealtimeConnectionState, ComposerMention, ComposerAttachment, PluginSidebarThreadsState, PluginProvidersState, PluginCodeThemeState, BranchesState, CheckoutState, PluginSidebarPullRequest, PluginSidebarThreadRowStatus, PluginSidebarThreadShortcut, PluginSidebarSplitLayout, ExperimentalSidebarNavigationItem, PluginEnvironmentProvidersState, PluginSidebarThreadActions, ExperimentalSidebarNavigationActions } from '@get-bb/plugin-sdk';
11
11
 
12
12
  type ExperimentalSidebarFooterCommandKind = "close" | "open" | "toggle";
13
13
  interface ExperimentalSidebarFooterRuntimeSnapshot {
@@ -112,8 +112,17 @@ interface ExperimentalFixedTabOpenCall {
112
112
  interface ComposerLog {
113
113
  /** Latest plain text in this isolated composer scope. */
114
114
  readonly text: string;
115
+ /**
116
+ * Latest text and mention pills, as `useComposer().draft` reports them.
117
+ * The harness composer has no caret, so cursor inserts land at the end.
118
+ */
119
+ readonly draft: ComposerDraftSnapshot;
120
+ /** The key `useComposer().key` reports for the current scope. */
121
+ readonly key: string;
115
122
  /** Latest host-provided composer scope. */
116
123
  readonly scope: PluginComposerScope;
124
+ /** Current picker snapshot, or null for a composer without pickers. */
125
+ readonly selection: ComposerSelection | null;
117
126
  /** Latest host-provided attachment count exposed through `useComposerView()`. */
118
127
  readonly attachmentCount: number;
119
128
  /** Latest host-rendered text effect requested by the plugin. */
@@ -126,19 +135,19 @@ interface ComposerLog {
126
135
  mentions: PluginComposerMention[];
127
136
  focusCount: number;
128
137
  /**
129
- * Every `experimental_submit` the plugin ran, in order. The harness composer
130
- * has no submit pipeline of its own, so it records the options and clears the
138
+ * Every `submit` the plugin ran, in order. The harness composer has no
139
+ * submit pipeline of its own, so it records the options and clears the
131
140
  * draft — enough to assert what a picker scheduled and that it tidied up.
132
141
  */
133
- submits: ExperimentalComposerSubmitOptions[];
142
+ submits: ComposerSubmitOptions[];
134
143
  /**
135
- * Every `experimental_setSelection` the harness composer accepted, in
136
- * order. The harness has no pickers of its own, so it records the request
137
- * and echoes it back as the settled selection, minus the fields the
138
- * composer's scope has no picker for (a thread has no project or
139
- * environment). Queued-message and side-chat scopes reject, as the app does.
144
+ * Every `setSelection` the harness composer accepted, in order. The
145
+ * harness has no pickers of its own, so it merges accepted fields into
146
+ * its current selection and returns that snapshot. A thread drops
147
+ * project and environment because it has no pickers for them. The
148
+ * queued-message scope rejects, as the app does.
140
149
  */
141
- selections: ExperimentalComposerSelection[];
150
+ selections: ComposerSelection[];
142
151
  }
143
152
  /** One recorded `experimental_useSidebarThreadActions()` call. */
144
153
  interface SidebarActionCall {
@@ -264,8 +273,21 @@ interface RenderSlotOptions<Contract extends PluginRpcContract = PluginRpcContra
264
273
  /** Initial state for this render's isolated composer scope and view. */
265
274
  composer?: {
266
275
  text?: string;
276
+ /** Mention pills already in `text`, with ranges into it. */
277
+ mentions?: readonly ComposerMention[];
267
278
  scope?: PluginComposerScope;
268
279
  attachmentCount?: number;
280
+ attachments?: readonly ComposerAttachment[];
281
+ layout?: "compact" | "expanded";
282
+ isRunning?: boolean;
283
+ isSubmitting?: boolean;
284
+ selection?: ComposerSelection;
285
+ /**
286
+ * What `submittingBlockedReason` reports, and what `submit` rejects
287
+ * with. Omitted → "Type a message first." while the draft is empty,
288
+ * "Submitting..." while `isSubmitting`, otherwise null.
289
+ */
290
+ submittingBlockedReason?: string | null;
269
291
  };
270
292
  /**
271
293
  * Threads and projects `experimental_useSidebarThreads()` reports. Omitted →
@@ -5,7 +5,7 @@
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 { StandardSchemaV1 as StandardSchemaV1$1, StandardSchemaV1InferOutput, PluginMachineValidateDecision, JsonValue as JsonValue$2, PluginEnvironmentProviderRequirements as PluginEnvironmentProviderRequirements$1, PluginEnvironmentValidateDecision, BbPluginApi, PluginSettingValue as PluginSettingValue$1, PluginSharedPortTunnelIdentity, PluginRowPresentation, PluginAgentToolContext, PluginAgentToolResult, PluginCliCommandInfo, PluginCliContext, PluginCliResult, PluginHttpAuthMode, PluginHttpHandler, PluginMentionTrigger, PluginMentionSearchContext, PluginMentionItem, PluginMentionProviderRegistration, PluginCliExecutionResult, PluginThreadEventName, PluginThreadEventPayloads, PluginAgentConfigurationContext, ExperimentalPluginProviderEnvContext, ExperimentalPluginProviderEnvEntry, ExperimentalPluginProviderEnvHealthContext, ExperimentalPluginProviderEnvHealth, PluginSettingDescriptors, ExperimentalPluginWebSocketHandler, PluginAgentConfiguration, PluginHookName, PluginHookHandler, PluginAiServiceDeclaration, PluginInteractionRequest, MessageDispatchHookContext } from '@get-bb/plugin-sdk';
8
+ import { StandardSchemaV1 as StandardSchemaV1$1, StandardSchemaV1InferOutput, PluginMachineValidateDecision, JsonValue as JsonValue$2, PluginEnvironmentProviderRequirements as PluginEnvironmentProviderRequirements$1, PluginEnvironmentValidateDecision, BbPluginApi, PluginSettingValue as PluginSettingValue$1, PluginSharedPortTunnelIdentity, PluginRowPresentation, PluginAgentToolContext, PluginAgentToolResult, PluginCliCommandInfo, PluginCliContext, PluginCliResult, PluginHttpAuthMode, PluginHttpHandler, PluginMentionTrigger, PluginMentionSearchContext, PluginMentionItem, PluginMentionProviderRegistration, ExperimentalPluginRpcCaller, PluginCliExecutionResult, PluginThreadEventName, PluginThreadEventPayloads, PluginAgentConfigurationContext, ExperimentalPluginProviderEnvContext, ExperimentalPluginProviderEnvEntry, ExperimentalPluginProviderEnvHealthContext, ExperimentalPluginProviderEnvHealth, PluginSettingDescriptors, ExperimentalPluginWebSocketHandler, PluginAgentConfiguration, PluginHookName, PluginHookHandler, PluginAiServiceDeclaration, PluginInteractionRequest, MessageDispatchHookContext } from '@get-bb/plugin-sdk';
9
9
  import { z } from 'zod';
10
10
 
11
11
  type PluginMachineProviderResource = Exclude<JsonValue$2, null>;
@@ -1262,9 +1262,14 @@ interface FakePluginBehaviorDrivers {
1262
1262
  /**
1263
1263
  * Invoke a registered rpc method with host semantics: input/output schemas,
1264
1264
  * strict JSON result normalization, and structured failure codes. Rejects
1265
- * with the same message/code/issues the frontend client surfaces.
1265
+ * with the same message/code/issues the frontend client surfaces. The
1266
+ * handler sees `options.experimental_caller` as its caller, `{ kind:
1267
+ * "client" }` by default; pass `{ kind: "plugin", pluginId }` to act as
1268
+ * another plugin calling through `bb.sdk.plugins.callRpc`.
1266
1269
  */
1267
- callRpc(method: string, input?: unknown): Promise<unknown>;
1270
+ callRpc(method: string, input?: unknown, options?: {
1271
+ experimental_caller?: ExperimentalPluginRpcCaller;
1272
+ }): Promise<unknown>;
1268
1273
  /**
1269
1274
  * Invoke the plugin's CLI command with host semantics: the result's
1270
1275
  * exitCode must be a number, stdout/stderr default to "", and a throwing