@botbuddy/cli 1.63.23 → 1.63.25

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.63.23",
3
- "source_commit": "d51ef61947fbda05da967f8f6a6d30affb2ff2bf",
4
- "source_sha256": "f3a066d0c0a41b186a8edc7602b00bd8ae522725aa5a8d678740235be11816b1",
2
+ "release_version": "1.63.25",
3
+ "source_commit": "8958cc912b2b6fd6621bb1d57d9aaacd8a0de862",
4
+ "source_sha256": "6ab5c67c7a6974a6e05cb648740810bbdd3984c65139a4026362e996313e0496",
5
5
  "target": "darwin-arm64",
6
- "sha256": "91dc67a61e2ec59657aab6a7c932bcc27d57c713a82e51747201576d570eeae6"
6
+ "sha256": "a79415429b2226770f056344eaeb8db9d64094281f308c1c9a31ed919688e3e7"
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
- "release_version": "1.63.23",
3
- "source_commit": "d51ef61947fbda05da967f8f6a6d30affb2ff2bf",
4
- "source_sha256": "f3a066d0c0a41b186a8edc7602b00bd8ae522725aa5a8d678740235be11816b1",
2
+ "release_version": "1.63.25",
3
+ "source_commit": "8958cc912b2b6fd6621bb1d57d9aaacd8a0de862",
4
+ "source_sha256": "6ab5c67c7a6974a6e05cb648740810bbdd3984c65139a4026362e996313e0496",
5
5
  "target": "linux-x64",
6
- "sha256": "bffecca81f03de0213a8800db20f787fd87538eb2ecb1e217028fcbf0aad19d5"
6
+ "sha256": "54a1b5959533132a7581445b9309ad456022dbf04bd5f92482728eb917c4ae8a"
7
7
  }
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "schema_version": 1,
3
- "release_version": "1.63.23",
4
- "source_commit": "d51ef61947fbda05da967f8f6a6d30affb2ff2bf",
5
- "source_sha256": "f3a066d0c0a41b186a8edc7602b00bd8ae522725aa5a8d678740235be11816b1",
3
+ "release_version": "1.63.25",
4
+ "source_commit": "8958cc912b2b6fd6621bb1d57d9aaacd8a0de862",
5
+ "source_sha256": "6ab5c67c7a6974a6e05cb648740810bbdd3984c65139a4026362e996313e0496",
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": "91dc67a61e2ec59657aab6a7c932bcc27d57c713a82e51747201576d570eeae6",
11
+ "sha256": "a79415429b2226770f056344eaeb8db9d64094281f308c1c9a31ed919688e3e7",
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": "bffecca81f03de0213a8800db20f787fd87538eb2ecb1e217028fcbf0aad19d5",
18
+ "sha256": "54a1b5959533132a7581445b9309ad456022dbf04bd5f92482728eb917c4ae8a",
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.63.23",
3
+ "version": "1.63.25",
4
4
  "description": "BotBuddy \u2014 Swarm coordination CLI for multi-agent workflows",
5
5
  "type": "module",
6
6
  "bin": {
package/src/stack.mjs CHANGED
@@ -65,16 +65,17 @@ export const STACK_SCHEMA_VERSION = 1;
65
65
  * BOT-2780 / BOT-2788: a `stack run` that may be served by another host reserves a requester
66
66
  * origin and allocates through BOT-2499's identified admission. Stable (the default ring):
67
67
  * auto or remote placement, with or without `--reap` (an idle stack is continued on the same
68
- * logical origin by a later run); runs that pin a host, a prepared stack path, a fixed nonce
69
- * or a co-located placement, or pass a callback port (BOT-2775), keep the legacy path
70
- * unchanged. Beta: BOT-2780's `--remote --reap` gate, which refuses those contexts instead.
68
+ * logical origin by a later run), callback runs included (BOT-2775 stable activation: they ask
69
+ * for callback_policy auto, see runStackLifecycle); runs that pin a host, a prepared stack
70
+ * path, a fixed nonce or a co-located placement keep the legacy path unchanged. Beta:
71
+ * BOT-2780's `--remote --reap` gate, which refuses those contexts instead.
71
72
  */
