@botbuddy/cli 1.36.0 → 1.36.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.36.0",
3
+ "version": "1.36.2",
4
4
  "description": "BotBuddy — Swarm coordination CLI for multi-agent workflows",
5
5
  "type": "module",
6
6
  "bin": {
Binary file
@@ -43,10 +43,10 @@ export async function saveHarnessSession(session, { cwd = process.cwd() } = {})
43
43
  worktree: root,
44
44
  minted_at: new Date().toISOString(),
45
45
  expires_at: session.expires_at,
46
- // BOT-1650 (Codex round-28 P2): mark provenance so a TIMER wait only relays for
47
- // a bootstrap-issued cache (the cross-shell receipt-of-principal contract). A
48
- // bb setup / self-heal cache shares this schema but omits this marker, so a
49
- // plain timer from such a shell stays fully offline.
46
+ // BOT-1650 (Codex round-28 P2): mark bootstrap provenance. Since BOT-2000 a
47
+ // TIMER wait relays for ANY runtime cache (bootstrap or bb setup / self-heal),
48
+ // so this marker no longer gates timer relaying; it still distinguishes a
49
+ // bootstrap-issued cache for the bootstrap bridge's own reuse checks.
50
50
  harness_origin: "bootstrap_agent_session",
51
51
  ...(session.session_token_generation ? { session_token_generation: session.session_token_generation } : {}),
52
52
  };
package/src/stack.mjs CHANGED
@@ -1587,19 +1587,24 @@ export async function cmdUp(opts, {
1587
1587
  }), opts, EXIT.OK);
1588
1588
  }
1589
1589
 
