@botbuddy/cli 1.63.39 → 1.63.41

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.
@@ -1,7 +1,7 @@
1
1
  {
2
- "release_version": "1.63.39",
3
- "source_commit": "d98a90edc6157b66510ab7b5ca58d210cbfc8e3a",
4
- "source_sha256": "7594e80018bcbd9014684e4d916376bdc0c8388ed4ccec4afaf52fde3a40c659",
2
+ "release_version": "1.63.41",
3
+ "source_commit": "89653c3c1d5a7144abd8bdab27a9496900cdb1d0",
4
+ "source_sha256": "d524b98618977c153c57ae009f9cbab3da72f0255fb9a0b01e840a7c78550dc3",
5
5
  "target": "darwin-arm64",
6
- "sha256": "98e622a171da78743c8311ddb4aef4565c2a5e836c143089ab799b23dea01fad"
6
+ "sha256": "ffee8ba3b0a0b849075f06f5186ca2aa8f14fca124c1460a396f410b95a655d7"
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
- "release_version": "1.63.39",
3
- "source_commit": "d98a90edc6157b66510ab7b5ca58d210cbfc8e3a",
4
- "source_sha256": "7594e80018bcbd9014684e4d916376bdc0c8388ed4ccec4afaf52fde3a40c659",
2
+ "release_version": "1.63.41",
3
+ "source_commit": "89653c3c1d5a7144abd8bdab27a9496900cdb1d0",
4
+ "source_sha256": "d524b98618977c153c57ae009f9cbab3da72f0255fb9a0b01e840a7c78550dc3",
5
5
  "target": "linux-x64",
6
- "sha256": "9fd7c1b2587f0b0abce54f923f87e0229405a5c951cac580ecaefc5f16e384cd"
6
+ "sha256": "594d96944fbea3354a6d26186d92e989470ee75734fd7fd53f5eb681eab1f43d"
7
7
  }
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "schema_version": 1,
3
- "release_version": "1.63.39",
4
- "source_commit": "d98a90edc6157b66510ab7b5ca58d210cbfc8e3a",
5
- "source_sha256": "7594e80018bcbd9014684e4d916376bdc0c8388ed4ccec4afaf52fde3a40c659",
3
+ "release_version": "1.63.41",
4
+ "source_commit": "89653c3c1d5a7144abd8bdab27a9496900cdb1d0",
5
+ "source_sha256": "d524b98618977c153c57ae009f9cbab3da72f0255fb9a0b01e840a7c78550dc3",
6
6
  "targets": {
7
7
  "darwin-arm64": {
8
8
  "goos": "darwin",
9
9
  "goarch": "arm64",
10
10
  "binary": "daemon/bin/darwin-arm64/botbuddy-daemon",
11
- "sha256": "98e622a171da78743c8311ddb4aef4565c2a5e836c143089ab799b23dea01fad",
11
+ "sha256": "ffee8ba3b0a0b849075f06f5186ca2aa8f14fca124c1460a396f410b95a655d7",
12
12
  "mode": "0755"
13
13
  },
14
14
  "linux-x64": {
15
15
  "goos": "linux",
16
16
  "goarch": "amd64",
17
17
  "binary": "daemon/bin/linux-x64/botbuddy-daemon",
18
- "sha256": "9fd7c1b2587f0b0abce54f923f87e0229405a5c951cac580ecaefc5f16e384cd",
18
+ "sha256": "594d96944fbea3354a6d26186d92e989470ee75734fd7fd53f5eb681eab1f43d",
19
19
  "mode": "0755"
20
20
  }
21
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbuddy/cli",
3
- "version": "1.63.39",
3
+ "version": "1.63.41",
4
4
  "description": "BotBuddy \u2014 Swarm coordination CLI for multi-agent workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -295,6 +295,8 @@ export async function runStagingAuth({ plan, host, env, deps, socketRun, resolve
295
295
  }
296
296
  const found = { binding: ctxPool.poolBinding };
297
297
  Object.assign(vars, { allowed: Object.keys(found.binding.personas ?? {}), tenant: ctxPool.tenant ?? undefined, poolId: ctxPool.poolId ?? undefined });
298
+ // BOT-2836: a retry addresses the same pool when one was named (several installed).
299
+ if (plan.pool) vars.retry = `${authLine} --pool ${ctxPool.poolId}`;
298
300
  const { findPersona } = await loadBinding();
299
301
  const persona = findPersona(found.binding, plan.persona);
300
302
  if (!persona) return refuse(io, "persona_not_allowlisted", 2, {}, vars);
