@get-bb/plugin-sdk 0.4.40 → 0.4.47

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.
@@ -392,6 +392,127 @@ function normalizeExperimentalFileOpenOptions(value) {
392
392
  }
393
393
 
394
394
  // src/internal/plugin-app-collector.ts
395
+ var sidebarFooterCommandSequence = 0;
396
+ var SIDEBAR_FOOTER_ACTION_KEYS = /* @__PURE__ */ new Set([
397
+ "id",
398
+ "label",
399
+ "icon",
400
+ "kind",
401
+ "onActivate"
402
+ ]);
403
+ var SIDEBAR_FOOTER_DISCLOSURE_KEYS = /* @__PURE__ */ new Set([
404
+ "id",
405
+ "label",
406
+ "icon",
407
+ "kind",
408
+ "component"
409
+ ]);
410
+ var SidebarFooterItemRuntime = class {
411
+ listeners = /* @__PURE__ */ new Set();
412
+ snapshot = {
413
+ command: null
414
+ };
415
+ subscribe = (listener) => {
416
+ this.listeners.add(listener);
417
+ return () => this.listeners.delete(listener);
418
+ };
419
+ getSnapshot = () => this.snapshot;
420
+ acknowledgeCommand = (sequence) => {
421
+ if (this.snapshot.command?.sequence !== sequence) return;
422
+ this.snapshot = { ...this.snapshot, command: null };
423
+ this.emit();
424
+ };
425
+ createDisclosureController() {
426
+ return Object.freeze({
427
+ open: () => this.request("open"),
428
+ close: () => this.request("close"),
429
+ toggle: () => this.request("toggle")
430
+ });
431
+ }
432
+ request(kind) {
433
+ sidebarFooterCommandSequence += 1;
434
+ this.snapshot = {
435
+ ...this.snapshot,
436
+ command: { sequence: sidebarFooterCommandSequence, kind }
437
+ };
438
+ this.emit();
439
+ }
440
+ emit() {
441
+ for (const listener of this.listeners) listener();
442
+ }
443
+ };
444
+ function adaptSidebarFooterAction(registration) {
445
+ return {
446
+ source: "sidebarFooterAction",
447
+ id: registration.id,
448
+ label: registration.title,
449
+ icon: registration.icon,
450
+ kind: "action",
451
+ onActivate: ({ openPluginDetails }) => registration.run({ openSettings: openPluginDetails }),
452
+ runtime: new SidebarFooterItemRuntime()
453
+ };
454
+ }
455
+ var SidebarFooterCollector = class {
456
+ constructor(collected, allCollected, seenIds) {
457
+ this.collected = collected;
458
+ this.allCollected = allCollected;
459
+ this.seenIds = seenIds;
460
+ }
461
+ collected;
462
+ allCollected;
463
+ seenIds;
464
+ register(registration) {
465
+ const kind = "experimental_sidebarFooter.register";
466
+ const id = requireSlotId(kind, registration?.id);
467
+ requireUniqueId(kind, this.seenIds, id);
468
+ const label = requireNonEmptyString(kind, "label", registration.label);
469
+ const icon = requireNonEmptyString(kind, "icon", registration.icon);
470
+ const runtime = new SidebarFooterItemRuntime();
471
+ if (registration.kind === "action") {
472
+ this.rejectUnknownKeys(kind, registration, SIDEBAR_FOOTER_ACTION_KEYS);
473
+ if (typeof registration.onActivate !== "function") {
474
+ throw new Error(`${kind}: "onActivate" must be a function`);
475
+ }
476
+ const item = {
477
+ id,
478
+ label,
479
+ icon,
480
+ kind: "action",
481
+ onActivate: registration.onActivate,
482
+ source: "experimental_sidebarFooter",
483
+ runtime
484
+ };
485
+ this.collected.push(item);
486
+ this.allCollected.push(item);
487
+ return;
488
+ }
489
+ if (registration.kind === "disclosure") {
490
+ this.rejectUnknownKeys(
491
+ kind,
492
+ registration,
493
+ SIDEBAR_FOOTER_DISCLOSURE_KEYS
494
+ );
495
+ const item = {
496
+ id,
497
+ label,
498
+ icon,
499
+ kind: "disclosure",
500
+ component: requireComponent(kind, registration.component),
501
+ source: "experimental_sidebarFooter",
502
+ runtime
503
+ };
504
+ this.collected.push(item);
505
+ this.allCollected.push(item);
506
+ return runtime.createDisclosureController();
507
+ }
508
+ throw new Error(`${kind}: "kind" must be "action" or "disclosure"`);
509
+ }
510
+ rejectUnknownKeys(kind, registration, allowed) {
511
+ for (const key of Object.keys(registration)) {
512
+ if (!allowed.has(key)) throw new Error(`${kind}: unknown field "${key}"`);
513
+ }
514
+ }
515
+ };
395
516
  var NAV_PANEL_REGISTRATION_KEYS = new Set(
396
517
  Object.keys({
397
518
  id: true,
@@ -421,7 +542,9 @@ function rejectStaleNavPanelKeys(kind, registration) {
421
542
  }
422
543
  }
423
544
  }
545
+ var sidebarFooterItemsByRegistrationSet = /* @__PURE__ */ new WeakMap();
424
546
  function collectPluginAppRegistrations(definition, onComposerCustomizationRejected = (reason) => console.warn(reason)) {
547
+ const sidebarFooterItems = [];
425
548
  const collected = {
426
549
  homepageSections: [],
427
550
  settingsSections: [],
@@ -432,6 +555,7 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
432
555
  composerCustomizations: [],
433
556
  pendingInteractions: [],
434
557
  sidebarFooterActions: [],
558
+ experimentalSidebarFooterItems: [],
435
559
  experimentalSidebarNavigations: [],
436
560
  threadLists: [],
437
561
  threadHeaderActions: [],
@@ -445,6 +569,7 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
445
569
  timelineRenderers: [],
446
570
  contentScripts: []
447
571
  };
572
+ sidebarFooterItemsByRegistrationSet.set(collected, sidebarFooterItems);
448
573
  const seenIds = {
449
574
  homepageSection: /* @__PURE__ */ new Set(),
450
575
  settingsSection: /* @__PURE__ */ new Set(),
@@ -454,7 +579,7 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
454
579
  newThreadPanelAction: /* @__PURE__ */ new Set(),
455
580
  composerCustomization: /* @__PURE__ */ new Set(),
456
581
  pendingInteraction: /* @__PURE__ */ new Set(),
457
- sidebarFooterAction: /* @__PURE__ */ new Set(),
582
+ sidebarFooterItem: /* @__PURE__ */ new Set(),
458
583
  sidebarNavigation: /* @__PURE__ */ new Set(),
459
584
  threadList: /* @__PURE__ */ new Set(),
460
585
  threadHeaderAction: /* @__PURE__ */ new Set(),
@@ -645,16 +770,18 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
645
770
  sidebarFooterAction(registration) {
646
771
  const kind = "slots.sidebarFooterAction";
647
772
  const id = requireSlotId(kind, registration?.id);
648
- requireUniqueId(kind, seenIds.sidebarFooterAction, id);
773
+ requireUniqueId(kind, seenIds.sidebarFooterItem, id);
649
774
  if (typeof registration.run !== "function") {
650
775
  throw new Error(`${kind}: "run" must be a function`);
651
776
  }
652
- collected.sidebarFooterActions.push({
777
+ const legacyRegistration = {
653
778
  id,
654
779
  title: requireNonEmptyString(kind, "title", registration.title),
655
780
  icon: requireNonEmptyString(kind, "icon", registration.icon),
656
781
  run: registration.run
657
- });
782
+ };
783
+ collected.sidebarFooterActions.push(legacyRegistration);
784
+ sidebarFooterItems.push(adaptSidebarFooterAction(legacyRegistration));
658
785
  },
659
786
  experimental_sidebarNavigation(registration) {
660
787
  const kind = "slots.experimental_sidebarNavigation";
@@ -816,6 +943,11 @@ function collectPluginAppRegistrations(definition, onComposerCustomizationReject
816
943
  });
817
944
  }
818
945
  },
946
+ experimental_sidebarFooter: new SidebarFooterCollector(
947
+ collected.experimentalSidebarFooterItems,
948
+ sidebarFooterItems,
949
+ seenIds.sidebarFooterItem
950
+ ),
819
951
  composer: {
820
952
  customize(registration) {
821
953
  const customization = collectComposerCustomization(
@@ -293,6 +293,42 @@ var RPC_METHOD_PATTERN = /^[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*$/;
293
293
  var BACKGROUND_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
294
294
  var CLI_COMMAND_NAME_PATTERN = /^[a-z0-9-]+$/;
295
295
  var AGENT_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
296
+ var PLUGIN_PROVIDER_ENV_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
297
+ var PLUGIN_PROVIDER_ENV_MAX_ENTRIES = 32;
298
+ var pluginProviderEnvEntrySchema = z3.object({
299
+ name: z3.string().regex(PLUGIN_PROVIDER_ENV_NAME_PATTERN),
300
+ value: z3.union([
301
+ z3.string(),
302
+ z3.object({ serverPath: z3.string().startsWith("/") }).strict()
303
+ ]),
304
+ reason: z3.string(),
305
+ secret: z3.boolean()
306
+ }).strict();
307
+ var pluginProviderEnvEntriesSchema = z3.array(pluginProviderEnvEntrySchema).max(PLUGIN_PROVIDER_ENV_MAX_ENTRIES).superRefine((entries, context) => {
308
+ const names = /* @__PURE__ */ new Set();
309
+ for (let index = 0; index < entries.length; index += 1) {
310
+ const name = entries[index]?.name;
311
+ if (name !== void 0 && names.has(name)) {
312
+ context.addIssue({
313
+ code: "custom",
314
+ path: [index, "name"],
315
+ message: "must be unique within one resolver"
316
+ });
317
+ }
318
+ if (name !== void 0) names.add(name);
319
+ }
320
+ });
321
+ function validatePluginProviderEnvEntries(value) {
322
+ const parsed = pluginProviderEnvEntriesSchema.safeParse(value);
323
+ if (!parsed.success) {
324
+ const issue = parsed.error.issues[0];
325
+ const path = issue?.path.length ? `[${issue.path.join(".")}] ` : "";
326
+ throw new Error(
327
+ `provider environment contribution ${path}${issue?.message ?? "is invalid"}`
328
+ );
329
+ }
330
+ return parsed.data;
331
+ }
296
332
  var PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS = 4096;
297
333
  var PLUGIN_AGENT_STATUS_LABEL_MAX_CHARS = 80;
298
334
  var PLUGIN_AGENT_SELECTION_MAX_IDS = 256;
@@ -2174,7 +2210,9 @@ function createFakePluginHostInternal(options, sharedState) {
2174
2210
  "ALTER TABLE _bb_migrations ADD COLUMN statement_hash TEXT"
2175
2211
  );
2176
2212
  }
2177
- const rows = database.prepare("SELECT id, statement_hash FROM _bb_migrations ORDER BY id").all();
2213
+ const rows = database.prepare(
2214
+ "SELECT id, statement_hash FROM _bb_migrations ORDER BY id"
2215
+ ).all();
2178
2216
  const applied = /* @__PURE__ */ new Map();
2179
2217
  for (const row of rows) applied.set(row.id, row.statement_hash);
2180
2218
  const statementHashes = statements.map(migrationStatementHash);
@@ -2273,6 +2311,8 @@ function createFakePluginHostInternal(options, sharedState) {
2273
2311
  }
2274
2312
  };
2275
2313
  const httpRoutes = [];
2314
+ const websocketRoutes = [];
2315
+ const websocketSessions = /* @__PURE__ */ new Set();
2276
2316
  const http = {
2277
2317
  route(method, path, handler, opts) {
2278
2318
  assertLive();
@@ -2306,6 +2346,29 @@ function createFakePluginHostInternal(options, sharedState) {
2306
2346
  );
2307
2347
  }
2308
2348
  httpRoutes.push({ method: normalizedMethod, path, auth, handler });
2349
+ },
2350
+ experimental_websocket(path, handler, opts) {
2351
+ assertLive();
2352
+ if (typeof path !== "string" || !path.startsWith("/")) {
2353
+ throw new Error(
2354
+ `websocket route path must be a string starting with "/", got ${JSON.stringify(path)}`
2355
+ );
2356
+ }
2357
+ if (typeof handler !== "function") {
2358
+ throw new Error(
2359
+ `websocket route handler for ${path} must be a function`
2360
+ );
2361
+ }
2362
+ const auth = opts?.auth ?? "local";
2363
+ if (auth !== "local" && auth !== "token" && auth !== "none") {
2364
+ throw new Error(
2365
+ `invalid auth mode "${String(auth)}" for websocket ${path} \u2014 use "local", "token", or "none"`
2366
+ );
2367
+ }
2368
+ if (websocketRoutes.some((route) => route.path === path)) {
2369
+ throw new Error(`websocket route ${path} is already registered`);
2370
+ }
2371
+ websocketRoutes.push({ path, auth, handler });
2309
2372
  }
2310
2373
  };
2311
2374
  const rpcHandlers = /* @__PURE__ */ new Map();
@@ -2466,6 +2529,8 @@ function createFakePluginHostInternal(options, sharedState) {
2466
2529
  };
2467
2530
  const agentTools = [];
2468
2531
  const providerRegistrations = [];
2532
+ const providerEnvResolvers = /* @__PURE__ */ new Map();
2533
+ const providerEnvHealthResolvers = /* @__PURE__ */ new Map();
2469
2534
  let agentConfigurationProvider = null;
2470
2535
  let instructionProvider = null;
2471
2536
  function registerProviderDeclaration(declaration) {
@@ -2931,6 +2996,44 @@ function createFakePluginHostInternal(options, sharedState) {
2931
2996
  const providers = {
2932
2997
  register(declaration) {
2933
2998
  return registerProviderDeclaration(declaration);
2999
+ },
3000
+ experimental_contributeEnv(providerId, resolve2) {
3001
+ assertLive();
3002
+ if (typeof providerId !== "string" || providerId.trim().length === 0) {
3003
+ throw new Error(
3004
+ "provider environment contribution requires a provider id"
3005
+ );
3006
+ }
3007
+ if (providerEnvResolvers.has(providerId)) {
3008
+ throw new Error(
3009
+ `provider environment contribution for "${providerId}" is already registered`
3010
+ );
3011
+ }
3012
+ if (typeof resolve2 !== "function") {
3013
+ throw new Error(
3014
+ "provider environment contribution requires a resolver function"
3015
+ );
3016
+ }
3017
+ providerEnvResolvers.set(providerId, resolve2);
3018
+ },
3019
+ experimental_contributeEnvHealth(providerId, resolve2) {
3020
+ assertLive();
3021
+ if (typeof providerId !== "string" || providerId.trim().length === 0) {
3022
+ throw new Error(
3023
+ "provider environment health contribution requires a provider id"
3024
+ );
3025
+ }
3026
+ if (providerEnvHealthResolvers.has(providerId)) {
3027
+ throw new Error(
3028
+ `provider environment health contribution for "${providerId}" is already registered`
3029
+ );
3030
+ }
3031
+ if (typeof resolve2 !== "function") {
3032
+ throw new Error(
3033
+ "provider environment health contribution requires a resolver function"
3034
+ );
3035
+ }
3036
+ providerEnvHealthResolvers.set(providerId, resolve2);
2934
3037
  }
2935
3038
  };
2936
3039
  const experimental_hooks = {
@@ -2981,6 +3084,9 @@ function createFakePluginHostInternal(options, sharedState) {
2981
3084
  pendingInteractions.delete(id);
2982
3085
  pending.resolve({ outcome: "cancelled", reason: "plugin-disposed" });
2983
3086
  }
3087
+ for (const session of [...websocketSessions]) {
3088
+ await session.closeForReload();
3089
+ }
2984
3090
  for (const controller of serviceControllers) controller.abort();
2985
3091
  for (const hook of [...disposeHooks].reverse()) {
2986
3092
  try {
@@ -3026,6 +3132,7 @@ function createFakePluginHostInternal(options, sharedState) {
3026
3132
  registrations: {
3027
3133
  settingsDescriptors,
3028
3134
  httpRoutes,
3135
+ websocketRoutes,
3029
3136
  get rpcMethods() {
3030
3137
  return [...rpcHandlers.keys()];
3031
3138
  },
@@ -3060,6 +3167,8 @@ function createFakePluginHostInternal(options, sharedState) {
3060
3167
  },
3061
3168
  mentionProviders,
3062
3169
  providerRegistrations,
3170
+ providerEnvResolvers,
3171
+ providerEnvHealthResolvers,
3063
3172
  aiServiceRegistrations
3064
3173
  },
3065
3174
  get pendingInteractions() {
@@ -3077,6 +3186,43 @@ function createFakePluginHostInternal(options, sharedState) {
3077
3186
  await handler({ hostId });
3078
3187
  }
3079
3188
  },
3189
+ async resolveProviderEnv(providerId, context) {
3190
+ assertLive();
3191
+ const resolve2 = providerEnvResolvers.get(providerId);
3192
+ if (resolve2 === void 0) return [];
3193
+ try {
3194
+ return validatePluginProviderEnvEntries(await resolve2(context));
3195
+ } catch (error) {
3196
+ emitLog(
3197
+ "warn",
3198
+ `provider environment contribution failed: ${error instanceof Error ? error.message : String(error)}`
3199
+ );
3200
+ return [];
3201
+ }
3202
+ },
3203
+ async resolveProviderEnvHealth(providerId, context) {
3204
+ assertLive();
3205
+ if (!providerEnvResolvers.has(providerId)) return null;
3206
+ const resolve2 = providerEnvHealthResolvers.get(providerId);
3207
+ if (resolve2 === void 0) return null;
3208
+ try {
3209
+ const value = await resolve2(context);
3210
+ if (value === null) return null;
3211
+ if (value.label.trim().length === 0) {
3212
+ throw new Error("label must not be empty");
3213
+ }
3214
+ if (value.statusMessage.trim().length === 0) {
3215
+ throw new Error("statusMessage must not be empty");
3216
+ }
3217
+ return value;
3218
+ } catch (error) {
3219
+ emitLog(
3220
+ "warn",
3221
+ `provider environment health contribution failed: ${error instanceof Error ? error.message : String(error)}`
3222
+ );
3223
+ return null;
3224
+ }
3225
+ },
3080
3226
  async experimental_emitHostSignal(hostId, signal, payload) {
3081
3227
  assertLive();
3082
3228
  if (hostId.trim().length === 0) {
@@ -3204,6 +3350,127 @@ function createFakePluginHostInternal(options, sharedState) {
3204
3350
  });
3205
3351
  return app.request(path, { ...init, method: normalizedMethod });
3206
3352
  },
3353
+ async experimental_openWebSocket(path, init) {
3354
+ assertLive();
3355
+ const url = new URL(path, "http://plugin.test");
3356
+ const route = websocketRoutes.find(
3357
+ (candidate) => candidate.path === url.pathname
3358
+ );
3359
+ if (!route) {
3360
+ throw new Error(
3361
+ `no websocket route ${url.pathname} is registered \u2014 registered: ${websocketRoutes.map((candidate) => candidate.path).join(", ") || "(none)"}`
3362
+ );
3363
+ }
3364
+ const request = new Request(url, { ...init, method: "GET" });
3365
+ let handlers;
3366
+ try {
3367
+ handlers = route.handler({
3368
+ request,
3369
+ url,
3370
+ headers: request.headers
3371
+ });
3372
+ if (typeof handlers !== "object" || handlers === null || Array.isArray(handlers)) {
3373
+ throw new Error("websocket route handler must return an object");
3374
+ }
3375
+ for (const name of [
3376
+ "onOpen",
3377
+ "onMessage",
3378
+ "onClose",
3379
+ "onError"
3380
+ ]) {
3381
+ const callback = handlers[name];
3382
+ if (callback !== void 0 && typeof callback !== "function") {
3383
+ throw new Error(
3384
+ `websocket route handler ${name} must be a function`
3385
+ );
3386
+ }
3387
+ }
3388
+ } catch (error) {
3389
+ emitLog(
3390
+ "warn",
3391
+ `websocket ${route.path} connect failed: ${errorMessage(error)}`
3392
+ );
3393
+ throw error;
3394
+ }
3395
+ const sent = [];
3396
+ const closeCalls = [];
3397
+ let readyState = 0;
3398
+ let closeNotified = false;
3399
+ let eventQueue = Promise.resolve();
3400
+ const invoke = async (event, run) => {
3401
+ eventQueue = eventQueue.then(async () => {
3402
+ try {
3403
+ await run();
3404
+ } catch (error) {
3405
+ emitLog(
3406
+ "warn",
3407
+ `websocket ${route.path} ${event} failed: ${errorMessage(error)}`
3408
+ );
3409
+ }
3410
+ });
3411
+ await eventQueue;
3412
+ };
3413
+ const socket = {
3414
+ send(data) {
3415
+ sent.push(typeof data === "string" ? data : new Uint8Array(data));
3416
+ },
3417
+ close(code, reason) {
3418
+ closeCalls.push({ code: code ?? null, reason: reason ?? null });
3419
+ if (readyState < 2) readyState = 2;
3420
+ },
3421
+ get readyState() {
3422
+ return readyState;
3423
+ }
3424
+ };
3425
+ const notifyClose = async (code, reason) => {
3426
+ if (closeNotified) return;
3427
+ closeNotified = true;
3428
+ readyState = 3;
3429
+ websocketSessions.delete(session);
3430
+ if (handlers.onClose !== void 0) {
3431
+ await invoke(
3432
+ "close",
3433
+ () => handlers.onClose?.(socket, { code, reason })
3434
+ );
3435
+ }
3436
+ };
3437
+ const session = {
3438
+ sent,
3439
+ closeCalls,
3440
+ get readyState() {
3441
+ return readyState;
3442
+ },
3443
+ async receive(data) {
3444
+ if (readyState !== 1) {
3445
+ throw new Error(
3446
+ "cannot receive a websocket message while not open"
3447
+ );
3448
+ }
3449
+ if (handlers.onMessage !== void 0) {
3450
+ await invoke("message", () => handlers.onMessage?.(socket, data));
3451
+ }
3452
+ },
3453
+ async close(code = 1e3, reason = "") {
3454
+ if (readyState < 2) socket.close(code, reason);
3455
+ await notifyClose(code, reason);
3456
+ },
3457
+ async error(error) {
3458
+ if (handlers.onError !== void 0) {
3459
+ await invoke("error", () => handlers.onError?.(socket, error));
3460
+ }
3461
+ },
3462
+ async closeForReload() {
3463
+ socket.close(1012, "Plugin reloaded or disabled");
3464
+ await notifyClose(1012, "Plugin reloaded or disabled");
3465
+ }
3466
+ };
3467
+ websocketSessions.add(session);
3468
+ readyState = 1;
3469
+ if (handlers.onOpen !== void 0) {
3470
+ await invoke("open", () => handlers.onOpen?.(socket));
3471
+ }
3472
+ return session;
3473
+ },
3207
3474
  runService(name) {
3208
3475
  const service = services.find((record) => record.name === name);
3209
3476
  if (!service) {
@@ -3348,6 +3615,151 @@ function makeThreadResponse(overrides = {}) {
3348
3615
  ...overrides
3349
3616
  };
3350
3617
  }
3618
+ function makePluginAgentConfigurationContext(overrides = {}) {
3619
+ const context = {
3620
+ thread: {
3621
+ id: "thread-test",
3622
+ title: null,
3623
+ parentThreadId: null,
3624
+ sourceThreadId: null
3625
+ },
3626
+ project: {
3627
+ id: "project-test",
3628
+ kind: "standard",
3629
+ name: "Test project",
3630
+ gitRemoteUrl: null
3631
+ },
3632
+ environment: {
3633
+ id: "environment-test",
3634
+ name: null,
3635
+ path: "/tmp/test",
3636
+ workspaceProvisionType: "unmanaged",
3637
+ branchName: null
3638
+ },
3639
+ host: { id: "host-test", name: "Test host" },
3640
+ provider: {
3641
+ id: "test-provider",
3642
+ model: "test-model",
3643
+ capabilities: { supportsNativeUserQuestion: false }
3644
+ },
3645
+ origin: { kind: null, pluginId: null }
3646
+ };
3647
+ return {
3648
+ thread: { ...context.thread, ...overrides.thread },
3649
+ project: { ...context.project, ...overrides.project },
3650
+ environment: { ...context.environment, ...overrides.environment },
3651
+ host: { ...context.host, ...overrides.host },
3652
+ provider: {
3653
+ ...context.provider,
3654
+ ...overrides.provider,
3655
+ capabilities: {
3656
+ ...context.provider.capabilities,
3657
+ ...overrides.provider?.capabilities
3658
+ }
3659
+ },
3660
+ origin: { ...context.origin, ...overrides.origin }
3661
+ };
3662
+ }
3663
+ function makeMessageDispatchHookContext(overrides = {}) {
3664
+ const context = {
3665
+ thread: makeThreadResponse({ status: "pending" }),
3666
+ project: {
3667
+ id: "project-1",
3668
+ kind: "standard",
3669
+ name: "Test project",
3670
+ gitRemoteUrl: null,
3671
+ createdAt: 0,
3672
+ updatedAt: 0
3673
+ },
3674
+ environment: null,
3675
+ host: null,
3676
+ input: { blocks: [], text: "Test message" },
3677
+ requestedExecution: {
3678
+ providerId: "test-provider",
3679
+ model: null,
3680
+ reasoningLevel: null,
3681
+ serviceTier: null,
3682
+ permissionMode: null
3683
+ },
3684
+ executionSources: {
3685
+ providerId: null,
3686
+ model: null,
3687
+ reasoningLevel: null,
3688
+ serviceTier: null,
3689
+ permissionMode: null
3690
+ },
3691
+ attempt: "start-turn",
3692
+ queuedMessage: null,
3693
+ origin: null,
3694
+ originPluginId: null,
3695
+ startedOnBehalfOf: null,
3696
+ parentThreadId: null
3697
+ };
3698
+ const environmentDefaults = {
3699
+ id: "environment-1",
3700
+ name: null,
3701
+ projectId: "project-1",
3702
+ hostId: "host-1",
3703
+ path: "/tmp/test",
3704
+ managed: true,
3705
+ isGitRepo: true,
3706
+ isWorktree: false,
3707
+ workspaceProvisionType: "unmanaged",
3708
+ branchName: "main",
3709
+ baseBranch: null,
3710
+ defaultBranch: "main",
3711
+ mergeBaseBranch: null,
3712
+ status: "ready",
3713
+ createdAt: 0,
3714
+ updatedAt: 0
3715
+ };
3716
+ const hostDefaults = {
3717
+ id: "host-1",
3718
+ name: "Test host",
3719
+ type: "persistent",
3720
+ status: "connected",
3721
+ maxPermissionMode: "full",
3722
+ lastSeenAt: null,
3723
+ lastRejectedProtocolVersion: null,
3724
+ createdAt: 0,
3725
+ updatedAt: 0
3726
+ };
3727
+ const project = { ...context.project, ...overrides.project };
3728
+ const host = overrides.host === void 0 ? context.host : overrides.host === null ? null : { ...hostDefaults, ...overrides.host };
3729
+ const environment = overrides.environment === void 0 ? context.environment : overrides.environment === null ? null : {
3730
+ ...environmentDefaults,
3731
+ projectId: project.id,
3732
+ hostId: host?.id ?? environmentDefaults.hostId,
3733
+ ...overrides.environment
3734
+ };
3735
+ const thread = {
3736
+ ...context.thread,
3737
+ projectId: project.id,
3738
+ environmentId: environment?.id ?? null,
3739
+ ...overrides.thread
3740
+ };
3741
+ return {
3742
+ ...context,
3743
+ ...overrides,
3744
+ thread,
3745
+ project,
3746
+ environment,
3747
+ host,
3748
+ input: { ...context.input, ...overrides.input },
3749
+ requestedExecution: {
3750
+ ...context.requestedExecution,
3751
+ ...overrides.requestedExecution
3752
+ },
3753
+ executionSources: {
3754
+ ...context.executionSources,
3755
+ ...overrides.executionSources
3756
+ },
3757
+ queuedMessage: overrides.queuedMessage === void 0 ? context.queuedMessage : overrides.queuedMessage === null ? null : makeQueueEntry({
3758
+ threadId: thread.id,
3759
+ ...overrides.queuedMessage
3760
+ })
3761
+ };
3762
+ }
3351
3763
  function makeQueueEntry(overrides = {}) {
3352
3764
  return {
3353
3765
  id: "queued_1",
@@ -3485,6 +3897,8 @@ export {
3485
3897
  createFakePluginHost,
3486
3898
  createFakeSdk,
3487
3899
  scanPublicSdkOnly as experimental_scanPublicSdkOnly,
3900
+ makeMessageDispatchHookContext,
3901
+ makePluginAgentConfigurationContext,
3488
3902
  makeQueueEntry,
3489
3903
  makeThreadResponse,
3490
3904
  makeTurnFailedEvent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@get-bb/plugin-sdk",
3
- "version": "0.4.40",
3
+ "version": "0.4.47",
4
4
  "homepage": "https://github.com/get-bb/bb#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/get-bb/bb/issues"