1590
- async function cmdStatus(leaseId, opts, { rpc = retryingStackCall() } = {}) {
1590
+ export async function cmdStatus(leaseId, opts, { rpc = retryingStackCall(), emitResult = emit } = {}) {
1591
1591
  const auth = await rpc.init();
1592
- if (!auth) return emit(buildReceipt({ command: "status", outcome: "error", lease_id: leaseId, error: "not authenticated — run `botbuddy login`" }), opts, EXIT.AUTH);
1592
+ if (!auth) return emitResult(buildReceipt({ command: "status", outcome: "error", lease_id: leaseId, error: "not authenticated — run `botbuddy login`" }), opts, EXIT.AUTH);
1593
1593
  const got = await rpc.call("get_stack_lease", { lease_id: leaseId });
1594
1594
  if (!got.ok) return got.auth
1595
- ? emit(buildReceipt({ command: "status", outcome: "error", lease_id: leaseId, error: got.error }), opts, EXIT.AUTH)
1596
- : emit(buildReceipt({ command: "status", outcome: "error", lease_id: leaseId, error: got.error }), opts, EXIT.BACKEND);
1595
+ ? emitResult(buildReceipt({ command: "status", outcome: "error", lease_id: leaseId, error: got.error }), opts, EXIT.AUTH)
1596
+ : emitResult(buildReceipt({ command: "status", outcome: "error", lease_id: leaseId, error: got.error }), opts, EXIT.BACKEND);
1597
1597
  const g = got.data;
1598
- if (!g.success) return emit(buildReceipt({ command: "status", outcome: "error", lease_id: leaseId, code: g.code, error: g.code }), opts, EXIT.BACKEND);
1599
- return emit(buildReceipt({
1598
+ if (!g.success) return emitResult(buildReceipt({ command: "status", outcome: "error", lease_id: leaseId, code: g.code, error: g.code }), opts, EXIT.BACKEND);
1599
+ return emitResult(buildReceipt({
1600
1600
  command: "status", outcome: g.state, lease_id: leaseId, state: g.state,
1601
1601
  host_key: g.host_key, slot: g.slot, connection: g.connection,
1602
1602
  queue_position: g.queue_position, idle_ttl_secs: g.idle_ttl_secs, queued: g.queued,
1603
+ // BOT-1960: a lease fenced in `reaping` says since when, and — once the server gave up
1604
+ // retrying its failed reap — that it is reap_failed and why.
1605
+ ...(g.reap_requested_at ? { reap_requested_at: g.reap_requested_at } : {}),
1606
+ ...(g.reap_failed_at ? { reap_failed: true, reap_failed_at: g.reap_failed_at } : {}),
1607
+ ...(g.state === "reaping" && g.error ? { error: g.error } : {}),
1603
1608
  }), opts, EXIT.OK);
1604
1609
  }
1605
1610
 
@@ -1803,7 +1808,27 @@ export async function cmdDone(leaseId, opts, {
1803
1808
  if (fin.ok && fin.data?.success) state = fin.data.state;
1804
1809
  else process.stderr.write(`${yellow("⚠")} stack: finalize failed (${fin.error || fin.data?.code}); the reaper will reconcile.\n`);
1805
1810
  }
1811
+ // BOT-1960: on a lease fenced in `reaping` by a FAILED reap, the server re-enqueues the
1812
+ // reap (the first time also resetting its retry budget) — or, once that budget is spent,
1813
+ // refuses. Say which, instead of a `released` that changed nothing.
1814
+ const retry = r.data.reap_retry;
1815
+ if (state === "reaping" && retry?.code === "REAP_RETRY_BUDGET_SPENT") {
1816
+ const since = r.data.reap_failed_at ? ` (since ${r.data.reap_failed_at})` : "";
1817
+ return emitResult(buildReceipt({ command: "done", outcome: "reap_failed", lease_id: leaseId, state, disposition,
1818
+ reap_failed_at: r.data.reap_failed_at ?? null, reap_retry: { enqueued: false, code: retry.code },
1819
+ error: `the reap failed on every retry${since}; no retries left — the lease stays reaping and its slot stays fenced. ` +
1820
+ "Remove the project's leftover containers (operator / `botbuddy docker hygiene`); the container census then finalizes the lease.",
1821
+ }), opts, EXIT.LEASE_FAILED);
1822
+ }
1823
+ const retried = retry?.code === "REAP_RETRY_ENQUEUED"
1824
+ ? {
1825
+ reap_retry: { enqueued: true, attempt: retry.attempt, budget_reset: retry.budget_reset === true },
1826
+ note: `the previous reap failed; a new reap job (attempt ${retry.attempt}) was enqueued` +
1827
+ (retry.budget_reset === true ? " and the reap retry budget was reset" : ""),
1828
+ }
1829
+ : {};
1806
1830
  return emitResult(buildReceipt({ command: "done", outcome: "released", lease_id: leaseId, state, disposition,
1831
+ ...retried,
1807
1832
  ...(helperTeardown ? { teardown: "helper" } : {}),
1808
1833
  ...(legacyTargetProof ? { legacy_target_proof: legacyTargetProof } : {}) }), opts, EXIT.OK);
1809
1834
  }
package/src/wait.mjs CHANGED
@@ -19,7 +19,7 @@ import { VERSION } from "./version.mjs";
19
19
  import { fileURLToPath } from "node:url";
20
20
  import { latestPublicCliCommand } from "./public-invocation.mjs";
21
21
  import { AGENT_KEY_RE, readAgentSessionTokenEnv, legacyAgentAliasHint } from "./agent-key.mjs";
22
- import { clearAgentState, hasBootstrapAgentSession, isRejectedCachedSession, resolveAgentSessionCredential, selfHealAgentSession } from "./agent-session.mjs";
22
+ import { clearAgentState, hasAgentSessionRuntime, isRejectedCachedSession, resolveAgentSessionCredential, selfHealAgentSession } from "./agent-session.mjs";
23
23
  import { touchAgentStateExpiry } from "./agent-state.mjs";
24
24
  import { SETUP_BLOCK } from "./setup-block.mjs";
25
25
  import { createWaitCheckpointStore, WaitCheckpointError } from "./wait-checkpoint.mjs";
@@ -1568,11 +1568,12 @@ export async function runWait(argv, { recoveryLocalWaitId = null } = {}) {
1568
1568
  : new WaitCheckpointError("wait_checkpoint_write_failed", String(error?.message ?? error)));
1569
1569
  }
1570
1570
 
1571
- // BOT-1650: a plain timer stays offline, but a timer from a bootstrapped harness
1572
- // registers so its terminal receipt proves the current session principal (the
1573
- // cross-shell bootstrap contract). Detected without a network self-heal.
1571
+ // BOT-1650 / BOT-2000: a timer from a worktree with any agent-session runtime
1572
+ // file (bootstrap or `bb setup`) registers, so it shows on /waits and its terminal
1573
+ // receipt proves the session principal. Only a credential-less worktree's timer
1574
+ // stays offline. Detected without a network self-heal.
1574
1575
  const needsRelay = conditions.some((c) => c.type !== "timer")
1575
- || await hasBootstrapAgentSession({ cwd: process.cwd() });
1576
+ || await hasAgentSessionRuntime({ cwd: process.cwd() });
1576
1577
  if (needsRelay) {
1577
1578
  try {
1578
1579
  const credential = await resolveAgentSessionCredential({ argv, env: process.env, cwd: process.cwd() });