@get-bb/plugin-sdk 0.4.55 → 0.4.84

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.
@@ -1,16 +1,3 @@
1
- // src/testing/fake-plugin-host.ts
2
- import { createHash } from "node:crypto";
3
- import { mkdtempSync, rmSync } from "node:fs";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
6
- import Database from "better-sqlite3";
7
- import { CronExpressionParser } from "cron-parser";
8
- import { Hono } from "hono";
9
-
10
- // ../domain/src/plugin-interaction-limits.ts
11
- var PLUGIN_INTERACTION_MAX_TITLE_LENGTH = 160;
12
- var PLUGIN_INTERACTION_MAX_PAYLOAD_BYTES = 64 * 1024;
13
-
14
1
  // src/internal/host-policy.ts
15
2
  import { z as z4 } from "zod";
16
3
 
@@ -286,6 +273,10 @@ var providerNativeRootSetSchema = z3.object({
286
273
  resolved: providerResolvedNativeRootsSchema
287
274
  }).strict();
288
275
 
276
+ // ../domain/src/plugin-interaction-limits.ts
277
+ var PLUGIN_INTERACTION_MAX_TITLE_LENGTH = 160;
278
+ var PLUGIN_INTERACTION_MAX_PAYLOAD_BYTES = 64 * 1024;
279
+
289
280
  // src/backend-contract.ts
290
281
  var PLUGIN_CLI_OUTPUT_MAX_BYTES = 1024 * 1024;
291
282
 
@@ -319,8 +310,7 @@ var pluginProviderEnvEntrySchema = z4.object({
319
310
  z4.string(),
320
311
  z4.object({ serverPath: z4.string().startsWith("/") }).strict()
321
312
  ]),
322
- reason: z4.string(),
323
- secret: z4.boolean()
313
+ reason: z4.string()
324
314
  }).strict();
