@botbuddy/cli 1.39.0 → 1.39.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 +38 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbuddy/cli",
3
- "version": "1.39.0",
3
+ "version": "1.39.1",
4
4
  "description": "BotBuddy — Swarm coordination CLI for multi-agent workflows",
5
5
  "type": "module",
6
6
  "bin": {
package/src/stack.mjs CHANGED
@@ -331,6 +331,29 @@ export function parseStackArgs(argv) {
331
331
  return { command, leaseId, opts, errors, childArgv };
332
332
  }
333
333
 
334
+ /**
335
+ * BOT-2019: the human line for a queued lease's server position. `queue_position` is the
336
+ * server's dispatch rank (stack_lease_dispatch_queue — the order leases are considered in,
337
+ * not a guaranteed start order); a held / owner-offline lease has none and the server says
338
+ * why in `queue_position_reason`. `scored_ticket_id` is the ticket the lease is scored under
339
+ * (its own, or the requesting agent's when it has none). Older servers send neither field.
340
+ */
341
+ export function describeQueuePosition(d) {
342
+ const reason = d?.queue_position_reason ? ` (${d.queue_position_reason})` : "";
343
+ const pos = d?.queue_position != null ? `queue position ${d.queue_position}` : `position: none${reason}`;
344
+ const where = [pos, d?.host_key ? `on ${d.host_key}` : null].filter(Boolean).join(" ");
345
+ return d?.scored_ticket_id ? `${where}, scored under ${d.scored_ticket_id}` : where;
346
+ }
347
+
348
+ /** BOT-2019: the queue fields a server returned for a queued lease, for a receipt. */
349
+ function queueReceiptFields(d) {
350
+ return {
351
+ queue_position: d?.queue_position ?? null,
352
+ ...(d?.queue_position_reason !== undefined ? { queue_position_reason: d.queue_position_reason } : {}),
353
+ ...(d?.scored_ticket_id !== undefined ? { scored_ticket_id: d.scored_ticket_id } : {}),
354
+ };
355
+ }
356
+
334
357
  /** Build the canonical receipt object (single JSON line to stdout). */
335
358
  export function buildReceipt(fields) {
336
359
  return { schema_version: STACK_SCHEMA_VERSION, ...fields };
@@ -1649,10 +1672,12 @@ export async function cmdUp(opts, {
1649
1672
 
1650
1673
  if (state === "queued") {
1651
1674
  // BOT-1319: a wrapped lefthook lane records this park as waiting for capacity.
1652
- const where = [d.queue_position != null ? `queue position ${d.queue_position}` : null, d.host_key ? `on ${d.host_key}` : null].filter(Boolean).join(" ");
1675
+ // BOT-2019: the server's dispatch rank, or `position: none (<reason>)`, never a crash on null.
1676
+ const where = describeQueuePosition(d);
1653
1677
  reportLaneStatus({ phase: "waiting", reason: "queued_for_capacity", detail: where || "host at capacity" });
1678
+ process.stderr.write(`stack: lease ${leaseId} queued — ${where}\n`);
1654
1679
  if (opts.noWait) {
1655
- return emitResult(buildReceipt({ command: "up", outcome: "queued", lease_id: leaseId, state, host_key: d.host_key, slot, queued: true, queue_position: d.queue_position, holders: d.holders }), opts, EXIT.QUEUED);
1680
+ return emitResult(buildReceipt({ command: "up", outcome: "queued", lease_id: leaseId, state, host_key: d.host_key, slot, queued: true, ...queueReceiptFields(d), holders: d.holders }), opts, EXIT.QUEUED);
1656
1681
  }
1657
1682
  // Park zero-poll until the lease leaves the queue (granted in BOT-1187 order).
1658
1683
  // BOT-2013: a lease reaped while queued (orphaned_agent, …) is lost, not "left the queue".
@@ -1837,7 +1862,9 @@ export async function cmdUp(opts, {
1837
1862
  }), opts, EXIT.OK);
1838
1863
  }
1839
1864
 
1840
- export async function cmdStatus(leaseId, opts, { rpc = retryingStackCall(), emitResult = emit } = {}) {
1865
+ export async function cmdStatus(leaseId, opts, {
1866
+ rpc = retryingStackCall(), emitResult = emit, stderr = (line) => process.stderr.write(`${line}\n`),
1867
+ } = {}) {
1841
1868
  const auth = await rpc.init();
1842
1869
  if (!auth) return emitResult(buildReceipt({ command: "status", outcome: "error", lease_id: leaseId, error: "not authenticated — run `botbuddy login`" }), opts, EXIT.AUTH);
1843
1870
  const got = await rpc.call("get_stack_lease", { lease_id: leaseId });
@@ -1846,10 +1873,15 @@ export async function cmdStatus(leaseId, opts, { rpc = retryingStackCall(), emit
1846
1873
  : emitResult(buildReceipt({ command: "status", outcome: "error", lease_id: leaseId, error: got.error }), opts, EXIT.BACKEND);
1847
1874
  const g = got.data;
1848
1875
  if (!g.success) return emitResult(buildReceipt({ command: "status", outcome: "error", lease_id: leaseId, code: g.code, error: g.code }), opts, EXIT.BACKEND);
1876
+ // BOT-2019: a queued lease names its server dispatch rank (or why it has none) and the
1877
+ // ticket it is scored under; a null position is reported, never a crash.
1878
+ const queued = g.state === "queued";
1879
+ if (queued) stderr(`stack: lease ${leaseId} queued — ${describeQueuePosition(g)}`);
1849
1880
  return emitResult(buildReceipt({
1850
1881
  command: "status", outcome: g.state, lease_id: leaseId, state: g.state,
1851
1882
  host_key: g.host_key, slot: g.slot, connection: g.connection,
1852
- queue_position: g.queue_position, idle_ttl_secs: g.idle_ttl_secs, queued: g.queued,
1883
+ ...(queued ? queueReceiptFields(g) : { queue_position: g.queue_position }),
1884
+ idle_ttl_secs: g.idle_ttl_secs, queued: g.queued,
1853
1885
  // BOT-1960: a lease fenced in `reaping` says since when, and — once the server gave up
1854
1886
  // retrying its failed reap — that it is reap_failed and why.
1855
1887
  ...(g.reap_requested_at ? { reap_requested_at: g.reap_requested_at } : {}),
@@ -2447,6 +2479,8 @@ export async function runStackLifecycle(opts, childArgv, adapters = {}) {
2447
2479
  return { exitCode: SIGNAL_EXIT[receivedSignal], outcome: "interrupted", leaseId, cleanup };
2448
2480
  }
2449
2481
  if (state === "queued") {
2482
+ // BOT-2019: say where the lease sits (server dispatch rank, or why it has none) before parking.
2483
+ stderrLine(`stack: lease ${leaseId} queued — ${describeQueuePosition(request.data)}`);
2450
2484
  activeWaitAbort = new AbortController();
2451
2485
  // BOT-2013: every reap state (reaping / reaped / reap_failed) means the queued lease is lost.
2452
2486
  const parked = await wait(leaseId, (s) => s !== "queued" && s != null, isReaped, { timeoutSec: opts.timeout, auth: rpc.auth, signal: activeWaitAbort.signal });