@get-bb/plugin-sdk 0.4.16 → 0.4.18

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.
@@ -9,8 +9,8 @@ import { z } from 'zod';
9
9
 
10
10
  /**
11
11
  * Core `bb` CLI top-level command names (plus commander's built-in help).
12
- * Plugin CLI commands may not shadow these. Maintained by hand and checked
13
- * against the real Commander program by
12
+ * Core commands win these names; plugin scaffolding rejects them. Maintained
13
+ * by hand and checked against the real Commander program by
14
14
  * apps/cli/src/__tests__/plugin-cli-proxy.test.ts.
15
15
  *
16
16
  * "automation" and "connect" are intentionally absent: builtin plugins own
@@ -583,6 +583,7 @@ interface PluginAiServiceDeclaration {
583
583
  readonly kinds: readonly PluginAiServiceKind[];
584
584
  }
585
585
 
586
+ declare function pluginCliCollisionWarning(pluginId: string, commandName: string): string | null;
586
587
  /**
587
588
  * Built-in dynamic tool names plugins may not shadow. Maintained by hand —
588
589
  * kept in sync with the built-in tools in
@@ -789,5 +790,5 @@ declare function agentToolIconRefusalMessage(toolName: string, problem: string):
789
790
  */
790
791
  declare function providerWithoutBridgeMessage(providerId: string): string;
791
792
 
792
- export { AGENT_TOOL_NAME_PATTERN, BACKGROUND_NAME_PATTERN, CLI_COMMAND_NAME_PATTERN, KV_VALUE_MAX_BYTES, MENTION_PROVIDER_ID_PATTERN, PLUGIN_AGENT_DYNAMIC_INSTRUCTIONS_MAX_CHARS, PLUGIN_AGENT_SELECTION_MAX_IDS, PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS, PLUGIN_AGENT_STATUS_LABEL_MAX_CHARS, PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES, PLUGIN_HTTP_METHODS, PLUGIN_MENTION_TRIGGER_VALUES, PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES, PLUGIN_PROVIDER_COMPOSER_ACTION_VALUES, PLUGIN_PROVIDER_DISPLAY_NAME_MAX_CHARS, PLUGIN_PROVIDER_PERMISSION_MODE_VALUES, PLUGIN_PROVIDER_REASONING_LEVEL_VALUES, PROVIDER_ID_PATTERN, RESERVED_AGENT_TOOL_NAMES, RESERVED_BB_CLI_COMMANDS, RPC_METHOD_PATTERN, SERVER_DIRECT_AI_SERVICE_IDS, SETTING_KEY_PATTERN, adoptHttpRouteResponse, agentToolIconRefusalMessage, aiServiceAlreadyRegisteredMessage, assertAiServiceRegistrable, assertNoRecursiveJsonSchemaReferences, deriveValidatedProviderOptions, enforcePluginCliOutputLimit, isPluginMentionTrigger, isStandardSchema, isZodSchemaLike, normalizeMentionProviderTriggers, parsePluginAgentToolPresentation, providerAlreadyRegisteredMessage, providerIconRefusalMessage, providerWithoutBridgeMessage, readRpcMethodContract, registerSettingDescriptors, rejectStaleAgentToolFields, summarizeParseIssues, undeclaredIconProblem, validatePluginAiServiceDeclaration, validatePluginProviderDeclaration, validateSettingsUpdate };
793
+ export { AGENT_TOOL_NAME_PATTERN, BACKGROUND_NAME_PATTERN, CLI_COMMAND_NAME_PATTERN, KV_VALUE_MAX_BYTES, MENTION_PROVIDER_ID_PATTERN, PLUGIN_AGENT_DYNAMIC_INSTRUCTIONS_MAX_CHARS, PLUGIN_AGENT_SELECTION_MAX_IDS, PLUGIN_AGENT_STATIC_INSTRUCTIONS_MAX_CHARS, PLUGIN_AGENT_STATUS_LABEL_MAX_CHARS, PLUGIN_AGENT_TOOL_PARAMETERS_MAX_BYTES, PLUGIN_HTTP_METHODS, PLUGIN_MENTION_TRIGGER_VALUES, PLUGIN_PROVIDER_BRIDGE_OPTIONS_MAX_BYTES, PLUGIN_PROVIDER_COMPOSER_ACTION_VALUES, PLUGIN_PROVIDER_DISPLAY_NAME_MAX_CHARS, PLUGIN_PROVIDER_PERMISSION_MODE_VALUES, PLUGIN_PROVIDER_REASONING_LEVEL_VALUES, PROVIDER_ID_PATTERN, RESERVED_AGENT_TOOL_NAMES, RESERVED_BB_CLI_COMMANDS, RPC_METHOD_PATTERN, SERVER_DIRECT_AI_SERVICE_IDS, SETTING_KEY_PATTERN, adoptHttpRouteResponse, agentToolIconRefusalMessage, aiServiceAlreadyRegisteredMessage, assertAiServiceRegistrable, assertNoRecursiveJsonSchemaReferences, deriveValidatedProviderOptions, enforcePluginCliOutputLimit, isPluginMentionTrigger, isStandardSchema, isZodSchemaLike, normalizeMentionProviderTriggers, parsePluginAgentToolPresentation, pluginCliCollisionWarning, providerAlreadyRegisteredMessage, providerIconRefusalMessage, providerWithoutBridgeMessage, readRpcMethodContract, registerSettingDescriptors, rejectStaleAgentToolFields, summarizeParseIssues, undeclaredIconProblem, validatePluginAiServiceDeclaration, validatePluginProviderDeclaration, validateSettingsUpdate };
793
794
  export type { AiServiceHostBinding, NormalizedPluginProviderDeclaration };
@@ -467,6 +467,12 @@ interface AcpClassifiedToolCall {
467
467
  item: DeltaItemShape;
468
468
  presentation: DeltaPresentation;
469
469
  }
470
+ interface AcpCommandResult {
471
+ /** The process exit code the agent reported; absent when it reported none. */
472
+ exitCode?: number;
473
+ /** The command's output text; absent when the agent reported none. */
474
+ output?: string;
475
+ }
470
476
 
