@botbuddy/cli 1.63.22 → 1.63.24

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.22",
3
- "source_commit": "f92023885ef6f9fc477091b12f6769cae9d85560",
4
- "source_sha256": "2dde93a23b6af3842f96d073bd0bd0113f5a6f3a3343dbb5a0037627aff377e9",
2
+ "release_version": "1.63.24",
3
+ "source_commit": "9da358c3b61afaf3c70fcbf9e73cc71500137b65",
4
+ "source_sha256": "f187bf83378e0de983fa1ab3e97928d784f08d8149598889de8d080b8221512f",
5
5
  "target": "darwin-arm64",
6
- "sha256": "d06bccfe161d0f6e6d574c8ae01611b696b801705c565a091fbe3a9f2d4d04d8"
6
+ "sha256": "29947b82499d5e2db4048530bf203f52546aef31625c59e017d2c2666b6ee5d5"
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
- "release_version": "1.63.22",
3
- "source_commit": "f92023885ef6f9fc477091b12f6769cae9d85560",
4
- "source_sha256": "2dde93a23b6af3842f96d073bd0bd0113f5a6f3a3343dbb5a0037627aff377e9",
2
+ "release_version": "1.63.24",
3
+ "source_commit": "9da358c3b61afaf3c70fcbf9e73cc71500137b65",
4
+ "source_sha256": "f187bf83378e0de983fa1ab3e97928d784f08d8149598889de8d080b8221512f",
5
5
  "target": "linux-x64",
