@botbuddy/cli 1.59.1 → 1.59.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.59.1",
3
+ "version": "1.59.3",
4
4
  "description": "BotBuddy \u2014 Swarm coordination CLI for multi-agent workflows",
5
5
  "type": "module",
6
6
  "bin": {
package/src/wait-core.mjs CHANGED
@@ -676,9 +676,12 @@ export function parseSseFrames(buffer) {
676
676
  }
677
677
 
678
678
  // ---------------------------------------------------------------------------
679
- // Cursor — the spine `seq` is a non-negative integer; reject garbage loudly.
679
+ // Cursor — replay uses JavaScript Numbers at the relay boundary, so only exact
680
+ // non-negative safe integers can be replayed without changing the requested floor.
680
681
  // ---------------------------------------------------------------------------
681
682
 
683
+ const MAX_SAFE_SIGNAL_SEQUENCE = BigInt(Number.MAX_SAFE_INTEGER);
684
+
682
685
  /**
683
686
  * @param {string|null|undefined} raw
684
687
  * @returns {{kind: "absent"} | {kind: "ok", value: string} | {kind: "invalid", raw: string}}
@@ -688,7 +691,9 @@ export function normalizeSince(raw) {
688
691
  const t = String(raw).trim();
689
692
  if (t === "") return { kind: "absent" };
690
693
  if (!/^\d+$/.test(t)) return { kind: "invalid", raw };
691
- return { kind: "ok", value: String(BigInt(t)) };
694
+ const value = BigInt(t);
695
+ if (value > MAX_SAFE_SIGNAL_SEQUENCE) return { kind: "invalid", raw };
696
+ return { kind: "ok", value: String(value) };
692
697
  }
693
698
 
694
699
  // ---------------------------------------------------------------------------
package/src/wait.mjs CHANGED
@@ -153,7 +153,7 @@ CONDITIONS (TYPE:key=val,key=val — repeat for several; --any wakes on the fir
153
153
  OPTIONS
154
154
  --any wake on the first matching condition (default; only mode in v1)
155
155
  --timeout <seconds> max wait (default 7200; hard cap 28800). Timeout is exit 2, not an error.
156
- --since <seq> resume from a prior receipt's next_cursor (replay what you missed)
156
+ --since <seq> resume from a prior receipt's next_cursor (0..9007199254740991; replay what you missed)
157
157
  --receipt-max-bytes <n> cap the receipt (512..65536; default 10240; payloads truncate to pointers)
158
158
  --heartbeat keep this agent session alive while waiting (so it is not reaped)
159
159
  --url <base> relay base URL (default $BOTBUDDY_RELAY_URL or https://api.bot-buddy.ai/functions/v1)
@@ -216,6 +216,7 @@ function parseArgv(argv) {
216
216
  mode: "any",
217
217
  timeout: 7200,
218
218
  since: null,
219
+ sinceProvided: false,
219
220
  receiptMaxBytes: 10240,
220
221
  heartbeat: false,
221
222
  url: process.env.BOTBUDDY_RELAY_URL || "https://api.bot-buddy.ai/functions/v1",
@@ -248,7 +249,7 @@ function parseArgv(argv) {
248
249
  else if (a === "--heartbeat") opts.heartbeat = true;
249
250
  else if (a === "--timeout") opts.timeout = Number(optionValue());
250
251
  else if (a === "--deadline-at") opts.deadlineAt = optionValue(); // internal recovery argv; never shown as public API
251
- else if (a === "--since") opts.since = optionValue();
252
+ else if (a === "--since") { opts.sinceProvided = true; opts.since = optionValue(); }
252
253
  else if (a === "--receipt-max-bytes") opts.receiptMaxBytes = Number(optionValue());
253
254
  else if (a === "--url") opts.url = optionValue();
254
255
  // BOT-1574: --token and --profile are RETIRED. A wait authenticates from
@@ -1572,6 +1573,19 @@ export async function runWait(argv, { recoveryLocalWaitId = null } = {}) {
1572
1573
  process.exit(EXIT.INVALID);
1573
1574
  }
1574
1575
 
1576
+ // BOT-2453: an explicit replay cursor is a request to replay, never permission
1577
+ // to silently fall back to live-only. Validate it before creating a recoverable
1578
+ // checkpoint, resolving credentials, registering a wait, or opening SSE.
1579
+ if (opts.sinceProvided) {
1580
+ const since = normalizeSince(opts.since);
1581
+ if (since.kind !== "ok") {
1582
+ process.stderr.write("bb-wait: --since must be a non-negative signal sequence <= 9007199254740991\n");
1583
+ emitReceipt({ schema_version: 1, outcome: "error", error: "invalid_since", cursor: opts.since });
1584
+ process.exit(EXIT.INVALID);
1585
+ }
1586
+ opts.since = since.value;
1587
+ }
1588
+
1575
1589
  const { conditions, errors } = parseConditions(opts.conditions);
1576
1590
  if (errors.length > 0) {
1577
1591
  for (const e of errors) process.stderr.write(`bb-wait: invalid condition '${e.spec}': ${e.message}\n`);