@@ -317,7 +319,10 @@ export async function runStagingAuth({ plan, host, env, deps, socketRun, resolve
317
319
  // The refusal's code is the pool's health reason when it is unhealthy (e.g. a
318
320
  // count-0 persona is persona_not_configured), so code, message and help agree.
319
321
  const reasonCode = typeof error.reason === "string" && error.reason !== "healthy" && isKnownCode(error.reason) ? error.reason : "pool_empty";
320
- return refuse(io, reasonCode, 3, Object.fromEntries(["reason", "detail", ...GUIDED_FIELDS].filter((key) => error[key] !== undefined).map((key) => [key, error[key]])), vars);
322
+ // BOT-2836: a healthy empty pool is the agent's own wait-and-retry, so it is
323
+ // rendered here with this lane; an unhealthy pool keeps the pool's health guidance.
324
+ const forwarded = reasonCode === "pool_empty" ? ["reason", "detail"] : ["reason", "detail", ...GUIDED_FIELDS];
325
+ return refuse(io, reasonCode, 3, Object.fromEntries(forwarded.filter((key) => error[key] !== undefined).map((key) => [key, error[key]])), vars);
321
326
  }
322
327
  const code = typeof error?.code === "string" && isKnownCode(error.code) ? error.code : "pool_take_failed";
323
328
  return refuse(io, code, 3, { detail: String(error?.message ?? error).slice(0, 300) }, vars);
@@ -236,6 +236,16 @@ export function resolveInstalledPool({ home = homedir(), poolId = null, tenantHi
236
236
  throw Object.assign(new ConfigError(`Several staging session pools are installed: ${installed.map((pool) => pool.poolId).join(", ")}.`, "pool_ambiguous"), { poolIds: installed.map((pool) => pool.poolId) });
237
237
  }
238
238
 
239
+ // BOT-2836: after a take, ask the installed launchd job to run its tick now, so the
240
+ // pool refills without waiting for the interval or a human `tick`. The job signs in
241
+ // (with its own backoff); the agent never does. No -k: a running tick is never killed.
242
+ // Best effort: an unloaded job surfaces through health, and never fails the take.
243
+ async function requestRefill({ ctx, exec, uid }) {
244
+ const label = ctx.poolBinding?.launchdLabel;
245
+ if (!label) return;
246
+ await createLaunchctl({ exec, uid }).kickstart(label).catch(() => {});
247
+ }
248
+
239
249
  /**
240
250
  * Agent path, used by `bb pw … auth`: claim one verified session for a persona
241
251
  * (alias or email) of the installed pool. Returns { taken, target, binding,
@@ -263,9 +273,11 @@ export async function takeForLane({
263
273
  const pool = buildPool({ ctx, target, now, logger, intervalSeconds, fetchImpl });
264
274
  try {
265
275
  const taken = await pool.take({ persona: entry.email, owner });
276
+ await requestRefill({ ctx, exec, uid });
266
277
  return { taken, target, binding: ctx.poolBinding, persona: entry, discard: () => pool.discardTaken(taken.slotId) };
267
278
  } catch (error) {
268
279
  if (!(error instanceof PoolEmptyError)) throw error;
280
+ await requestRefill({ ctx, exec, uid });
269
281
  const health = await assessHealth({ ctx, pool, now, exec, uid, requestedPersona: entry.email });
270
282
  let effective = health;
271
283
  if (health.ok) {
@@ -131,10 +131,12 @@ export const ERROR_CODES = Object.freeze({
131
131
  `${AUTH} # once; if it repeats, report this code and detail`, "retry_once"),
132
132
 
133
133
  // ── the pool's health (human repairs; agents report once and stop)
134
- pool_empty: entry("human", "repair",
134
+ // BOT-2836: a healthy pool emptied between ticks is a wait, not a repair. The take
135
+ // already asked the human-installed job to refill now, so the agent waits once and retries.
136
+ pool_empty: entry("agent", "take",
135
137
  "The staging session pool is healthy but has no ready session for {persona}.",
136
- "Agents took every ready session since the last tick, or the ready ones were dead or AAL1 (factor rotated) and were discarded.",
137
- "{tickCommand}"),
138
+ "Agents took every ready session since the last tick, or the ready ones were dead or AAL1 (factor rotated) and were discarded; the pool job was asked to refill now.",
139
+ "bb wait 'timer:duration=60' && {retry} # once; if it repeats, report this code", "retry_once"),
138
140
  job_not_loaded: entry("human", "install",
139
141
  "The staging session pool is not installed (or not loaded) on this Mac.",
140
142
  "The one-time install has not been run here, or the Mac was migrated.",
@@ -30,8 +30,8 @@ import {
30
30
  export class PoolEmptyError extends Error {
31
31
  constructor(persona, intervalHint) {
32
32
  super(
33
- `No ready ${persona} session in the pool. The human-owned pool job refills on its next tick${intervalHint}; ` +
34
- "report this blocker and its remedy once, then stop; ask the human to check the job. " +
33
+ `No ready ${persona} session in the pool. The human-owned pool job refills on its next tick${intervalHint} ` +
34
+ "and was asked to refill now; wait once, retry once, and report it if it repeats. " +
35
35
  "Agents never sign in to staging themselves.",
36
36
  );
37
37
  this.name = "PoolEmptyError";