@botbuddy/cli 1.36.0 → 1.36.1

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/stack.mjs +31 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbuddy/cli",
3
- "version": "1.36.0",
3
+ "version": "1.36.1",
4
4
  "description": "BotBuddy — Swarm coordination CLI for multi-agent workflows",
5
5
  "type": "module",
6
6
  "bin": {
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
  }