@botbuddy/cli 1.45.0 → 1.45.2

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.0",
3
+ "version": "1.45.2",
4
4
  "description": "BotBuddy — Swarm coordination CLI for multi-agent workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1 @@
1
+ {"schema_version":1,"source_version":"1.45.1","source_identity":"2fa051c4d0ee5cb09fda8d9e15c4952af94d701a6a43e98c8831912040cb41f2"}
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
  }
package/src/wait-core.mjs CHANGED
@@ -436,10 +436,13 @@ function prSubjectMatches(subjectKey, params) {
436
436
  return subjectKey.startsWith(`${params.repo}#`);
437
437
  }
438
438
 
439
- // Does a commit field on a signal payload start with the condition's sha prefix?
440
- // A missing / non-string field never matches a sha-scoped condition.
439
+ // Does a commit field on a signal payload identify the same commit as the
440
+ // condition's SHA? GitHub may send either side as a 7–40-character prefix, so
441
+ // accept either prefix direction while refusing malformed payload values.
441
442
  function shaPrefixMatches(value, sha) {
442
- return typeof value === "string" && value.trim().toLowerCase().startsWith(sha);
443
+ if (typeof value !== "string") return false;
444
+ const candidate = value.trim().toLowerCase();
445
+ return /^[0-9a-f]{7,40}$/.test(candidate) && (candidate.startsWith(sha) || sha.startsWith(candidate));
443
446
  }
444
447
 
445
448
  // BOT-1975: optional head scoping for pr-state. pr_state signals carry the PR
package/src/wait.mjs CHANGED
@@ -78,8 +78,8 @@ CONDITIONS (TYPE:key=val,key=val — repeat for several; --any wakes on the fir
78
78
  by the PR read model. Omit pr= to wake on ANY of your PRs
79
79
  in the repo; or pass pr=<owner/repo#n> in one value. A
80
80
  conflict/clean flip sets payload.mergeable_changed. The
81
- payload carries head_ref_oid; sha=<head> (7-40 hex, a
82
- prefix is fine) wakes only on signals for that head, so a
81
+ payload carries head_ref_oid; sha=<head> (7-40 hex; either
82
+ side may be an abbreviated prefix) wakes only on signals for that head, so a
83
83
  previous head's check result can't wake you. A push alone
84
84
  emits nothing — the head rides on the next transition.
85
85
  pr-review:repo=<owner/repo>[,pr=<n>][,reviewer=<login>][,sha=<commit>]
@@ -96,7 +96,7 @@ CONDITIONS (TYPE:key=val,key=val — repeat for several; --any wakes on the fir
96
96
  existing review will not re-wake it.
97
97
  reviewer=<login> wakes only on that reviewer (case-
98
98
  insensitive; a trailing [bot] is ignored). sha=<commit>
99
- wakes only on verdicts for that commit (submitted reviews
99
+ wakes only on verdicts for that commit (either side may be an abbreviated prefix; submitted reviews
100
100
  and clean passes carry commit_sha; thread comments do not,
101
101
  so they never match sha=); the usage-limit decline always
102
102
  passes sha=. Any other key is refused (exit 4).