325
315
  var pluginProviderEnvEntriesSchema = z4.array(pluginProviderEnvEntrySchema).max(PLUGIN_PROVIDER_ENV_MAX_ENTRIES).superRefine((entries, context) => {
326
316
  const names = /* @__PURE__ */ new Set();
@@ -1769,12 +1759,23 @@ function pluginHookAlreadyRegisteredMessage(hook) {
1769
1759
  }
1770
1760
  var ENVIRONMENT_PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9-]{1,63}$/;
1771
1761
  var ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS = 80;
1762
+ var ENVIRONMENT_PROVIDER_DESCRIPTION_MAX_CHARS = 200;
1772
1763
  var ENVIRONMENT_PROVIDER_REQUIREMENT_NAMES = [
1773
1764
  "projectCheckout",
1774
1765
  "gitCheckout",
1775
1766
  "gitRemote",
1776
1767
  "projectless"
1777
1768
  ];
1769
+ var environmentCompositionSchema = z4.object({
1770
+ id: z4.string().regex(ENVIRONMENT_PROVIDER_ID_PATTERN),
1771
+ displayName: z4.string().trim().min(1).max(ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS),
1772
+ description: z4.string().trim().min(1).max(ENVIRONMENT_PROVIDER_DESCRIPTION_MAX_CHARS).optional().transform((value) => value ?? null),
1773
+ icon: z4.string().trim().min(1).transform(
1774
+ (icon) => isPluginOwnedIconPath(icon) ? validateProviderRelativePath(icon, "Composition icon") : icon
1775
+ ).optional().transform((value) => value ?? null),
1776
+ machineProviderId: z4.string().regex(ENVIRONMENT_PROVIDER_ID_PATTERN),
1777
+ environmentProviderId: z4.string().regex(ENVIRONMENT_PROVIDER_ID_PATTERN)
1778
+ }).strict();
1778
1779
  function validatePluginEnvironmentProviderDeclaration(declaration) {
1779
1780
  if (typeof declaration !== "object" || declaration === null) {
1780
1781
  throw new Error("environment provider declaration must be an object");
@@ -1791,17 +1792,14 @@ function validatePluginEnvironmentProviderDeclaration(declaration) {
1791
1792
  `environment provider "${id}" needs a displayName of 1-${ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS} characters`
1792
1793
  );
1793
1794
  }
1794
- const icon = declaration.icon === void 0 ? null : z4.string().min(1).parse(declaration.icon).trim();
1795
- if (icon !== null) {
1796
- if (isPluginOwnedIconPath(icon))
1797
- validateProviderRelativePath(icon, `"${id}" icon`);
1798
- else if (!isNamespacedGlyph(icon) && /[/\\]/u.test(icon))
1799
- throw new Error(
1800
- `environment provider "${id}" icon must be a glyph, declared icon, or plugin-relative path`
1801
- );
1802
- }
1803
- if (icon !== null && icon.length === 0) {
1804
- throw new Error(`environment provider "${id}" declares an empty icon`);
1795
+ const description = z4.string().trim().min(1).max(ENVIRONMENT_PROVIDER_DESCRIPTION_MAX_CHARS).optional().transform((value) => value ?? null).parse(declaration.description);
1796
+ const icon = z4.string().trim().min(1).optional().transform((value) => value ?? null).parse(declaration.icon);
1797
+ if (icon !== null && isPluginOwnedIconPath(icon)) {
1798
+ validateProviderRelativePath(icon, `"${id}" icon`);
1799
+ } else if (icon !== null && !isNamespacedGlyph(icon) && /[/\\]/u.test(icon)) {
1800
+ throw new Error(
1801
+ `environment provider "${id}" icon must be a glyph, declared icon, or plugin-relative path`
1802
+ );
1805
1803
  }
1806
1804
  const requires = normalizeEnvironmentProviderRequirements(id, declaration);
1807
1805
  const inputs = normalizeEnvironmentProviderInputs(id, declaration);
@@ -1823,6 +1821,7 @@ function validatePluginEnvironmentProviderDeclaration(declaration) {
1823
1821
  return {
1824
1822
  id,
1825
1823
  displayName,
1824
+ description,
1826
1825
  icon,
1827
1826
  requires,
1828
1827
  inputs: inputs === null ? null : inputs.schema,
@@ -1891,6 +1890,115 @@ var environmentProviderPolicySchema = z4.object({
1891
1890
  retireGraceMs: z4.number().int().nonnegative().nullable().default(3e5),
1892
1891
  pathKeys: z4.enum(["per-thread", "per-attempt"]).default("per-thread")
1893
1892
  }).strict();
1893
+ var MACHINE_PROVIDER_DESCRIPTION_MAX_CHARS = 200;
1894
+ function validatePluginMachineProviderDeclaration(declaration) {
1895
+ if (typeof declaration !== "object" || declaration === null) {
1896
+ throw new Error("machine provider declaration must be an object");
1897
+ }
1898
+ const id = declaration.id;
1899
+ if (typeof id !== "string" || !ENVIRONMENT_PROVIDER_ID_PATTERN.test(id)) {
1900
+ throw new Error(
1901
+ `invalid machine provider id ${JSON.stringify(id)} \u2014 use 2-64 lowercase letters, digits, or "-", starting with a letter or digit`
1902
+ );
1903
+ }
1904
+ const displayName = typeof declaration.displayName === "string" ? declaration.displayName.trim() : "";
1905
+ if (displayName.length === 0 || displayName.length > ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS) {
1906
+ throw new Error(
1907
+ `machine provider "${id}" needs a displayName of 1-${ENVIRONMENT_PROVIDER_DISPLAY_NAME_MAX_CHARS} characters`
1908
+ );
1909
+ }
1910
+ const description = z4.string().trim().min(1).max(MACHINE_PROVIDER_DESCRIPTION_MAX_CHARS).parse(declaration.description);
1911
+ const icon = z4.string().trim().min(1).parse(declaration.icon);
1912
+ if (isPluginOwnedIconPath(icon)) {
1913
+ validateProviderRelativePath(icon, `"${id}" icon`);
1914
+ } else if (!isNamespacedGlyph(icon) && /[/\\]/u.test(icon)) {
1915
+ throw new Error(
1916
+ `machine provider "${id}" icon must be a glyph, declared icon, or plugin-relative path`
1917
+ );
1918
+ }
1919
+ const ephemeral = z4.boolean().default(false).parse(declaration.ephemeral);
1920
+ const inputs = normalizeMachineProviderInputs(id, declaration);
1921
+ if (typeof declaration.create !== "function" || typeof declaration.reconcileCleanup !== "function" || typeof declaration.remove !== "function") {
1922
+ throw new Error(
1923
+ `machine provider "${id}" must declare create, reconcileCleanup and remove functions`
1924
+ );
1925
+ }
1926
+ const hasSuspend = typeof declaration.suspend === "function";
1927
+ const hasResume = typeof declaration.resume === "function";
1928
+ if (hasSuspend !== hasResume) {
1929
+ throw new Error(
1930
+ `machine provider "${id}" must declare suspend and resume together`
1931
+ );
1932
+ }
1933
+ if (declaration.validate !== void 0 && typeof declaration.validate !== "function") {
1934
+ throw new Error(
1935
+ `machine provider "${id}" declares a validate that is not a function`
1936
+ );
1937
+ }
1938
+ if (declaration.availability !== void 0 && typeof declaration.availability !== "function") {
1939
+ throw new Error(
1940
+ `machine provider "${id}" declares availability that is not a function`
1941
+ );
1942
+ }
1943
+ return {
1944
+ id,
1945
+ displayName,
1946
+ description,
1947
+ icon,
1948
+ ephemeral,
1949
+ inputs: inputs === null ? null : inputs.schema,
1950
+ inputsJsonSchema: inputs === null ? null : inputs.jsonSchema,
1951
+ availability: declaration.availability ?? null,
1952
+ validate: declaration.validate ?? null,
1953
+ reconcileCleanup: declaration.reconcileCleanup,
1954
+ create: declaration.create,
1955
+ suspend: declaration.suspend ?? null,
1956
+ resume: declaration.resume ?? null,
1957
+ remove: declaration.remove
1958
+ };
1959
+ }
1960
+ function normalizeMachineProviderInputs(id, declaration) {
1961
+ const inputs = declaration.inputs;
1962
+ if (inputs === void 0) return null;
1963
+ if (!isStandardSchema(inputs)) {
1964
+ throw new Error(
1965
+ `machine provider "${id}" declares an inputs that is not a Standard Schema v1 validator`
1966
+ );
1967
+ }
1968
+ let converted;
1969
+ try {
1970
+ converted = JSON.parse(JSON.stringify(standardSchemaToJsonSchema(inputs)));
1971
+ } catch (error) {
1972
+ throw new Error(
1973
+ `machine provider "${id}" declares an inputs validator that cannot be published as JSON Schema (${error instanceof Error ? error.message : String(error)}) \u2014 declare it with zod 4 or a validator exposing toJSONSchema()`
1974
+ );
1975
+ }
1976
+ const jsonSchema = jsonValueSchema.safeParse(converted);
1977
+ if (!jsonSchema.success) {
1978
+ throw new Error(
1979
+ `machine provider "${id}" declares an inputs schema whose JSON Schema is not JSON-serializable`
1980
+ );
1981
+ }
1982
+ return { schema: inputs, jsonSchema: jsonSchema.data };
1983
+ }
1984
+ function validateServerAccessProviderDeclaration(declaration) {
1985
+ if (typeof declaration !== "object" || declaration === null)
1986
+ throw new Error("Invalid server access provider declaration");
1987
+ if (typeof declaration.id !== "string" || !/^[a-z][a-z0-9-]*$/u.test(declaration.id) || declaration.id === "direct")
1988
+ throw new Error("Invalid or reserved server access provider id");
1989
+ if (typeof declaration.displayName !== "string" || !declaration.displayName.trim() || typeof declaration.description !== "string" || !declaration.description.trim() || typeof declaration.availability !== "function" || typeof declaration.acquire !== "function" || typeof declaration.release !== "function")
1990
+ throw new Error("Invalid server access provider declaration");
1991
+ return declaration;
1992
+ }
1993
+
1994
+ // src/testing/fake-plugin-host.ts
1995
+ import { createHash } from "node:crypto";
1996
+ import { mkdtempSync, rmSync } from "node:fs";
1997
+ import { tmpdir } from "node:os";
1998
+ import { join } from "node:path";
1999
+ import Database from "better-sqlite3";
2000
+ import { CronExpressionParser } from "cron-parser";
2001
+ import { Hono } from "hono";
1894
2002
 
1895
2003
  // src/testing/fake-sdk.ts
1896
2004
  function withSpawnAttribution(pluginId, args) {
@@ -2920,6 +3028,8 @@ function createFakePluginHostInternal(options, sharedState) {
2920
3028
  overrides: options.sdk
2921
3029
  });
2922
3030
  const threadEventHandlers = {
3031
+ "experimental_thread.events": [],
3032
+ "experimental_terminal.input": [],
2923
3033
  "thread.created": [],
2924
3034
  "thread.active": [],
2925
3035
  "thread.idle": [],
@@ -2936,7 +3046,10 @@ function createFakePluginHostInternal(options, sharedState) {
2936
3046
  const hooks = {
2937
3047
  "message.dispatch": null
2938
3048
  };
3049
+ const environmentCompositions = /* @__PURE__ */ new Map();
2939
3050
  const environmentProviders = /* @__PURE__ */ new Map();
3051
+ const machineProviders = /* @__PURE__ */ new Map();
3052
+ const serverAccessProviders = /* @__PURE__ */ new Map();
2940
3053
  const disposeHooks = [];
2941
3054
  const serviceControllers = [];
2942
3055
  let nextInteractionId = 1;
@@ -3206,6 +3319,26 @@ function createFakePluginHostInternal(options, sharedState) {
3206
3319
  const experimental_environments = {
3207
3320
  register(declaration) {
3208
3321
  assertLive();
3322
+ if ("machineProviderId" in declaration) {
3323
+ const composition = environmentCompositionSchema.parse(declaration);
3324
+ const problem2 = composition.icon === null ? null : undeclaredIconProblem(
3325
+ pluginId,
3326
+ declaredIconNames,
3327
+ composition.icon
3328
+ );
3329
+ if (problem2 !== null)
3330
+ throw new Error(providerIconRefusalMessage(composition.id, problem2));
3331
+ if (environmentProviders.has(composition.id))
3332
+ throw new Error(
3333
+ "Environment ID is already registered as a concrete provider"
3334
+ );
3335
+ environmentCompositions.set(composition.id, composition);
3336
+ return;
3337
+ }
3338
+ if (environmentCompositions.has(declaration.id))
3339
+ throw new Error(
3340
+ "Environment ID is already registered as a composition"
3341
+ );
3209
3342
  const target = validatePluginEnvironmentProviderDeclaration(declaration);
3210
3343
  const problem = target.icon === null ? null : undeclaredIconProblem(pluginId, declaredIconNames, target.icon);
3211
3344
  if (problem !== null)
@@ -3217,6 +3350,29 @@ function createFakePluginHostInternal(options, sharedState) {
3217
3350
  requestedDrains += 1;
3218
3351
  }
3219
3352
  };
3353
+ const unavailableMachineBootstrap = () => {
3354
+ throw new Error(
3355
+ "Configure machineBootstrap in createFakePluginHost to exercise machine bootstrap"
3356
+ );
3357
+ };
3358
+ const experimental_machines = {
3359
+ async getResource(hostId) {
3360
+ assertLive();
3361
+ return options.machineResource ? options.machineResource(hostId) : null;
3362
+ },
3363
+ ...options.machineBootstrap ?? {
3364
+ bootstrap: unavailableMachineBootstrap
3365
+ },
3366
+ register(declaration) {
3367
+ assertLive();
3368
+ const target = validatePluginMachineProviderDeclaration(declaration);
3369
+ const problem = target.icon === null ? null : undeclaredIconProblem(pluginId, declaredIconNames, target.icon);
3370
+ if (problem !== null) {
3371
+ throw new Error(providerIconRefusalMessage(target.id, problem));
3372
+ }
3373
+ machineProviders.set(target.id, target);
3374
+ }
3375
+ };
3220
3376
  const bb = {
3221
3377
  pluginId,
3222
3378
  log,
@@ -3233,6 +3389,22 @@ function createFakePluginHostInternal(options, sharedState) {
3233
3389
  events,
3234
3390
  experimental_hooks,
3235
3391
  experimental_environments,
3392
+ experimental_machines,
3393
+ experimental_serverAccess: {
3394
+ register(declaration) {
3395
+ assertLive();
3396
+ validateServerAccessProviderDeclaration(declaration);
3397
+ if (serverAccessProviders.has(declaration.id))
3398
+ throw new Error(
3399
+ `Server access provider "${declaration.id}" is already registered`
3400
+ );
3401
+ serverAccessProviders.set(declaration.id, declaration);
3402
+ },
3403
+ recheck() {
3404
+ assertLive();
3405
+ requestedDrains += 1;
3406
+ }
3407
+ },
3236
3408
  status,
3237
3409
  server,
3238
3410
  hosts,
@@ -3320,6 +3492,8 @@ function createFakePluginHostInternal(options, sharedState) {
3320
3492
  },
3321
3493
  get threadEventHandlers() {
3322
3494
  return {
3495
+ "experimental_thread.events": threadEventHandlers["experimental_thread.events"].length,
3496
+ "experimental_terminal.input": threadEventHandlers["experimental_terminal.input"].length,
3323
3497
  "thread.created": threadEventHandlers["thread.created"].length,
3324
3498
  "thread.active": threadEventHandlers["thread.active"].length,
3325
3499
  "thread.idle": threadEventHandlers["thread.idle"].length,
@@ -3337,9 +3511,18 @@ function createFakePluginHostInternal(options, sharedState) {
3337
3511
  get hooks() {
3338
3512
  return { ...hooks };
3339
3513
  },
3514
+ get environmentCompositions() {
3515
+ return new Map(environmentCompositions);
3516
+ },
3340
3517
  get environmentProviders() {
3341
3518
  return new Map(environmentProviders);
3342
3519
  },
3520
+ get serverAccessProviders() {
3521
+ return new Map(serverAccessProviders);
3522
+ },
3523
+ get machineProviders() {
3524
+ return new Map(machineProviders);
3525
+ },
3343
3526
  mentionProviders,
3344
3527
  providerRegistrations,
3345
3528
  providerEnvResolvers,
@@ -3765,8 +3948,16 @@ function makeHostResponse(overrides = {}) {
3765
3948
  return {
3766
3949
  id: "host-1",
3767
3950
  name: "Test host",
3768
- status: "connected",
3769
3951
  type: "persistent",
3952
+ status: "connected",
3953
+ machineProviderId: null,
3954
+ lifecycle: {
3955
+ phase: "active",
3956
+ suspendedAt: null,
3957
+ message: null,
3958
+ pendingLog: "",
3959
+ teardown: null
3960
+ },
3770
3961
  maxPermissionMode: "full",
3771
3962
  lastSeenAt: null,
3772
3963
  lastRejectedProtocolVersion: null,
@@ -3910,8 +4101,16 @@ function makeMessageDispatchHookContext(overrides = {}) {
3910
4101
  const hostDefaults = {
3911
4102
  id: "host-1",
3912
4103
  name: "Test host",
3913
- status: "connected",
3914
4104
  type: "persistent",
4105
+ status: "connected",
4106
+ machineProviderId: null,
4107
+ lifecycle: {
4108
+ phase: "active",
4109
+ suspendedAt: null,
4110
+ message: null,
4111
+ pendingLog: "",
4112
+ teardown: null
4113
+ },
3915
4114
  maxPermissionMode: "full",
3916
4115
  lastSeenAt: null,
3917
4116
  lastRejectedProtocolVersion: null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@get-bb/plugin-sdk",
3
- "version": "0.4.55",
3
+ "version": "0.4.84",
4
4
  "homepage": "https://github.com/get-bb/bb#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/get-bb/bb/issues"
@@ -56,6 +56,12 @@
56
56
  "import": "./dist/environment-provider.js",
57
57
  "default": "./dist/environment-provider.js"
58
58
  },
59
+ "./machine-provider": {
60
+ "source": "./src/machine-provider.ts",
61
+ "types": "./bundled-types/bb-plugin-sdk-machine-provider.d.ts",
62
+ "import": "./dist/machine-provider.js",
63
+ "default": "./dist/machine-provider.js"
64
+ },
59
65
  "./app": {
60
66
  "source": "./src/app.ts",
61
67
  "types": "./bundled-types/bb-plugin-sdk-app.d.ts",