@botbuddy/cli 1.45.1 → 1.45.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbuddy/cli",
3
- "version": "1.45.1",
3
+ "version": "1.45.3",
4
4
  "description": "BotBuddy — Swarm coordination CLI for multi-agent workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -77,6 +77,12 @@ export function repairFailureRecovery(code, error = null) {
77
77
  if (code === "client_key_unavailable") {
78
78
  return "the client key could not be read (locked or failing credential store); unlock it, then run `bb doctor --fix`";
79
79
  }
80
+ // BOT-1787: this is a server-side identity conflict, not an authentication
81
+ // failure. Preserve the real remedy so a healthy credential is never needlessly
82
+ // rotated; `bb setup` clears this worktree's cache and re-registers it.
83
+ if (code === "agent_tenant_immutable") {
84
+ return "this worktree's agent identity belongs to another tenant; run `bb setup` to register this tenant, then `bb doctor --fix`";
85
+ }
80
86
  // BOT-1750 (Codex R9 P2): `session_mint_unauthorized` means a locally-unexpired
81
87
  // client key was revoked/rejected server-side — only a fresh login replaces it, so
82
88
  // point at `bb login`, not a binding repair (the binding is valid).
Binary file
package/src/api.mjs CHANGED
@@ -445,7 +445,13 @@ export async function callToolJson(toolName, args = {}, { fetchImpl = fetch, sig
445
445
  }
446
446
  let payload;
447
447
  try { payload = await res.json(); } catch { return { ok: false, status: res.status, error: "invalid_json" }; }
448
- if (payload.error?.message) return { ok: false, status: res.status, error: payload.error.message };
448
+ if (payload.error?.message) {
449
+ // Typed domain errors retain their verdict inside the HTTP-200 JSON-RPC
450
+ // envelope. Do not infer credential failure from an identity conflict.
451
+ const code = payload.error.data?.code;
452
+ return { ok: false, status: res.status, error: payload.error.message,
453
+ ...(typeof code === "string" && code ? { code } : {}) };
454
+ }
449
455
  const text = payload.result?.content?.map((c) => c.text).filter(Boolean).join("\n") ?? "";
450
456
  let data;
451
457
  try { data = text ? JSON.parse(text) : {}; } catch { data = { raw: text }; }
package/src/stack.mjs CHANGED
@@ -320,7 +320,7 @@ export function parseStackArgs(argv) {
320
320
  case "--idle-ttl": {
321
321
  if (need(a, rest[i + 1])) {
322
322
  const n = Number(rest[++i]);
323
- if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) errors.push("--idle-ttl must be a positive integer number of seconds");
323
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 2) errors.push("--idle-ttl must be an integer of at least 2 seconds so the one-second heartbeat can keep the lease alive");
324
324
  else opts.idleTtl = n;
325
325
  }
326
326
  break;
@@ -1766,6 +1766,20 @@ function readStackIdentity(dir, read = readFileSync) {
1766
1766
  * is best-effort (unreadable config ⇒ treated as isolated, since the parse+provision
1767
1767
  * guards already blocked a same-project subdir at `up`). Never mutates.
1768
1768
  */
1769
+ // BOT-1935: every local-exec lease has persisted `connection.botbuddy_docker_target`
1770
+ // since CLI 1.5.0, so a TARGETLESS lease activated on/after this instant was provisioned
1771
+ // by the Helper, not by a pre-1.5.0 client (prod 069096a4, 2026-09-29). The server names
1772
+ // the Helper as `provisioner` only when it holds the Helper's succeeded receipt; this is
1773
+ // the diagnosis for the rest. It never authorizes anything: the lease stays fenced.
1774
+ export const HELPER_ERA_ACTIVATED_AT = Date.parse("2026-09-01T00:00:00Z");
1775
+
1776
+ export function targetlessHelperLeaseRefusal(lease, leaseId) {
1777
+ const activatedAt = Date.parse(lease?.activated_at ?? "");
1778
+ if (lease?.connection?.botbuddy_docker_target || !(activatedAt >= HELPER_ERA_ACTIVATED_AT)) return null;
1779
+ return `this lease was provisioned by the Helper; release it with \`bb stack done ${leaseId}\` (no --local-exec) ` +
1780
+ "from the owning session, or ask the operator; lease and slot remain fenced";
1781
+ }
1782
+
1769
1783
  export function localExecTeardownIsNonIsolated(lease, read = readFileSync) {
1770
1784
  const stackPath = lease?.stack_path || ".";
1771
1785
  if (stackPath === ".") return true;
@@ -2580,16 +2594,20 @@ export async function cmdDone(leaseId, opts, {
2580
2594
  // the compatibility contract still pins them there, so keep this path OrbStack-only:
2581
2595
  // a Docker Desktop daemon (newly admitted by the preflight allowlist for MODERN
2582
2596
  // leases) must never authorize teardown of a targetless legacy OrbStack lease.
2597
+ // BOT-1935: a targetless lease activated after the 1.5.0 era is not legacy — the
2598
+ // Helper provisioned it (local-exec has persisted a target since 1.5.0). Same fence,
2599
+ // same exit code; only the diagnosis names the release path that actually works.
2600
+ const helperLeaseRefusal = targetlessHelperLeaseRefusal(current.data, leaseId);
2583
2601
  if (dockerTarget?.validation !== "orbstack") {
2584
2602
  return emitResult(buildReceipt({ command: "done", outcome: "refused", lease_id: leaseId,
2585
- error: `pre-1.5.0 lease has no persisted Docker target; only an OrbStack daemon may authorize legacy teardown, not ${dockerTarget?.validation || "an unverified engine"}; lease and slot remain fenced`,
2603
+ error: helperLeaseRefusal ?? `pre-1.5.0 lease has no persisted Docker target; only an OrbStack daemon may authorize legacy teardown, not ${dockerTarget?.validation || "an unverified engine"}; lease and slot remain fenced`,
2586
2604
  expected_docker_target: null, observed_docker_target: dockerTarget,
2587
2605
  }), opts, EXIT.LEASE_FAILED);
2588
2606
  }
2589
2607
  const proof = proveLegacyTarget(current.data, dockerTarget, opts);
2590
2608
  if (!proof?.ok) {
2591
2609
  return emitResult(buildReceipt({ command: "done", outcome: "refused", lease_id: leaseId,
2592
- error: `pre-1.5.0 lease has no persisted Docker target and compatibility proof failed: ${proof?.error || "unknown proof failure"}; lease and slot remain fenced`,
2610
+ error: helperLeaseRefusal ?? `pre-1.5.0 lease has no persisted Docker target and compatibility proof failed: ${proof?.error || "unknown proof failure"}; lease and slot remain fenced`,
2593
2611
  expected_docker_target: null, observed_docker_target: dockerTarget,
2594
2612
  }), opts, EXIT.LEASE_FAILED);
2595
2613
  }