72
73
  export const requesterOriginEnabled = (opts) => {
73
74
  if (opts.execution === "local" || opts.localOnly || opts.localExec) return false;
74
75
  // Beta keeps BOT-2780's gate (remote + --reap); its idle/callback runs stay on BOT-2775's path.
75
76
  if (opts.executionRing === "beta") return opts.execution === "remote" && opts.reap === true;
76
77
  if (opts.execution != null && opts.execution !== "auto" && opts.execution !== "remote") return false;
77
- return opts.host == null && opts.stackNonce == null && (opts.stackPath == null || opts.stackPath === "" || opts.stackPath === ".") && opts.callbackPort == null;
78
+ return opts.host == null && opts.stackNonce == null && (opts.stackPath == null || opts.stackPath === "" || opts.stackPath === ".");
78
79
  };
79
80
  // Exact replays of one identified request after an uncertain answer (lost response, 5xx,
80
81
  // ADMISSION_UNCERTAIN, pending delivery). The same request_id can never create a second lease.
@@ -277,16 +278,20 @@ ${bold("run OPTIONS")}
277
278
  is requested through the identified admission (exact replay after a lost
278
279
  response, no fallback).
279
280
  --callback-port N The caller runs a listener the stack calls back through
280
- host.docker.internal:N (e.g. 4600): sent as callback_port. On a stable or
281
- auto REMOTE lease run first probes the host with a throwaway reverse forward
282
- and opens -R 127.0.0.1:N:127.0.0.1:N only when the host keeps it on loopback
283
- (sshd GatewayPorts no/clientspecified); otherwise exit 5
284
- CALLBACK_NOT_LOOPBACK (BOT-2184). With --remote --execution-ring beta it
285
- instead requires a Linux Go runtime with native callbacks (protocol 5): a
286
- private SSH Unix-socket route to a fixed requester gateway, renewed against
287
- current owned authorization (BOT-2775). Unknown/older hosts or a backend
288
- that does not acknowledge it fail HOST_CALLBACK_TRANSPORT_UNSUPPORTED; the
289
- child never receives the route capability.
281
+ host.docker.internal:N (e.g. 4600): sent as callback_port. A remote
282
+ callback is served by a Linux Go runtime with native callbacks (protocol
283
+ 5): a private SSH Unix-socket route to a fixed requester gateway, renewed
284
+ against current owned authorization (BOT-2775). An unpinned stable/auto run
285
+ asks for callback_policy auto: it runs co-located or on a host with native
286
+ callbacks, and Swift Helper hosts no longer take it remotely. With --remote
287
+ --execution-ring beta the native transport is required explicitly. A remote
288
+ lease the backend does not acknowledge with it fails
289
+ HOST_CALLBACK_TRANSPORT_UNSUPPORTED (never a legacy -R); the child never
290
+ receives the route capability. A run pinned with --host, --stack-path or a
291
+ fixed nonce keeps the legacy -R forward: it first probes the host with a
292
+ throwaway reverse forward and opens -R 127.0.0.1:N:127.0.0.1:N only when
293
+ the host keeps it on loopback (sshd GatewayPorts no/clientspecified);
294
+ otherwise exit 5 CALLBACK_NOT_LOOPBACK (BOT-2184).
290
295
  --bundle-max-mb N Remote-lease bundle size cap in MB (default 50, compressed). The bundle
291
296
  excludes .bb-stack.json, node_modules, supabase/.temp|.branches and
292
297
  untracked-secret dotenv files (.env, .env.local, .env.*.local).
@@ -331,9 +336,11 @@ ${bold("IDLE LEASES")} (BOT-1803)
331
336
 
332
337
  ${bold("REMOTE LEASES")} (BOT-2142, stack run)
333
338
  The receipt carries execution_mode (local|remote), execution_host_key, requester_host,
