@botbuddy/cli 1.61.3 → 1.61.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.
@@ -1,7 +1,7 @@
1
1
  {
2
- "release_version": "1.61.3",
3
- "source_commit": "7c2f279f36d7ceb5f8d7a84e5698fe2dcf2f22e8",
4
- "source_sha256": "170bd3f629a5bc5f894ae46191da975ff49f6761fd1b64da2aa6c2f76cc2472a",
2
+ "release_version": "1.61.5",
3
+ "source_commit": "f53c37db06f6fc5ab544623f2a273c4e21de1b72",
4
+ "source_sha256": "7280c8ff78ee957cc06bc7d07ed12a4b36a4c64c3a5968a710a1d371ab1b33c9",
5
5
  "target": "darwin-arm64",
6
- "sha256": "612de50e3fd1d37a43007f8aaca735dbb2f5cc92e9c2b8d96fd198f0153ef6ec"
6
+ "sha256": "c57ada939dcf3d5c0ad7cefdbf27ae9995a06a14446ca8774a94c76c61083b00"
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
- "release_version": "1.61.3",
3
- "source_commit": "7c2f279f36d7ceb5f8d7a84e5698fe2dcf2f22e8",
4
- "source_sha256": "170bd3f629a5bc5f894ae46191da975ff49f6761fd1b64da2aa6c2f76cc2472a",
2
+ "release_version": "1.61.5",
3
+ "source_commit": "f53c37db06f6fc5ab544623f2a273c4e21de1b72",
4
+ "source_sha256": "7280c8ff78ee957cc06bc7d07ed12a4b36a4c64c3a5968a710a1d371ab1b33c9",
5
5
  "target": "linux-x64",
6
- "sha256": "75f71518b6446f547153e71e5c2a2cf15c8c475fd0e2039acfe561fa5cd66309"
6
+ "sha256": "1002356a262f413e793af8779bec4208426d47bc38d6e5ef115a06b0fa4fd64c"
7
7
  }
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "schema_version": 1,
3
- "release_version": "1.61.3",
4
- "source_commit": "7c2f279f36d7ceb5f8d7a84e5698fe2dcf2f22e8",
5
- "source_sha256": "170bd3f629a5bc5f894ae46191da975ff49f6761fd1b64da2aa6c2f76cc2472a",
3
+ "release_version": "1.61.5",
4
+ "source_commit": "f53c37db06f6fc5ab544623f2a273c4e21de1b72",
5
+ "source_sha256": "7280c8ff78ee957cc06bc7d07ed12a4b36a4c64c3a5968a710a1d371ab1b33c9",
6
6
  "targets": {
7
7
  "darwin-arm64": {
8
8
  "goos": "darwin",
9
9
  "goarch": "arm64",
10
10
  "binary": "daemon/bin/darwin-arm64/botbuddy-daemon",
11
- "sha256": "612de50e3fd1d37a43007f8aaca735dbb2f5cc92e9c2b8d96fd198f0153ef6ec",
11
+ "sha256": "c57ada939dcf3d5c0ad7cefdbf27ae9995a06a14446ca8774a94c76c61083b00",
12
12
  "mode": "0755"
13
13
  },
14
14
  "linux-x64": {
15
15
  "goos": "linux",
16
16
  "goarch": "amd64",
17
17
  "binary": "daemon/bin/linux-x64/botbuddy-daemon",
18
- "sha256": "75f71518b6446f547153e71e5c2a2cf15c8c475fd0e2039acfe561fa5cd66309",
18
+ "sha256": "1002356a262f413e793af8779bec4208426d47bc38d6e5ef115a06b0fa4fd64c",
19
19
  "mode": "0755"
20
20
  }
21
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbuddy/cli",
3
- "version": "1.61.3",
3
+ "version": "1.61.5",
4
4
  "description": "BotBuddy \u2014 Swarm coordination CLI for multi-agent workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,15 +4,18 @@
4
4
  // no local approval polling loop: host policy wakes only through the pushed
5
5
  // BOT-2324 raw wait condition, then this flow re-registers server-side.
6
6
  import { createHash } from "node:crypto";
7
+ import { dirname } from "node:path";
7
8
 
8
9
  import { DAEMON_PRODUCTION_MCP_SERVER, enrollDaemonDeviceProfileFromBootstrapIdentity } from "./enrollment.mjs";
9
10
  import { installRemoteService, installedBinaryPath, stageRemoteDaemonRelease } from "./installer.mjs";
10
11
  import {
11
12
  createRemoteBootstrapIdentity,
12
13
  deliverDaemonProfileFromBootstrapIdentityOverSSH,
14
+ installDaemonRuntimeConfigOverSSH,
13
15
  inspectRemoteBootstrapIdentity,
14
16
  verifyRemoteServiceIdentity,
15
17
  } from "./profile-delivery.mjs";
18
+ import { buildRuntimeConfig } from "./runtime-config.mjs";
16
19
  import { validateDaemonRelease } from "./release.mjs";
17
20
  import { renderLinuxUnit, renderMacDaemon } from "./service.mjs";
18
21
 
@@ -45,7 +48,7 @@ function exactObject(value, keys) {
45
48
  function checkedSpec(spec) {
46
49
  const keys = [
47
50
  "mode", "serverUrl", "sshHost", "serviceUser", "daemonBinary", "profilePath", "requestingHost",
48
- "runtimeEngineId", "platform", "arch", "releaseRoot", "home", "config", "approvalTimeoutSeconds",
51
+ "runtimeEngineId", "platform", "arch", "releaseRoot", "home", "config", "hostPolicy", "approvalTimeoutSeconds",
49
52
  ];
50
53
  const optional = ["caller", "callerHarness", "callerTicket"];
51
54
  if (!spec || typeof spec !== "object" || Array.isArray(spec) ||
@@ -59,7 +62,11 @@ function checkedSpec(spec) {
59
62
  typeof spec.requestingHost !== "string" || !spec.requestingHost || Buffer.byteLength(spec.requestingHost, "utf8") > 80 ||
60
63
  typeof spec.runtimeEngineId !== "string" || !ENGINE_ID_RE.test(spec.runtimeEngineId) ||
61
64
  !["darwin", "linux"].includes(spec.platform) || typeof spec.arch !== "string" || !spec.arch ||
62
- !validPath(spec.releaseRoot) || !validPath(spec.home) || !validPath(spec.config) ||
65
+ !validPath(spec.releaseRoot) || !validPath(spec.home) || !validPath(spec.config) || !spec.config.endsWith("/daemon.json") ||
66
+ dirname(spec.config) !== dirname(spec.profilePath) ||
67
+ !spec.hostPolicy || typeof spec.hostPolicy !== "object" || Array.isArray(spec.hostPolicy) ||
68
+ Object.keys(spec.hostPolicy).length !== 4 ||
69
+ !["machineId", "dockerEndpoint", "supabaseBinary", "dockerCLI"].every((key) => typeof spec.hostPolicy[key] === "string" && spec.hostPolicy[key]) ||
63
70
  !Number.isSafeInteger(spec.approvalTimeoutSeconds) || spec.approvalTimeoutSeconds < 1 || spec.approvalTimeoutSeconds > 3_600 ||
64
71
  optional.some((key) => spec[key] !== undefined && (typeof spec[key] !== "string" || !spec[key]))
65
72
  ) fail("daemon_bootstrap_spec_invalid");
@@ -215,12 +222,14 @@ export async function bootstrapFreshDaemon(input, {
215
222
  enroll = enrollDaemonDeviceProfileFromBootstrapIdentity,
216
223
  deliver = deliverDaemonProfileFromBootstrapIdentityOverSSH,
217
224
  installRemote = installRemoteService,
225
+ installRuntimeConfig = installDaemonRuntimeConfigOverSSH,
218
226
  } = {}) {
219
227
  const spec = checkedSpec(input);
220
228
  if (typeof registerHost !== "function" || typeof verifyRemoteIdentity !== "function" ||
221
229
  typeof createIdentity !== "function" || typeof inspectIdentity !== "function" ||
222
230
  typeof validateRelease !== "function" || typeof stageRemote !== "function" || typeof enroll !== "function" ||
223
231
  typeof deliver !== "function" || typeof installRemote !== "function" ||
232
+ typeof installRuntimeConfig !== "function" ||
224
233
  (awaitApproval !== null && typeof awaitApproval !== "function") ||
225
234
  (onApprovalRequired !== null && typeof onApprovalRequired !== "function") ||
226
235
  (resumeStore !== null && (typeof resumeStore !== "object" || typeof resumeStore.save !== "function" ||
@@ -355,6 +364,42 @@ export async function bootstrapFreshDaemon(input, {
355
364
  }
356
365
  }
357
366
 
367
+ let runtimeConfig;
368
+ try {
369
+ runtimeConfig = buildRuntimeConfig({
370
+ host_key: publicIdentity.hostKey,
371
+ state_dir: dirname(spec.config),
372
+ docker_endpoint: spec.hostPolicy.dockerEndpoint,
373
+ expected_engine_id: spec.runtimeEngineId,
374
+ machine_id: spec.hostPolicy.machineId,
375
+ supabase_binary: spec.hostPolicy.supabaseBinary,
376
+ docker_cli: spec.hostPolicy.dockerCLI,
377
+ host_credential_profile: spec.profilePath,
378
+ device_consent: true,
379
+ runtime_version: `go-daemon/${release.releaseVersion}`,
380
+ runtime_git_sha: release.sourceCommit,
381
+ runtime_protocol_version: 2,
382
+ });
383
+ } catch {
384
+ fail("daemon_bootstrap_runtime_config_invalid");
385
+ }
386
+ try {
387
+ const installed = await installRuntimeConfig({
388
+ config: JSON.stringify(runtimeConfig),
389
+ configPath: spec.config,
390
+ sshHost: spec.sshHost,
391
+ serviceUser: spec.serviceUser,
392
+ daemonBinary: trustedDaemonBinary,
393
+ profilePath: spec.profilePath,
394
+ expectedHostKey: publicIdentity.hostKey,
395
+ directInstall: remote.directInstall,
396
+ });
397
+ if (!installed || installed.installed !== true) fail("daemon_bootstrap_runtime_config_failed");
398
+ } catch (error) {
399
+ if (error instanceof DaemonBootstrapError) throw error;
400
+ fail("daemon_bootstrap_runtime_config_failed");
401
+ }
402
+
358
403
  const serviceSpec = {
359
404
  user: spec.serviceUser,
360
405
  home: spec.home,
@@ -29,6 +29,7 @@ import {
29
29
  createRemoteBootstrapIdentity,
30
30
  deliverDaemonProfileFromBootstrapIdentityOverSSH,
31
31
  deliverDaemonProfileFromHelperIdentityOverSSH,
32
+ installDaemonRuntimeConfigOverSSH,
32
33
  inspectRemoteBootstrapIdentity,
33
34
  verifyRemoteHelperIdentity as verifyRemoteHelperIdentityOnTarget,
34
35
  verifyRemoteServiceIdentity,
@@ -285,6 +286,7 @@ export async function cmdDaemon(args, {
285
286
  verifyRuntime = checkRuntimeConfig,
286
287
  enroll = enrollDaemonDeviceProfileFromHelperIdentity,
287
288
  deliver = deliverDaemonProfileFromHelperIdentityOverSSH,
289
+ installRuntimeConfig = installDaemonRuntimeConfigOverSSH,
288
290
  verifyRemoteIdentity = verifyRemoteServiceIdentity,
289
291
  verifyRemoteHelperIdentity = verifyRemoteHelperIdentityOnTarget,
290
292
  bootstrap = bootstrapFreshDaemon,
@@ -332,6 +334,7 @@ export async function cmdDaemon(args, {
332
334
  },
333
335
  enroll: enrollBootstrap,
334
336
  deliver: deliverBootstrap,
337
+ installRuntimeConfig,
335
338
  installRemote,
336
339
  onApprovalRequired: ({ approval_url: approvalUrl, host_key: hostKey }) => {
337
340
  notice(`bb daemon bootstrap: owner approval required for ${hostKey}: ${approvalUrl}`);
@@ -334,3 +334,32 @@ export async function deliverDaemonProfileFromBootstrapIdentityOverSSH({
334
334
  installFlag: "--install-profile-from-bootstrap-identity",
335
335
  });
336
336
  }
337
+
338
+ // Runtime policy is public host metadata, never a credential. It is still
339
+ // installed through the daemon's descriptor-pinned private root so a caller
340
+ // cannot redirect it with a symlink or broaden a prior host's policy.
341
+ export async function installDaemonRuntimeConfigOverSSH({
342
+ config,
343
+ configPath,
344
+ sshHost,
345
+ serviceUser,
346
+ daemonBinary,
347
+ profilePath,
348
+ expectedHostKey,
349
+ sshBin = "ssh",
350
+ spawnImpl = realSpawn,
351
+ env = process.env,
352
+ timeoutMs = DEFAULT_TIMEOUT_MS,
353
+ directInstall = false,
354
+ } = {}) {
355
+ safeInputs({ sshHost, serviceUser, daemonBinary, profilePath, expectedHostKey, sshBin, spawnImpl, env, timeoutMs, directInstall });
356
+ if (typeof config !== "string" || config.length === 0 || Buffer.byteLength(config, "utf8") > 64 * 1024 ||
357
+ typeof configPath !== "string" || !SAFE_ABSOLUTE_PATH.test(configPath) || !configPath.endsWith("/daemon.json")) throw invalid();
358
+ const command = remoteDaemonCommand({
359
+ serviceUser, daemonBinary, directInstall,
360
+ args: ["--install-runtime-config", "--config", configPath, "--profile", profilePath, "--expected-host-key", expectedHostKey],
361
+ });
362
+ const stdout = await fixedSsh({ sshHost, sshBin, spawnImpl, env, timeoutMs, command, stdin: Buffer.from(config, "utf8") });
363
+ if (!stdout.equals(Buffer.from('{"installed":true}\n'))) throw failed();
364
+ return { installed: true };
365
+ }
package/src/stack.mjs CHANGED
@@ -219,6 +219,8 @@ ${bold("run OPTIONS")}
219
219
  --local-only Require a co-located host (execution "local"); no bundle is built.
220
220
  Default (neither flag): execution "auto" — co-located if it admits now,
221
221
  else a remote host with capacity; the bundle digest is sent up front.
222
+ --execution-ring R Capacity rollout ring: stable (default) excludes beta daemon hosts.
223
+ beta is explicit opt-in and requires \`stack run --remote\`.
222
224
  --callback-port N The caller runs a listener the stack calls back through
223
225
  host.docker.internal:N (e.g. 4600): sent as callback_port. On a REMOTE
224
226
  lease run first probes the host with a throwaway reverse forward and opens
@@ -365,7 +367,7 @@ export function parseStackArgs(argv) {
365
367
  connectionFile: null, noWait: false, claimOnly: false, localExec: false, keepVolumes: false, waitCapacity: null, holdOnTimeout: null,
366
368
  dockerContext: null, dockerEndpoint: null,
367
369
  // BOT-2142: execution placement (null = command default), remote-lease callback + bundle cap.
368
- execution: null, remote: false, localOnly: false, callbackPort: null, bundleMaxBytes: null,
370
+ execution: null, remote: false, localOnly: false, executionRing: "stable", callbackPort: null, bundleMaxBytes: null,
369
371
  // BOT-2269: exact stack-relative files shipped in the remote-lease bundle despite the
370
372
  // default exclusions (`--bundle-include`, repeatable).
371
373
  bundleInclude: [],
@@ -453,6 +455,14 @@ export function parseStackArgs(argv) {
453
455
  }
454
456
  case "--remote": opts.remote = true; break;
455
457
  case "--local-only": opts.localOnly = true; break;
458
+ case "--execution-ring": {
459
+ if (need(a, rest[i + 1])) {
460
+ const ring = rest[++i];
461
+ if (ring !== "stable" && ring !== "beta") errors.push("--execution-ring must be stable or beta");
462
+ else opts.executionRing = ring;
463
+ }
464
+ break;
465
+ }
456
466
  case "--callback-port": {
457
467
  if (need(a, rest[i + 1])) {
458
468
  const n = Number(rest[++i]);
@@ -508,6 +518,12 @@ export function parseStackArgs(argv) {
508
518
  if (opts.remote && opts.localExec) {
509
519
  errors.push("--remote cannot be combined with --local-exec: a local-exec stack is started on THIS machine");
510
520
  }
521
+ if (opts.executionRing === "beta" && command !== "run") {
522
+ errors.push("--execution-ring beta is valid only with `stack run`");
523
+ }
524
+ if (opts.executionRing === "beta" && !opts.remote) {
525
+ errors.push("--execution-ring beta requires --remote so the beta daemon can serve the stack");
526
+ }
511
527
  if ((opts.remote || opts.localOnly) && !["up", "run"].includes(command)) {
512
528
  errors.push("--remote and --local-only are valid only with `stack up` or `stack run`");
513
529
  } else if (command === "up" && opts.remote) {
@@ -3846,6 +3862,7 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
3846
3862
  stack_path: execution.stackPath, worktree_root: execution.worktreeRoot,
3847
3863
  machine_uuid: hardwareUuid,
3848
3864
  execution: executionRequest,
3865
+ execution_ring: opts.executionRing,
3849
3866
  ...(bundle ? { bundle_sha256: bundle.sha256 } : {}),
3850
3867
  ...(stackDigest ? { stack_digest: stackDigest } : {}),
3851
3868
  // BOT-1803 (Codex #1160 P1): a run never re-adopts an idle stack it cannot prove current.