471
477
  /**
472
478
  * Per-agent dialects: the vendor side channels of an ACP agent.
@@ -525,6 +531,17 @@ interface AcpDialect {
525
531
  * the shared classifier in charge — which is the normal answer.
526
532
  */
527
533
  classifyToolCall?(event: AcpToolCallUpdateEvent): AcpClassifiedToolCall | undefined;
534
+ /**
535
+ * A command result carried in the agent's non-standard rawOutput shape.
536
+ * Returning `undefined` leaves the shared ACP result parser in charge.
537
+ */
538
+ commandResult?(event: AcpToolCallUpdateEvent): AcpCommandResult | undefined;
539
+ /**
540
+ * A command event with the agent's non-standard result fields normalized
541
+ * into the shared ACP result shapes. The hook runs only after the call has
542
+ * classified as a command; existing shared result fields must win.
543
+ */
544
+ normalizeCommandEvent?(event: AcpToolCallUpdateEvent): AcpToolCallUpdateEvent;
528
545
  /**
529
546
  * A vendor JSON-RPC request the agent sends to the client. A dialect that
530
547
  * answers one returns the JSON-RPC result to reply with (`{}` is a valid
@@ -7865,6 +7865,7 @@ declare const pluginCatalogSearchResultSchema: z$1.ZodObject<{
7865
7865
  iconUrl: z$1.ZodNullable<z$1.ZodString>;
7866
7866
  incompatibleReason: z$1.ZodNullable<z$1.ZodString>;
7867
7867
  installed: z$1.ZodBoolean;
7868
+ installs: z$1.ZodDefault<z$1.ZodNullable<z$1.ZodNumber>>;
7868
7869
  marketplace: z$1.ZodString;
7869
7870
  marketplaceDisplayName: z$1.ZodString;
7870
7871
  official: z$1.ZodBoolean;
@@ -10233,6 +10234,15 @@ declare const sendMessageRequestSchema: z$1.ZodObject<{
10233
10234
  }>>;
10234
10235
  }, z$1.core.$strip>;
10235
10236
  type SendMessageRequest = z$1.infer<typeof sendMessageRequestSchema>;
10237
+ declare const sendMessageResponseSchema: z$1.ZodObject<{
10238
+ delivery: z$1.ZodEnum<{
10239
+ deferred: "deferred";
10240
+ queued: "queued";
10241
+ sent: "sent";
10242
+ }>;
10243
+ ok: z$1.ZodLiteral<true>;
10244
+ }, z$1.core.$strip>;
10245
+ type SendMessageResponse = z$1.infer<typeof sendMessageResponseSchema>;
10236
10246
  declare const editMessageRequestSchema: z$1.ZodObject<{
10237
10247
  executionInputSources: z$1.ZodOptional<z$1.ZodObject<{
10238
10248
  model: z$1.ZodOptional<z$1.ZodEnum<{
@@ -15194,9 +15204,7 @@ type ThreadPaneActionResult = ThreadPaneActionResponse;
15194
15204
  type ThreadDeleteResult = {
15195
15205
  ok: true;
15196
15206
  };
15197
- type ThreadSendResult = {
15198
- ok: true;
15199
- };
15207
+ type ThreadSendResult = SendMessageResponse;
15200
15208
  type ThreadEditMessageResult = EditMessageResponse;
15201
15209
  type ThreadStopResult = {
15202
15210
  ok: true;
@@ -15851,8 +15859,9 @@ interface PluginCliExecutionResult {
15851
15859
  error?: PluginCliOutputLimitError;
15852
15860
  }
15853
15861
  interface PluginCliRegistration {
15854
- /** Top-level command name (`bb <name> …`): lowercase [a-z0-9-]+, and not
15855
- * a core bb command (see RESERVED_BB_CLI_COMMANDS in the server). */
15862
+ /** Preferred top-level command name (`bb <name> …`): lowercase [a-z0-9-]+.
15863
+ * A core collision logs an activation warning and remains available through
15864
+ * `bb plugin run <plugin-id>`. */
15856
15865
  name: string;
15857
15866
  summary: string;
15858
15867
  /** Subcommand metadata rendered in help and the plugin-commands skill
@@ -15864,7 +15873,7 @@ interface PluginCli {
15864
15873
  /**
15865
15874
  * Register this plugin's `bb` subcommand. One registration per factory
15866
15875
  * execution; a repeated call is rejected. Core bb commands always win
15867
- * name collisions; reserved names are rejected at registration.
15876
+ * name collisions; the plugin is warned and remains explicitly callable by id.
15868
15877
  */
15869
15878
  register(registration: PluginCliRegistration): void;
15870
15879
  }