334
- transport (none|ssh-forward), and for a remote lease forwards [{name, remote, local}] + bundle.
339
+ transport (none|ssh-forward), and for a remote lease forwards [{name, remote, local}] + bundle,
340
+ plus callback_transport "native_origin_v1" when --callback-port used the native route (BOT-2775).
335
341
  The child also gets BOTBUDDY_STACK_EXECUTION_MODE and BOTBUDDY_STACK_TRANSPORT.
336
- Requeue (BOT-2207/BOT-2334): under auto, including --callback-port, a lease that QUEUES on this machine uploads its bundle at once;
342
+ Requeue (BOT-2207/BOT-2334): under auto, including --callback-port, a lease that QUEUES on this machine uploads its bundle at once
343
+ (a callback lease moves only to a host with native callbacks, BOT-2775);
337
344
  every minute the control plane re-offers it to this owner's other hosts and it may move there
338
345
  (placement_reason moved_remote_on_requeue), and run then forwards to the new host. While queued,
339
346
  run prints one \`considered: <host> — <verdict>\` line per host weighed (e.g. "census stale 131 s");
@@ -347,11 +354,13 @@ ${bold("REMOTE LEASES")} (BOT-2142, stack run)
347
354
  4 STACK_EXECUTION_MODE_MISMATCH the slot already holds a lease in another mode/host — stack done it first
348
355
  (the four codes above create NO lease)
349
356
  5 BUNDLE_UPLOAD_FAILED the signed bundle upload failed (the lease is released first)
350
- 5 CALLBACK_NOT_LOOPBACK --callback-port (stable/auto): the GatewayPorts probe (before any real -R)
357
+ 5 CALLBACK_NOT_LOOPBACK --callback-port (pinned legacy -R): the GatewayPorts probe (before any real -R)
351
358
  or the post-forward check found the host's -R listener not loopback-only
352
359
  (sshd GatewayPorts yes), or could not check it (no lsof, no listener)
353
- 5 HOST_CALLBACK_TRANSPORT_UNSUPPORTED --callback-port (beta remote): native callback transport is
360
+ 5 HOST_CALLBACK_TRANSPORT_UNSUPPORTED --callback-port (remote native): native callback transport is
354
361
  unavailable; needs the installed Linux Go runtime (protocol 5) and backend
362
+ 4 REQUESTER_ORIGIN_EXPLICIT_CONFLICT an unpinned stable --callback-port run in a repo whose
363
+ supabase/config.toml sets its own [auth] external_url (no lease requested)
355
364
  5 CALLBACK_NOT_READY / CALLBACK_CONSUMING_NAMESPACE_UNREACHABLE the native route did not
356
365
  answer from the stack's own network before the child (lease reaped)
357
366
  6 CALLBACK_TRANSPORT_LOST / CALLBACK_AUTHORIZATION_LOST the native route or its current
@@ -1040,7 +1049,9 @@ export async function resolveRunExecution(cwd, opts, { materialize = materialize
1040
1049
  // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal -- canonical realpath of the current worktree; fixed own source config, never a request-supplied path.
1041
1050
  await patchRequesterAuthOrigin(readFileSync(join(worktreeRoot, "supabase", "config.toml"), "utf8"), requesterOrigin.origin);
1042
1051
  } catch (error) {
1043
- if (error?.code !== "REQUESTER_ORIGIN_EXPLICIT_CONFLICT" || opts.executionRing === "beta") throw error;
1052
+ // BOT-2775: a stable callback run is refused too. The legacy allocator would place it
1053
+ // on any remote host with the legacy -R callback, which Linux Go hosts cannot serve.
1054
+ if (error?.code !== "REQUESTER_ORIGIN_EXPLICIT_CONFLICT" || opts.executionRing === "beta" || opts.callbackPort != null) throw error;
1044
1055
  warn("stack: supabase/config.toml sets its own [auth] external_url, so this run keeps the legacy (non-requester-origin) path");
1045
1056
  await requesterOrigin.close();
1046
1057
  requesterOrigin = null;
@@ -4128,8 +4139,14 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
4128
4139
  "an idle stack on this slot will be re-provisioned rather than reused.");
4129
4140
  }
4130
4141
  idleLease.stackDigest = stackDigest;
4131
- // Runtime-first rollout: only explicit beta callers activate the new transport.
4142
+ // BOT-2775: an explicit beta caller requires the native transport up front. A stable/default
4143
+ // run with a callback asks for callback_policy auto on its identified admission (BOT-2779):
4144
+ // the backend places it on a host that serves native callbacks (Linux Go protocol 5) or
4145
+ // co-located, and the transport the lease answers with decides the route (useNativeCallback).
4146
+ // A remote answer without the native transport fails closed; it never falls back to -R.
4132
4147
  const requireNativeCallback = opts.callbackPort != null && opts.executionRing === "beta" && executionRequest !== "local";
4148
+ const autoCallbackPolicy = opts.callbackPort != null && opts.executionRing !== "beta" && executionRequest !== "local" && Boolean(execution.admissionRequestId);
4149
+ let useNativeCallback = requireNativeCallback;
4133
4150
  const leaseArgs = {
4134
4151
  slot, host_key: opts.host || undefined, repo: opts.repo, ticket_id: opts.ticket,
4135
4152
  ticket_url: opts.ticketUrl || undefined, pr_id: opts.prId || undefined, pr_url: opts.prUrl || undefined,
@@ -4144,7 +4161,8 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
4144
4161
  require_stack_digest: true,
4145
4162
  // BOT-2269: what the bundle carries beyond the defaults (paths, sha256, key NAMES).
4146
4163
  ...(bundle?.included?.length ? { bundle_included: bundle.included } : {}),
4147
- ...(opts.callbackPort != null ? { callback_port: opts.callbackPort, ...(requireNativeCallback ? { callback_transport: NATIVE_CALLBACK_TRANSPORT } : {}) } : {}),
4164
+ ...(opts.callbackPort != null ? { callback_port: opts.callbackPort,
4165
+ ...(requireNativeCallback ? { callback_transport: NATIVE_CALLBACK_TRANSPORT } : autoCallbackPolicy ? { callback_policy: "auto" } : {}) } : {}),
4148
4166
  };
4149
4167
  // BOT-2780: the identified admission key and a digest of the immutable origin context.
4150
4168
  // The intent (including the bundle digest that names the reserved origin) is fingerprinted
@@ -4508,7 +4526,7 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
4508
4526
  placement.mode = request.data.execution_mode ?? null;
4509
4527
  placement.hostKey = request.data.host_key ?? null;
4510
4528
  placement.requesterHost = request.data.requester_host ?? null;
4511
- if (remoteLease && requireNativeCallback && request.data.callback_transport !== NATIVE_CALLBACK_TRANSPORT) {
4529
+ if (remoteLease && (requireNativeCallback || autoCallbackPolicy) && request.data.callback_transport !== NATIVE_CALLBACK_TRANSPORT) {
4512
4530
  const cleanup = await requestCleanup();
4513
4531
  return { exitCode: cleanup.ok ? EXIT.BACKEND : EXIT.CLEANUP_FAILED, outcome: "error", code: "HOST_CALLBACK_TRANSPORT_UNSUPPORTED", leaseId, error: "backend did not acknowledge required remote callback transport", cleanup };
4514
4532
  }
@@ -4779,17 +4797,27 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
4779
4797
  stderrLine(`stack: lease ${leaseId} moved to ${placement.hostKey ?? "another host"} while queued (${placement.placementReason ?? "moved"}) — forwarding to it`);
4780
4798
  }
4781
4799
  let childConnection = current.data.connection;
4800
+ // BOT-2775: a callback_policy auto lease serves its callback natively whenever it is remote,
4801
+ // including one that moved to a remote host while queued. Its current lease must name the
4802
+ // native transport; anything else is refused before any forward opens.
4803
+ if (servedRemote && autoCallbackPolicy) {
4804
+ if (current.data.callback_transport !== NATIVE_CALLBACK_TRANSPORT) {
4805
+ const cleanup = await requestCleanup();
4806
+ return { exitCode: cleanup.ok ? EXIT.BACKEND : EXIT.CLEANUP_FAILED, outcome: "error", code: "HOST_CALLBACK_TRANSPORT_UNSUPPORTED", leaseId, error: "the remote lease does not carry the native callback transport its callback policy requires", cleanup };
4807
+ }
4808
+ useNativeCallback = true;
4809
+ }
4782
4810
  if (servedRemote) {
4783
4811
  // BOT-2142: forward every stack port over ONE ssh and hand the child the local ports.
4784
4812
  sshTarget = current.data.connection?.ssh_target ?? current.data.ssh_target ?? sshTarget;
4785
4813
  try {
4786
- tunnel = await openTunnel({ connection: current.data.connection, sshTarget, hostKey: placement.hostKey, callbackPort: requireNativeCallback ? null : opts.callbackPort,
4787
- ...(requireNativeCallback ? { reserveLocalPorts: [opts.callbackPort] } : {}) });
4814
+ tunnel = await openTunnel({ connection: current.data.connection, sshTarget, hostKey: placement.hostKey, callbackPort: useNativeCallback ? null : opts.callbackPort,
4815
+ ...(useNativeCallback ? { reserveLocalPorts: [opts.callbackPort] } : {}) });
4788
4816
  } catch (error) {
4789
4817
  const cleanup = await requestCleanup();
4790
4818
  return { exitCode: EXIT.BACKEND, outcome: "error", code: error?.code ?? "REMOTE_UNREACHABLE", leaseId, error: String(error?.message ?? error), cleanup };
4791
4819
  }
4792
- if (requireNativeCallback) {
4820
+ if (useNativeCallback) {
4793
4821
  try {
4794
4822
  nativeCallback = await openCallback({ current: current.data, leaseId, hostKey: placement.hostKey, callbackPort: opts.callbackPort, requesterMachineId: machineUuidFn(), sshTarget,
4795
4823
  readCurrent: async (signal) => { let observed; try { observed = await api.get(leaseId, { signal }); } catch (error) { throw Object.assign(new Error(`current callback status unreadable (${error?.message ?? error})`), { transient: true }); } if (!observed?.ok) {
@@ -4802,6 +4830,7 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
4802
4830
  return observed.data; },
4803
4831
  });
4804
4832
  nativeCallback.onExit((exit) => { if (childResult) return; callbackLost = exit; fencing = true; stopChild("SIGTERM", EXIT.LEASE_FAILED); });
4833
+ placement.callbackTransport = NATIVE_CALLBACK_TRANSPORT;
4805
4834
  } catch (error) {
4806
4835
  await closeTunnel(); const cleanup = await requestCleanup();
4807
4836
  return { exitCode: cleanup.ok && !callbackCleanupError && error?.code !== "CALLBACK_CLEANUP_UNCERTAIN" ? EXIT.BACKEND : EXIT.CLEANUP_FAILED, outcome: "error", code: error?.code ?? "CALLBACK_NOT_READY", leaseId, error: String(error?.message ?? "callback transport unavailable"), cleanup };
@@ -5061,6 +5090,8 @@ export function runReceiptFields(result) {
5061
5090
  execution_host_key: result.execution?.hostKey ?? null,
5062
5091
  requester_host: result.execution?.requesterHost ?? null,
5063
5092
  transport: result.execution?.transport ?? "none",
5093
+ // BOT-2775: present only when the callback was served over the native route.
5094
+ ...(result.execution?.callbackTransport ? { callback_transport: result.execution.callbackTransport } : {}),
5064
5095
  // BOT-2780: the reserved requester origin generated auth links name (never a credential).
5065
5096
  ...(result.execution?.requesterOrigin ? { requester_origin: result.execution.requesterOrigin } : {}),
5066
5097
  // BOT-2788: "previous" (an idle stack's origin re-reserved), "previous_port_busy" or "new".