6
- "sha256": "a5ce863685465d6ef9a20ad98758c2d14f4306f5e60dfb013f6dc8388e8405b4"
6
+ "sha256": "855a97c0028f1dbd208d7ead972b6ffbc195a945b22e14ba356fdc7365e730b5"
7
7
  }
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "schema_version": 1,
3
- "release_version": "1.63.22",
4
- "source_commit": "f92023885ef6f9fc477091b12f6769cae9d85560",
5
- "source_sha256": "2dde93a23b6af3842f96d073bd0bd0113f5a6f3a3343dbb5a0037627aff377e9",
3
+ "release_version": "1.63.24",
4
+ "source_commit": "9da358c3b61afaf3c70fcbf9e73cc71500137b65",
5
+ "source_sha256": "f187bf83378e0de983fa1ab3e97928d784f08d8149598889de8d080b8221512f",
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": "d06bccfe161d0f6e6d574c8ae01611b696b801705c565a091fbe3a9f2d4d04d8",
11
+ "sha256": "29947b82499d5e2db4048530bf203f52546aef31625c59e017d2c2666b6ee5d5",
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": "a5ce863685465d6ef9a20ad98758c2d14f4306f5e60dfb013f6dc8388e8405b4",
18
+ "sha256": "855a97c0028f1dbd208d7ead972b6ffbc195a945b22e14ba356fdc7365e730b5",
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.22",
3
+ "version": "1.63.24",
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.
@@ -83,6 +84,7 @@ const ADMISSION_REPLAYABLE = new Set(["ADMISSION_UNCERTAIN", "RETRY_SAME_REQUEST
83
84
  // The key already has a ledger row (or had one): never replay it blindly or treat it as unallocated.
84
85
  const ADMISSION_COMMITTED_UNCERTAIN = new Set(["REQUEST_IDENTITY_MISMATCH", "REQUEST_CLOSED", "ORIGINAL_LEASE_UNAVAILABLE"]);
85
86
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
87
+ const SQLSTATE_RE = /^[0-9A-Z]{5}$/;
86
88
 
87
89
  /**
88
90
  * Codex #1077 P1 / BOT-2184: an sshd with `GatewayPorts yes` binds a `-R` forward on every
@@ -276,16 +278,20 @@ ${bold("run OPTIONS")}
276
278
  is requested through the identified admission (exact replay after a lost
277
279
  response, no fallback).
278
280
  --callback-port N The caller runs a listener the stack calls back through
279
- host.docker.internal:N (e.g. 4600): sent as callback_port. On a stable or
280
- auto REMOTE lease run first probes the host with a throwaway reverse forward
281
- and opens -R 127.0.0.1:N:127.0.0.1:N only when the host keeps it on loopback
282
- (sshd GatewayPorts no/clientspecified); otherwise exit 5
283
- CALLBACK_NOT_LOOPBACK (BOT-2184). With --remote --execution-ring beta it
284
- instead requires a Linux Go runtime with native callbacks (protocol 5): a
285
- private SSH Unix-socket route to a fixed requester gateway, renewed against
286
- current owned authorization (BOT-2775). Unknown/older hosts or a backend
287
- that does not acknowledge it fail HOST_CALLBACK_TRANSPORT_UNSUPPORTED; the
288
- 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).
289
295
  --bundle-max-mb N Remote-lease bundle size cap in MB (default 50, compressed). The bundle
290
296
  excludes .bb-stack.json, node_modules, supabase/.temp|.branches and
291
297
  untracked-secret dotenv files (.env, .env.local, .env.*.local).
@@ -330,9 +336,11 @@ ${bold("IDLE LEASES")} (BOT-1803)
330
336
 
331
337
  ${bold("REMOTE LEASES")} (BOT-2142, stack run)
332
338
  The receipt carries execution_mode (local|remote), execution_host_key, requester_host,
333
- 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).
334
341
  The child also gets BOTBUDDY_STACK_EXECUTION_MODE and BOTBUDDY_STACK_TRANSPORT.
335
- 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);
336
344
  every minute the control plane re-offers it to this owner's other hosts and it may move there
337
345
  (placement_reason moved_remote_on_requeue), and run then forwards to the new host. While queued,
338
346
  run prints one \`considered: <host> — <verdict>\` line per host weighed (e.g. "census stale 131 s");
@@ -346,11 +354,13 @@ ${bold("REMOTE LEASES")} (BOT-2142, stack run)
346
354
  4 STACK_EXECUTION_MODE_MISMATCH the slot already holds a lease in another mode/host — stack done it first
347
355
  (the four codes above create NO lease)
348
356
  5 BUNDLE_UPLOAD_FAILED the signed bundle upload failed (the lease is released first)
349
- 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)
350
358
  or the post-forward check found the host's -R listener not loopback-only
351
359
  (sshd GatewayPorts yes), or could not check it (no lsof, no listener)
352
- 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
353
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)
354
364
  5 CALLBACK_NOT_READY / CALLBACK_CONSUMING_NAMESPACE_UNREACHABLE the native route did not
355
365
  answer from the stack's own network before the child (lease reaped)
356
366
  6 CALLBACK_TRANSPORT_LOST / CALLBACK_AUTHORIZATION_LOST the native route or its current
@@ -1039,7 +1049,9 @@ export async function resolveRunExecution(cwd, opts, { materialize = materialize
1039
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.
1040
1050
  await patchRequesterAuthOrigin(readFileSync(join(worktreeRoot, "supabase", "config.toml"), "utf8"), requesterOrigin.origin);
1041
1051
  } catch (error) {
1042
- 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;
1043
1055
  warn("stack: supabase/config.toml sets its own [auth] external_url, so this run keeps the legacy (non-requester-origin) path");
1044
1056
  await requesterOrigin.close();
1045
1057
  requesterOrigin = null;
@@ -1364,6 +1376,11 @@ export function leaseRefusalError(data) {
1364
1376
  `Inspect with \`bb stack status ${data.lease_id}\`; after the owning batch ends, release with \`bb stack done ${data.lease_id} --reap\`, ` +
1365
1377
  `then wait with \`bb wait 'lease:id=${data.lease_id},state=reaped' --heartbeat\`.`;
1366
1378
  }
1379
+ // BOT-2812: a server fault, not the caller's: the database rolled the admission back.
1380
+ if (data?.code === "ADMISSION_FAILED") {
1381
+ return `the server's identified admission failed in the database${data.sqlstate ? ` (SQLSTATE ${data.sqlstate})` : ""} and rolled back, ` +
1382
+ "so no lease was created and this stack's port claim is released. This is a BotBuddy server fault: report it with this run's admission request id.";
1383
+ }
1367
1384
  return data?.message || REMOTE_REFUSAL_GUIDANCE[data?.code] || data?.code || "lease request refused";
1368
1385
  }
1369
1386
 
@@ -4122,8 +4139,14 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
4122
4139
  "an idle stack on this slot will be re-provisioned rather than reused.");
4123
4140
  }
4124
4141
  idleLease.stackDigest = stackDigest;
4125
- // 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.
4126
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;
4127
4150
  const leaseArgs = {
4128
4151
  slot, host_key: opts.host || undefined, repo: opts.repo, ticket_id: opts.ticket,
4129
4152
  ticket_url: opts.ticketUrl || undefined, pr_id: opts.prId || undefined, pr_url: opts.prUrl || undefined,
@@ -4138,7 +4161,8 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
4138
4161
  require_stack_digest: true,
4139
4162
  // BOT-2269: what the bundle carries beyond the defaults (paths, sha256, key NAMES).
4140
4163
  ...(bundle?.included?.length ? { bundle_included: bundle.included } : {}),
4141
- ...(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" } : {}) } : {}),
4142
4166
  };
4143
4167
  // BOT-2780: the identified admission key and a digest of the immutable origin context.
4144
4168
  // The intent (including the bundle digest that names the reserved origin) is fingerprinted
@@ -4207,6 +4231,8 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
4207
4231
  // for its reap and send this same request again; it is never adopted or released.
4208
4232
  else return { ...res, data: { success: false, code, ...(res.data?.message ? { message: res.data.message } : {}),
4209
4233
  ...(res.data?.phase ? { phase: res.data.phase } : {}), ...(res.data?.wait ? { wait: res.data.wait } : {}),
4234
+ // BOT-2812: the database ran the call and rolled it back; its SQLSTATE names the class.
4235
+ ...(code === "ADMISSION_FAILED" && SQLSTATE_RE.test(res.data?.sqlstate ?? "") ? { sqlstate: res.data.sqlstate } : {}),
4210
4236
  ...(code === HOST_CAPACITY_SOURCE_STALE_CODE ? capacityStaleReceiptFields(res.data) : {}) } };
4211
4237
  } else {
4212
4238
  const ack = identifiedAdmissionAck(res.data, leaseArgs.request_id);
@@ -4450,6 +4476,7 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
4450
4476
  return {
4451
4477
  exitCode: leaseRefusalExit(request.data?.code), outcome: "error", code: request.data?.code,
4452
4478
  ...capacityStaleReceiptFields(request.data), error: leaseRefusalError(request.data),
4479
+ ...(request.data?.sqlstate ? { sqlstate: request.data.sqlstate } : {}),
4453
4480
  ...(request.data.lease_id ? { leaseId: request.data.lease_id, state: request.data.state ?? null } : {}),
4454
4481
  };
4455
4482
  }
@@ -4499,7 +4526,7 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
4499
4526
  placement.mode = request.data.execution_mode ?? null;
4500
4527
  placement.hostKey = request.data.host_key ?? null;
4501
4528
  placement.requesterHost = request.data.requester_host ?? null;
4502
- if (remoteLease && requireNativeCallback && request.data.callback_transport !== NATIVE_CALLBACK_TRANSPORT) {
4529
+ if (remoteLease && (requireNativeCallback || autoCallbackPolicy) && request.data.callback_transport !== NATIVE_CALLBACK_TRANSPORT) {
4503
4530
  const cleanup = await requestCleanup();
4504
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 };
4505
4532
  }
@@ -4770,17 +4797,27 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
4770
4797
  stderrLine(`stack: lease ${leaseId} moved to ${placement.hostKey ?? "another host"} while queued (${placement.placementReason ?? "moved"}) — forwarding to it`);
4771
4798
  }
4772
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
+ }
4773
4810
  if (servedRemote) {
4774
4811
  // BOT-2142: forward every stack port over ONE ssh and hand the child the local ports.
4775
4812
  sshTarget = current.data.connection?.ssh_target ?? current.data.ssh_target ?? sshTarget;
4776
4813
  try {
4777
- tunnel = await openTunnel({ connection: current.data.connection, sshTarget, hostKey: placement.hostKey, callbackPort: requireNativeCallback ? null : opts.callbackPort,
4778
- ...(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] } : {}) });
4779
4816
  } catch (error) {
4780
4817
  const cleanup = await requestCleanup();
4781
4818
  return { exitCode: EXIT.BACKEND, outcome: "error", code: error?.code ?? "REMOTE_UNREACHABLE", leaseId, error: String(error?.message ?? error), cleanup };
4782
4819
  }
4783
- if (requireNativeCallback) {
4820
+ if (useNativeCallback) {
4784
4821
  try {
4785
4822
  nativeCallback = await openCallback({ current: current.data, leaseId, hostKey: placement.hostKey, callbackPort: opts.callbackPort, requesterMachineId: machineUuidFn(), sshTarget,
4786
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) {
@@ -4793,6 +4830,7 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
4793
4830
  return observed.data; },
4794
4831
  });
4795
4832
  nativeCallback.onExit((exit) => { if (childResult) return; callbackLost = exit; fencing = true; stopChild("SIGTERM", EXIT.LEASE_FAILED); });
4833
+ placement.callbackTransport = NATIVE_CALLBACK_TRANSPORT;
4796
4834
  } catch (error) {
4797
4835
  await closeTunnel(); const cleanup = await requestCleanup();
4798
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 };
@@ -5052,6 +5090,8 @@ export function runReceiptFields(result) {
5052
5090
  execution_host_key: result.execution?.hostKey ?? null,
5053
5091
  requester_host: result.execution?.requesterHost ?? null,
5054
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 } : {}),
5055
5095
  // BOT-2780: the reserved requester origin generated auth links name (never a credential).
5056
5096
  ...(result.execution?.requesterOrigin ? { requester_origin: result.execution.requesterOrigin } : {}),
5057
5097
  // BOT-2788: "previous" (an idle stack's origin re-reserved), "previous_port_busy" or "new".