@@ -24,16 +24,23 @@ function parseNamespacedGlyph(glyph) {
24
24
  // ../domain/src/plugin-cli.ts
25
25
  var RESERVED_BB_CLI_COMMANDS = [
26
26
  "environment",
27
+ "file",
27
28
  "guide",
28
29
  "help",
30
+ "machine",
29
31
  "manager",
32
+ "marketplace",
30
33
  "plugin",
31
34
  "project",
32
35
  "provider",
36
+ "settings",
33
37
  "skill",
34
38
  "status",
39
+ "terminal",
35
40
  "theme",
36
- "thread"
41
+ "thread",
42
+ "updates",
43
+ "voice"
37
44
  ];
38
45
 
39
46
  // ../domain/src/provider-fork.ts
@@ -252,6 +259,10 @@ var providerNativeRootSetSchema = z2.object({
252
259
  var PLUGIN_CLI_OUTPUT_MAX_BYTES = 1024 * 1024;
253
260
 
254
261
  // src/internal/host-policy.ts
262
+ function pluginCliCollisionWarning(pluginId, commandName) {
263
+ if (!RESERVED_BB_CLI_COMMANDS.includes(commandName)) return null;
264
+ return `CLI command "${commandName}" collides with core command "bb ${commandName}"; core keeps the short form. Use "bb plugin run ${pluginId}" to invoke this plugin.`;
265
+ }
255
266
  var RESERVED_AGENT_TOOL_NAMES = [
256
267
  "update_environment_directory"
257
268
  ];
@@ -1646,6 +1657,7 @@ export {
1646
1657
  isZodSchemaLike,
1647
1658
  normalizeMentionProviderTriggers,
1648
1659
  parsePluginAgentToolPresentation,
1660
+ pluginCliCollisionWarning,
1649
1661
  providerAlreadyRegisteredMessage,
1650
1662
  providerIconRefusalMessage,
1651
1663
  providerWithoutBridgeMessage,
@@ -3116,7 +3116,7 @@ var deltaItemShapeSchema = z24.discriminatedUnion("type", [
3116
3116
  }),
3117
3117
  z24.object({
3118
3118
  type: z24.literal("fileChange"),
3119
- /** Empty only on bare close-without-open fallbacks (path unknown). */
3119
+ /** Empty while a path is not yet known, including bare close fallbacks. */
3120
3120
  changes: z24.array(deltaFileChangeSchema)
3121
3121
  }),
3122
3122
  /**
@@ -5235,13 +5235,126 @@ var CURSOR_ACP_DIALECT = {
5235
5235
  handleClientRequest: cursorHandleClientRequest,
5236
5236
  maintenance: CURSOR_ACP_MAINTENANCE
5237
5237
  };
5238
+ var ompBashRawInputSchema = z35.object({
5239
+ command: z35.string(),
5240
+ async: z35.boolean().optional()
5241
+ }).passthrough();
5242
+ var ompBashRawOutputSchema = z35.object({
5243
+ content: z35.array(
5244
+ z35.object({
5245
+ type: z35.literal("text"),
5246
+ text: z35.string()
5247
+ }).passthrough()
5248
+ ),
5249
+ details: z35.object({
5250
+ exitCode: z35.number().int().optional(),
5251
+ wallTimeMs: z35.number().nonnegative().optional(),
5252
+ timedOut: z35.boolean().optional(),
5253
+ signal: z35.unknown().optional(),
5254
+ async: z35.unknown().optional()
5255
+ }).passthrough(),
5256
+ // If an OMP version emits generic ACP result fields, the shared parser
5257
+ // owns those exit/output/timeout/signal semantics.
5258
+ exitCode: z35.number().int().nullable().optional(),
5259
+ exit_code: z35.number().int().nullable().optional(),
5260
+ stdout: z35.string().optional(),
5261
+ stderr: z35.string().optional(),
5262
+ output_for_prompt: z35.string().optional(),
5263
+ signal: z35.string().nullable().optional(),
5264
+ timed_out: z35.boolean().optional()
5265
+ }).passthrough();
5266
+ function stripOmpTrailingNotice(text, notice) {
5267
+ const suffix = `
5268
+
5269
+ ${notice}`;
5270
+ return text.endsWith(suffix) ? text.slice(0, -suffix.length) : text;
5271
+ }
5272
+ function ompCommandResult(event) {
5273
+ if (event.kind !== "execute") {
5274
+ return void 0;
5275
+ }
5276
+ const parsedInput = ompBashRawInputSchema.safeParse(event.rawInput);
5277
+ const parsedOutput = ompBashRawOutputSchema.safeParse(event.rawOutput);
5278
+ if (!parsedInput.success || parsedInput.data.command.trim().length === 0 || !parsedOutput.success) {
5279
+ return void 0;
5280
+ }
5281
+ const rawOutput = parsedOutput.data;
5282
+ const details = rawOutput.details;
5283
+ const hasGenericCommandResult = rawOutput.exitCode !== void 0 || rawOutput.exit_code !== void 0 || rawOutput.stdout !== void 0 || rawOutput.stderr !== void 0 || rawOutput.output_for_prompt !== void 0 || rawOutput.signal !== void 0 && rawOutput.signal !== null || rawOutput.timed_out === true;
5284
+ if (parsedInput.data.async === true || details.async !== void 0 || hasGenericCommandResult) {
5285
+ return void 0;
5286
+ }
5287
+ if (details.exitCode === void 0 && details.wallTimeMs === void 0) {
5288
+ return void 0;
5289
+ }
5290
+ let output = rawOutput.content.map((block) => block.text).join("\n");
5291
+ if (details.exitCode !== void 0) {
5292
+ output = stripOmpTrailingNotice(
5293
+ output,
5294
+ `Command exited with code ${String(details.exitCode)}`
5295
+ );
5296
+ }
5297
+ if (details.wallTimeMs !== void 0) {
5298
+ output = stripOmpTrailingNotice(
5299
+ output,
5300
+ `Wall time: ${(details.wallTimeMs / 1e3).toFixed(2)} seconds`
5301
+ );
5302
+ }
5303
+ const isCompletedForegroundBash = event.status === "completed" && details.timedOut !== true && (details.signal === void 0 || details.signal === null);
5304
+ const exitCode = details.exitCode ?? (isCompletedForegroundBash ? 0 : void 0);
5305
+ return {
5306
+ ...exitCode === void 0 ? {} : { exitCode },
5307
+ ...output.length === 0 ? {} : { output }
5308
+ };
5309
+ }
5310
+ var OMP_ACP_DIALECT = {
5311
+ id: "omp",
5312
+ commandResult: ompCommandResult
5313
+ };
5314
+ var openCodeCommandRawOutputSchema = z35.object({
5315
+ output: z35.unknown().optional(),
5316
+ metadata: z35.object({
5317
+ exit: z35.number().int().nullable().optional(),
5318
+ output: z35.string().optional()
5319
+ }).passthrough().optional()
5320
+ }).passthrough();
5321
+ function normalizeOpenCodeCommandEvent(event) {
5322
+ const parsed = openCodeCommandRawOutputSchema.safeParse(event.rawOutput);
5323
+ if (!parsed.success) {
5324
+ return event;
5325
+ }
5326
+ const rawOutput = parsed.data;
5327
+ const output = typeof rawOutput.output === "string" ? rawOutput.output : rawOutput.metadata?.output;
5328
+ const hasSharedOutput = rawOutput["stdout"] !== void 0 || rawOutput["stderr"] !== void 0 || rawOutput["output_for_prompt"] !== void 0;
5329
+ const hasSharedExitCode = rawOutput["exitCode"] !== void 0 || rawOutput["exit_code"] !== void 0;
5330
+ const exitCode = rawOutput.metadata?.exit ?? void 0;
5331
+ if ((output === void 0 || hasSharedOutput) && (exitCode === void 0 || hasSharedExitCode)) {
5332
+ return event;
5333
+ }
5334
+ return {
5335
+ ...event,
5336
+ rawOutput: {
5337
+ ...rawOutput,
5338
+ ...output === void 0 || hasSharedOutput ? {} : { stdout: output },
5339
+ ...exitCode === void 0 || hasSharedExitCode ? {} : { exitCode }
5340
+ }
5341
+ };
5342
+ }
5343
+ var OPENCODE_ACP_DIALECT = {
5344
+ id: "opencode",
5345
+ normalizeCommandEvent: normalizeOpenCodeCommandEvent
5346
+ };
5238
5347
  var DIALECTS_BY_ID = /* @__PURE__ */ new Map([
5239
5348
  [CURSOR_ACP_DIALECT.id, CURSOR_ACP_DIALECT],
5240
- [GROK_ACP_DIALECT.id, GROK_ACP_DIALECT]
5349
+ [GROK_ACP_DIALECT.id, GROK_ACP_DIALECT],
5350
+ [OMP_ACP_DIALECT.id, OMP_ACP_DIALECT],
5351
+ [OPENCODE_ACP_DIALECT.id, OPENCODE_ACP_DIALECT]
5241
5352
  ]);
5242
5353
  var DIALECT_IDS_BY_COMMAND = {
5243
5354
  "cursor-agent": CURSOR_ACP_DIALECT.id,
5244
- grok: GROK_ACP_DIALECT.id
5355
+ grok: GROK_ACP_DIALECT.id,
5356
+ omp: OMP_ACP_DIALECT.id,
5357
+ opencode: OPENCODE_ACP_DIALECT.id
5245
5358
  };
5246
5359
  function resolveAcpDialect(launch) {
5247
5360
  if (launch.dialectId !== void 0) {
@@ -5317,9 +5430,6 @@ function classifyAcpToolCall(event, options) {
5317
5430
  }
5318
5431
  }
5319
5432
  const paths = extractAcpToolCallPaths(event, options);
5320
- if (paths.length === 0) {
5321
- return { kind: "generic" };
5322
- }
5323
5433
  const hasDiff = (event.content ?? []).some((entry) => entry.type === "diff");
5324
5434
  if (hasDiff || event.kind === "edit") {
5325
5435
  return { kind: "file_change", changeKind: "update", paths };
@@ -5553,7 +5663,10 @@ function urlFromTitle(title) {
5553
5663
  function looksLikePath(token) {
5554
5664
  return token.startsWith("/") || token.startsWith("~") || token.startsWith(".");
5555
5665
  }
5556
- function fileChangeVerb(changes) {
5666
+ function fileChangeVerb(changes, fallback) {
5667
+ if (changes.length === 0) {
5668
+ return fallback;
5669
+ }
5557
5670
  if (changes.every((change) => change.kind === "add")) {
5558
5671
  return "add";
5559
5672
  }
@@ -5582,11 +5695,11 @@ function buildAcpFileChanges(event, operation, options) {
5582
5695
  const [path5] = operation.paths;
5583
5696
  return path5 === void 0 ? [] : [{ path: path5, kind: operation.changeKind }];
5584
5697
  }
5585
- function fileChangeItem(changes) {
5698
+ function fileChangeItem(changes, fallbackVerb) {
5586
5699
  return {
5587
5700
  item: { type: "fileChange", changes },
5588
5701
  presentation: fileChangePresentation({
5589
- verb: fileChangeVerb(changes),
5702
+ verb: fileChangeVerb(changes, fallbackVerb),
5590
5703
  paths: changes.map((change) => change.path)
5591
5704
  })
5592
5705
  };
@@ -5697,9 +5810,7 @@ function classifyAcpToolCall2(event, injected, options) {
5697
5810
  }
5698
5811
  if (operation.kind === "file_change") {
5699
5812
  const changes = buildAcpFileChanges(event, operation, options);
5700
- if (changes.length > 0) {
5701
- return fileChangeItem(changes);
5702
- }
5813
+ return fileChangeItem(changes, operation.changeKind);
5703
5814
  }
5704
5815
  const title = toOptionalString(event.title);
5705
5816
  switch (event.kind) {
@@ -5920,6 +6031,73 @@ function createAcpDeltaTranslator(options = {}) {
5920
6031
  }
5921
6032
  return classifyAcpToolCall2(event, injected, pathOptions);
5922
6033
  }
6034
+ function withClientFileWrites(event, writes) {
6035
+ if (writes.length === 0) {
6036
+ return event;
6037
+ }
6038
+ const paths = new Set(
6039
+ writes.map((write) => resolveAcpToolCallPath(write.path, pathOptions))
6040
+ );
6041
+ return {
6042
+ ...event,
6043
+ content: [
6044
+ ...(event.content ?? []).filter(
6045
+ (entry) => entry.type !== "diff" || !paths.has(resolveAcpToolCallPath(entry.path, pathOptions))
6046
+ ),
6047
+ ...writes
6048
+ ]
6049
+ };
6050
+ }
6051
+ function mergeFsWriteIntoOpenToolCall(context, write) {
6052
+ const writePath = resolveAcpToolCallPath(write.path, pathOptions);
6053
+ const fileChangeCalls = threadCallEntries(context).flatMap(
6054
+ ([key, open]) => {
6055
+ if (open.openedType !== "fileChange") {
6056
+ return [];
6057
+ }
6058
+ const classified = classifyCall(context, open.event);
6059
+ return classified.item.type === "fileChange" ? [
6060
+ {
6061
+ key,
6062
+ open,
6063
+ paths: classified.item.changes.map((change) => change.path)
6064
+ }
6065
+ ] : [];
6066
+ }
6067
+ );
6068
+ const exactMatches = fileChangeCalls.filter(
6069
+ ({ paths }) => paths.includes(writePath)
6070
+ );
6071
+ const pathPendingMatches = fileChangeCalls.filter(
6072
+ ({ paths }) => paths.length === 0
6073
+ );
6074
+ const matching = exactMatches.length === 1 ? exactMatches[0] : exactMatches.length === 0 && pathPendingMatches.length === 1 ? pathPendingMatches[0] : void 0;
6075
+ if (matching === void 0) {
6076
+ return false;
6077
+ }
6078
+ const previous = matching.open.clientFileWrites?.find(
6079
+ (entry) => resolveAcpToolCallPath(entry.path, pathOptions) === writePath
6080
+ );
6081
+ const oldText = previous === void 0 ? write.oldText : previous.oldText;
6082
+ const diff = {
6083
+ type: "diff",
6084
+ path: write.path,
6085
+ ...oldText === void 0 ? {} : { oldText },
6086
+ newText: write.content
6087
+ };
6088
+ const clientFileWrites = [
6089
+ ...(matching.open.clientFileWrites ?? []).filter(
6090
+ (entry) => resolveAcpToolCallPath(entry.path, pathOptions) !== writePath
6091
+ ),
6092
+ diff
6093
+ ];
6094
+ mergedToolCalls.set(matching.key, {
6095
+ ...matching.open,
6096
+ clientFileWrites,
6097
+ event: withClientFileWrites(matching.open.event, clientFileWrites)
6098
+ });
6099
+ return true;
6100
+ }
5923
6101
  function toRawEvent(rawEvent) {
5924
6102
  const parsed = providerRawEventSchema.safeParse(rawEvent);
5925
6103
  if (parsed.success) {
@@ -6036,7 +6214,8 @@ function createAcpDeltaTranslator(options = {}) {
6036
6214
  };
6037
6215
  }
6038
6216
  function commandCloseFields(event, status) {
6039
- const result = extractAcpCommandResult(event);
6217
+ const normalizedEvent = dialect.normalizeCommandEvent?.(event) ?? event;
6218
+ const result = dialect.commandResult?.(normalizedEvent) ?? extractAcpCommandResult(normalizedEvent);
6040
6219
  const exitCode = result.exitCode ?? (status === "failed" ? 1 : void 0);
6041
6220
  return {
6042
6221
  ...result.output === void 0 ? {} : { aggregatedOutput: result.output, resultText: result.output },
@@ -6155,7 +6334,10 @@ function createAcpDeltaTranslator(options = {}) {
6155
6334
  const event = withDialectIdentity(parsed.data);
6156
6335
  const key = callKey(context, event.toolCallId);
6157
6336
  const open = mergedToolCalls.get(key);
6158
- const merged = mergeAcpToolCallEvents(open?.event, event);
6337
+ const merged = withClientFileWrites(
6338
+ mergeAcpToolCallEvents(open?.event, event),
6339
+ open?.clientFileWrites ?? []
6340
+ );
6159
6341
  if (isTerminalAcpStatus(merged.status)) {
6160
6342
  mergedToolCalls.delete(key);
6161
6343
  return [
@@ -6174,10 +6356,12 @@ function createAcpDeltaTranslator(options = {}) {
6174
6356
  event: merged,
6175
6357
  openedType: open?.openedType ?? mergedType,
6176
6358
  ...open?.permissionTitle === void 0 ? {} : { permissionTitle: open.permissionTitle },
6177
- ...open?.delegation === void 0 ? {} : { delegation: open.delegation }
6359
+ ...open?.delegation === void 0 ? {} : { delegation: open.delegation },
6360
+ ...open?.clientFileWrites === void 0 ? {} : { clientFileWrites: open.clientFileWrites }
6178
6361
  });
6179
6362
  if (event.status === "in_progress" && mergedType === "command" && open?.openedType === "command") {
6180
- const streamed = extractAcpStreamedCommandOutput(event);
6363
+ const normalizedEvent = dialect.normalizeCommandEvent?.(event) ?? event;
6364
+ const streamed = extractAcpStreamedCommandOutput(normalizedEvent);
6181
6365
  return streamed === void 0 ? suppressedUnhandled(rawEvent) : [
6182
6366
  {
6183
6367
  kind: "command.outputSnapshot",
@@ -6354,6 +6538,9 @@ function createAcpDeltaTranslator(options = {}) {
6354
6538
  if (!params.success) {
6355
6539
  return [];
6356
6540
  }
6541
+ if (mergeFsWriteIntoOpenToolCall(context, params.data)) {
6542
+ return [];
6543
+ }
6357
6544
  const rawEvent = {
6358
6545
  jsonrpc: "2.0",
6359
6546
  method: ACP_FS_WRITE_METHOD,
@@ -6676,31 +6863,44 @@ function buildAcpModelDiscoveryAgentCommand(launchSpec) {
6676
6863
  ...launchEnvVars(launchSpec)
6677
6864
  };
6678
6865
  }
6679
- function buildAcpModelListParams(launchSpec) {
6866
+ function buildAcpModelListParams(launchSpec, options) {
6867
+ const primaryModels = [
6868
+ ...options.primaryModels ?? launchSpec?.modelCli?.primaryModels ?? []
6869
+ ];
6870
+ const reasoningProbePriorityModelIds = [
6871
+ ...options.reasoningProbePriorityModelIds
6872
+ ];
6680
6873
  if (launchSpec === null) {
6681
- return { primaryModels: [] };
6874
+ return {
6875
+ primaryModels,
6876
+ reasoningProbePriorityModelIds,
6877
+ parameterizedModelPicker: options.parameterizedModelPicker
6878
+ };
6682
6879
  }
6683
6880
  const listCommand = buildAcpModelListCommand(launchSpec);
6684
6881
  const agent = buildAcpModelDiscoveryAgentCommand(launchSpec);
6685
6882
  return {
6686
6883
  ...listCommand !== void 0 ? { listCommand } : {},
6687
6884
  ...agent !== void 0 ? { agent } : {},
6688
- primaryModels: [...launchSpec.modelCli?.primaryModels ?? []],
6885
+ primaryModels,
6886
+ reasoningProbePriorityModelIds,
6887
+ parameterizedModelPicker: options.parameterizedModelPicker,
6689
6888
  ...launchSpec.reasoningCli !== void 0 ? { reasoningCli: launchSpec.reasoningCli } : {},
6690
6889
  ...launchSpec.nativeReasoning !== void 0 ? { nativeReasoning: launchSpec.nativeReasoning } : {}
6691
6890
  };
6692
6891
  }
6693
- function buildAcpModelSelectionParam(launchSpec, options) {
6892
+ function buildAcpModelSelectionParam(launchSpec, options, parameterizedModelPicker) {
6694
6893
  const model = options.model;
6695
6894
  const listCommand = buildAcpModelListCommand(launchSpec);
6696
6895
  if (!model || model === ACP_DEFAULT_MODEL_ID) {
6697
6896
  return {};
6698
6897
  }
6699
- if (!listCommand || !launchSpec.modelCli?.selectFlag) {
6898
+ if (parameterizedModelPicker || !listCommand || !launchSpec.modelCli?.selectFlag) {
6700
6899
  return {
6701
6900
  modelSelection: {
6702
6901
  modelId: model,
6703
- ...options.reasoningLevel !== void 0 ? { reasoningLevel: options.reasoningLevel } : {}
6902
+ ...options.reasoningLevel !== void 0 ? { reasoningLevel: options.reasoningLevel } : {},
6903
+ ...parameterizedModelPicker && options.serviceTier !== void 0 ? { serviceTier: options.serviceTier } : {}
6704
6904
  }
6705
6905
  };
6706
6906
  }
@@ -6710,7 +6910,7 @@ function buildAcpModelSelectionParam(launchSpec, options) {
6710
6910
  selectFlag: launchSpec.modelCli.selectFlag,
6711
6911
  model,
6712
6912
  ...options.reasoningLevel !== void 0 ? { reasoningLevel: options.reasoningLevel } : {},
6713
- // Only "fast" changes resolution; "default" is the catalog's normal id.
6913
+ // Only "fast" changes launch resolution; "default" is the normal id.
6714
6914
  ...options.serviceTier === "fast" ? { serviceTier: options.serviceTier } : {}
6715
6915
  }
6716
6916
  };
@@ -6736,7 +6936,12 @@ function buildAcpSessionParams(args) {
6736
6936
  args: [...launchSpec.args]
6737
6937
  },
6738
6938
  ...args.dialectId === void 0 ? {} : { dialectId: args.dialectId },
6739
- ...buildAcpModelSelectionParam(launchSpec, options),
6939
+ ...buildAcpModelSelectionParam(
6940
+ launchSpec,
6941
+ options,
6942
+ args.parameterizedModelPicker
6943
+ ),
6944
+ parameterizedModelPicker: args.parameterizedModelPicker,
6740
6945
  ...launchSpec.reasoningCli !== void 0 ? { reasoningCli: launchSpec.reasoningCli } : {},
6741
6946
  ...launchSpec.nativeReasoning !== void 0 ? { nativeReasoning: launchSpec.nativeReasoning } : {},
6742
6947
  ...launchSpec.permissionCli !== void 0 ? { permissionCli: launchSpec.permissionCli } : {},
@@ -8128,6 +8333,13 @@ async function authenticateAcpAgent(args) {
8128
8333
  );
8129
8334
  }
8130
8335
  }
8336
+ function acpClientCapabilities(parameterizedModelPicker, fsAccess = false) {
8337
+ return {
8338
+ fs: { readTextFile: fsAccess, writeTextFile: fsAccess },
8339
+ terminal: false,
8340
+ ...parameterizedModelPicker === true ? { _meta: { parameterizedModelPicker: true } } : {}
8341
+ };
8342
+ }
8131
8343
  async function loadAgentModelCatalog(listCommand) {
8132
8344
  const stdout = await new Promise((resolveExec, rejectExec) => {
8133
8345
  execFile4(
@@ -8177,8 +8389,12 @@ async function loadAgentModelCatalog(listCommand) {
8177
8389
  cachedModelCatalog = { key, catalog };
8178
8390
  return catalog;
8179
8391
  }
8180
- async function loadSessionDiscoveredModels(agent) {
8181
- const key = JSON.stringify(agent);
8392
+ async function loadSessionDiscoveredModels(agent, reasoningProbePriorityModelIds, parameterizedModelPicker) {
8393
+ const key = JSON.stringify({
8394
+ agent,
8395
+ reasoningProbePriorityModelIds,
8396
+ parameterizedModelPicker
8397
+ });
8182
8398
  if (cachedSessionDiscoveredModels?.key === key && Date.now() - cachedSessionDiscoveredModels.fetchedAt < SESSION_MODEL_DISCOVERY_TTL_MS) {
8183
8399
  return cachedSessionDiscoveredModels.models;
8184
8400
  }
@@ -8219,10 +8435,7 @@ async function loadSessionDiscoveredModels(agent) {
8219
8435
  params: {
8220
8436
  protocolVersion: ACP_PROTOCOL_VERSION,
8221
8437
  clientInfo: { name: "bb", version: "1.0.0" },
8222
- clientCapabilities: {
8223
- fs: { readTextFile: false, writeTextFile: false },
8224
- terminal: false
8225
- }
8438
+ clientCapabilities: acpClientCapabilities(parameterizedModelPicker)
8226
8439
  },
8227
8440
  resultSchema: acpInitializeResultSchema
8228
8441
  });
@@ -8260,7 +8473,8 @@ async function loadSessionDiscoveredModels(agent) {
8260
8473
  const reasoningByModel = await discoverAcpNativeReasoningByModel({
8261
8474
  connection,
8262
8475
  sessionId: newSession.sessionId,
8263
- modelOption
8476
+ modelOption,
8477
+ reasoningProbePriorityModelIds
8264
8478
  });
8265
8479
  const models = reasoningByModel === null ? configOptionModels : buildModelCatalogFromConfigOptions(modelOption, reasoningByModel);
8266
8480
  cachedSessionDiscoveredModels = {
@@ -8288,6 +8502,23 @@ async function discoverAcpNativeReasoningByModel(args) {
8288
8502
  return null;
8289
8503
  }
8290
8504
  const modelOption = args.modelOption;
8505
+ const modelByValue = new Map(
8506
+ modelOptions.map((model) => [model.value, model])
8507
+ );
8508
+ const modelsToProbe = [];
8509
+ const addedModels = /* @__PURE__ */ new Set();
8510
+ for (const value of args.reasoningProbePriorityModelIds) {
8511
+ const model = modelByValue.get(value);
8512
+ if (model && !addedModels.has(model.value)) {
8513
+ modelsToProbe.push(model);
8514
+ addedModels.add(model.value);
8515
+ }
8516
+ }
8517
+ for (const model of modelOptions) {
8518
+ if (!addedModels.has(model.value)) {
8519
+ modelsToProbe.push(model);
8520
+ }
8521
+ }
8291
8522
  const supportByModel = /* @__PURE__ */ new Map();
8292
8523
  let timeout;
8293
8524
  const timeoutReached = new Promise((resolve4) => {
@@ -8299,7 +8530,7 @@ async function discoverAcpNativeReasoningByModel(args) {
8299
8530
  try {
8300
8531
  return await Promise.race([
8301
8532
  (async () => {
8302
- for (const model of modelOptions) {
8533
+ for (const model of modelsToProbe) {
8303
8534
  const configState = await args.connection.request({
8304
8535
  method: "session/set_config_option",
8305
8536
  params: {
@@ -8459,6 +8690,12 @@ async function selectAcpNativeModel(args) {
8459
8690
  modelSelection: selection,
8460
8691
  nativeReasoning: args.nativeReasoning
8461
8692
  });
8693
+ await selectAcpNativeServiceTier({
8694
+ connection: args.connection,
8695
+ sessionId: args.sessionId,
8696
+ configOptions,
8697
+ modelSelection: selection
8698
+ });
8462
8699
  }
8463
8700
  async function selectAcpNativeReasoning(args) {
8464
8701
  const reasoningLevel = args.modelSelection.reasoningLevel;
@@ -8489,6 +8726,28 @@ async function selectAcpNativeReasoning(args) {
8489
8726
  } catch {
8490
8727
  }
8491
8728
  }
8729
+ async function selectAcpNativeServiceTier(args) {
8730
+ const serviceTier = args.modelSelection.serviceTier;
8731
+ if (serviceTier === void 0) {
8732
+ return;
8733
+ }
8734
+ const fastOption = (args.configOptions ?? []).find(
8735
+ (option) => option.id === "fast" && option.type === "select"
8736
+ );
8737
+ const value = serviceTier === "fast" ? "true" : "false";
8738
+ if (!fastOption?.options?.some((option) => option.value === value)) {
8739
+ return;
8740
+ }
8741
+ await args.connection.request({
8742
+ method: "session/set_config_option",
8743
+ params: {
8744
+ sessionId: args.sessionId,
8745
+ configId: fastOption.id,
8746
+ value
8747
+ },
8748
+ resultSchema: acpConfigStateResultSchema
8749
+ });
8750
+ }
8492
8751
  function buildPromptContentBlocks(session, input) {
8493
8752
  const blocks = [];
8494
8753
  const instructions = session.pendingInstructions;
@@ -8854,10 +9113,10 @@ async function startAgentSession(request) {
8854
9113
  params: {
8855
9114
  protocolVersion: ACP_PROTOCOL_VERSION,
8856
9115
  clientInfo: { name: "bb", version: "1.0.0" },
8857
- clientCapabilities: {
8858
- fs: { readTextFile: true, writeTextFile: true },
8859
- terminal: false
8860
- }
9116
+ clientCapabilities: acpClientCapabilities(
9117
+ params.parameterizedModelPicker,
9118
+ true
9119
+ )
8861
9120
  },
8862
9121
  resultSchema: acpInitializeResultSchema
8863
9122
  });
@@ -9327,15 +9586,22 @@ async function handleModelList(id, params) {
9327
9586
  );
9328
9587
  return;
9329
9588
  }
9330
- const sessionDiscoveredModels = params.listCommand === void 0 && params.agent ? await loadSessionDiscoveredModels(params.agent) : null;
9589
+ const sessionDiscoveredModels = params.listCommand === void 0 && params.agent ? await loadSessionDiscoveredModels(
9590
+ params.agent,
9591
+ params.reasoningProbePriorityModelIds,
9592
+ params.parameterizedModelPicker
9593
+ ) : null;
9331
9594
  if (sessionDiscoveredModels) {
9332
- sendResult(id, {
9333
- models: applyConfiguredReasoningToModels(sessionDiscoveredModels, {
9334
- reasoningCli: params.reasoningCli,
9335
- nativeReasoning: params.nativeReasoning
9336
- }),
9337
- selectedOnlyModels: []
9338
- });
9595
+ sendResult(
9596
+ id,
9597
+ splitPrimaryModels(
9598
+ applyConfiguredReasoningToModels(sessionDiscoveredModels, {
9599
+ reasoningCli: params.reasoningCli,
9600
+ nativeReasoning: params.nativeReasoning
9601
+ }),
9602
+ params.primaryModels
9603
+ )
9604
+ );
9339
9605
  return;
9340
9606
  }
9341
9607
  sendResult(id, {
@@ -9367,8 +9633,24 @@ var acpProviderOptionsSchema = z39.object({
9367
9633
  * the provider, so a third-party registration of a known agent gets the
9368
9634
  * same reporting fidelity a first-party one does.
9369
9635
  */
9370
- acpDialect: z39.string().min(1).optional()
9636
+ acpDialect: z39.string().min(1).optional(),
9637
+ /** Enables an agent's separate model configuration options. */
9638
+ parameterizedModelPicker: z39.boolean().optional(),
9639
+ /** Bare model ids shown before the collapsed "More models" pool. */
9640
+ primaryModels: z39.array(z39.string().min(1)).optional(),
9641
+ /** Model ids to probe first during bounded native discovery. */
9642
+ reasoningProbePriorityModelIds: z39.array(z39.string().min(1)).optional()
9371
9643
  }).passthrough();
9644
+ function decodeAcpModelPickerOptions(providerOptions) {
9645
+ const parsed = acpProviderOptionsSchema.parse(providerOptions ?? {});
9646
+ return {
9647
+ parameterizedModelPicker: parsed.parameterizedModelPicker === true,
9648
+ ...parsed.primaryModels === void 0 ? {} : { primaryModels: [...parsed.primaryModels] },
9649
+ reasoningProbePriorityModelIds: [
9650
+ ...parsed.reasoningProbePriorityModelIds ?? []
9651
+ ]
9652
+ };
9653
+ }
9372
9654
  function decodeAdditionalWorkspaceWriteRoots(providerOptions) {
9373
9655
  return acpProviderOptionsSchema.parse(providerOptions ?? {}).additionalWorkspaceWriteRoots ?? [];
9374
9656
  }
@@ -9410,14 +9692,19 @@ async function handleRequest2(request) {
9410
9692
  };
9411
9693
  sendResult(request.id, result);
9412
9694
  return;
9413
- case "model/list":
9695
+ case "model/list": {
9696
+ const modelPicker = decodeAcpModelPickerOptions(
9697
+ request.params.providerOptions
9698
+ );
9414
9699
  await handleModelList(
9415
9700
  request.id,
9416
9701
  buildAcpModelListParams(
9417
- decodeLaunchSpec(request.params.providerOptions)
9702
+ decodeLaunchSpec(request.params.providerOptions),
9703
+ modelPicker
9418
9704
  )
9419
9705
  );
9420
9706
  return;
9707
+ }
9421
9708
  case "provider/health": {
9422
9709
  const launchSpec = decodeLaunchSpec(request.params.providerOptions);
9423
9710
  sendResult(
@@ -9496,6 +9783,9 @@ async function handleRequest2(request) {
9496
9783
  );
9497
9784
  return;
9498
9785
  }
9786
+ const modelPicker = decodeAcpModelPickerOptions(
9787
+ params.options.providerOptions
9788
+ );
9499
9789
  const sessionParams = buildAcpSessionParams({
9500
9790
  additionalWorkspaceWriteRoots: decodeAdditionalWorkspaceWriteRoots(
9501
9791
  params.options.providerOptions
@@ -9507,6 +9797,7 @@ async function handleRequest2(request) {
9507
9797
  ...params.options,
9508
9798
  skillRoots: configuredSkillRoots ?? void 0
9509
9799
  },
9800
+ parameterizedModelPicker: modelPicker.parameterizedModelPicker,
9510
9801
  launchSpec,
9511
9802
  providerLabel: launchSpec.displayName,
9512
9803
  threadId: params.threadId
@@ -2246,7 +2246,7 @@ var deltaItemShapeSchema = z14.discriminatedUnion("type", [
2246
2246
  }),
2247
2247
  z14.object({
2248
2248
  type: z14.literal("fileChange"),
2249
- /** Empty only on bare close-without-open fallbacks (path unknown). */
2249
+ /** Empty while a path is not yet known, including bare close fallbacks. */
2250
2250
  changes: z14.array(deltaFileChangeSchema)
2251
2251
  }),
2252
2252
  /**
@@ -4693,7 +4693,7 @@ var deltaItemShapeSchema = z30.discriminatedUnion("type", [
4693
4693
  }),
4694
4694
  z30.object({
4695
4695
  type: z30.literal("fileChange"),
4696
- /** Empty only on bare close-without-open fallbacks (path unknown). */
4696
+ /** Empty while a path is not yet known, including bare close fallbacks. */
4697
4697
  changes: z30.array(deltaFileChangeSchema)
4698
4698
  }),
4699
4699
  /**
@@ -37,16 +37,23 @@ function parseNamespacedGlyph(glyph) {
37
37
  // ../domain/src/plugin-cli.ts
38
38
  var RESERVED_BB_CLI_COMMANDS = [
39
39
  "environment",
40
+ "file",
40
41
  "guide",
41
42
  "help",
43
+ "machine",
42
44
  "manager",
45
+ "marketplace",
43
46
  "plugin",
44
47
  "project",
45
48
  "provider",
49
+ "settings",
46
50
  "skill",
47
51
  "status",
52
+ "terminal",
48
53
  "theme",
49
- "thread"
54
+ "thread",
55
+ "updates",
56
+ "voice"
50
57
  ];
51
58
 
52
59
  // ../domain/src/provider-fork.ts
@@ -265,6 +272,10 @@ var providerNativeRootSetSchema = z2.object({
265
272
  var PLUGIN_CLI_OUTPUT_MAX_BYTES = 1024 * 1024;
266
273
 
267
274
  // src/internal/host-policy.ts
275
+ function pluginCliCollisionWarning(pluginId, commandName) {
276
+ if (!RESERVED_BB_CLI_COMMANDS.includes(commandName)) return null;
277
+ return `CLI command "${commandName}" collides with core command "bb ${commandName}"; core keeps the short form. Use "bb plugin run ${pluginId}" to invoke this plugin.`;
278
+ }
268
279
  var RESERVED_AGENT_TOOL_NAMES = [
269
280
  "update_environment_directory"
270
281
  ];
@@ -2265,11 +2276,6 @@ function createFakePluginHostInternal(options, sharedState) {
2265
2276
  `invalid cli command name ${JSON.stringify(name)} \u2014 use lowercase letters, digits, and "-"`
2266
2277
  );
2267
2278
  }
2268
- if (RESERVED_BB_CLI_COMMANDS.includes(name)) {
2269
- throw new Error(
2270
- `cli command name "${name}" is reserved by the bb CLI \u2014 pick another name`
2271
- );
2272
- }
2273
2279
  if (typeof registration.summary !== "string" || registration.summary.trim().length === 0) {
2274
2280
  throw new Error(`cli command "${name}" must provide a summary`);
2275
2281
  }
@@ -2300,6 +2306,8 @@ function createFakePluginHostInternal(options, sharedState) {
2300
2306
  commands: validatedCommands,
2301
2307
  run: registration.run.bind(registration)
2302
2308
  };
2309
+ const warning = pluginCliCollisionWarning(pluginId, name);
2310
+ if (warning) emitLog("warn", warning);
2303
2311
  }
2304
2312
  };
2305
2313
  const agentTools = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@get-bb/plugin-sdk",
3
- "version": "0.4.16",
3
+ "version": "0.4.18",
4
4
  "homepage": "https://github.com/get-bb/bb#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/get-bb/bb/issues"