@botbuddy/cli 1.63.40 → 1.63.42
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/daemon/bin/darwin-arm64/botbuddy-daemon +0 -0
- package/daemon/bin/linux-x64/botbuddy-daemon +0 -0
- package/daemon-release/darwin-arm64.json +4 -4
- package/daemon-release/linux-x64.json +4 -4
- package/daemon-release/manifest.json +5 -5
- package/package.json +1 -1
- package/src/pw/staging-auth.mjs +6 -1
- package/src/stack.mjs +4 -2
- package/src/staging-pool/cli.mjs +12 -0
- package/src/staging-pool/errors.mjs +5 -3
- package/src/staging-pool/pool.mjs +2 -2
|
Binary file
|
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"release_version": "1.63.
|
|
3
|
-
"source_commit": "
|
|
4
|
-
"source_sha256": "
|
|
2
|
+
"release_version": "1.63.42",
|
|
3
|
+
"source_commit": "68cd7a93519197cc7b96a190fa0c4b4799a9c45d",
|
|
4
|
+
"source_sha256": "3a4e94f577771c564aa24d54529368c6b6d325be1e64ef0a30da1531f02be6ed",
|
|
5
5
|
"target": "darwin-arm64",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "3c5f90061df675f1e06408ce5e1f3c9b6350e5a237216b0dbc11d71d656a1b35"
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"release_version": "1.63.
|
|
3
|
-
"source_commit": "
|
|
4
|
-
"source_sha256": "
|
|
2
|
+
"release_version": "1.63.42",
|
|
3
|
+
"source_commit": "68cd7a93519197cc7b96a190fa0c4b4799a9c45d",
|
|
4
|
+
"source_sha256": "3a4e94f577771c564aa24d54529368c6b6d325be1e64ef0a30da1531f02be6ed",
|
|
5
5
|
"target": "linux-x64",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "a5a474195eca0a8b57ddf8c5d50cfddb88f083a57bc99e80dc4f1a7d10e99535"
|
|
7
7
|
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
|
-
"release_version": "1.63.
|
|
4
|
-
"source_commit": "
|
|
5
|
-
"source_sha256": "
|
|
3
|
+
"release_version": "1.63.42",
|
|
4
|
+
"source_commit": "68cd7a93519197cc7b96a190fa0c4b4799a9c45d",
|
|
5
|
+
"source_sha256": "3a4e94f577771c564aa24d54529368c6b6d325be1e64ef0a30da1531f02be6ed",
|
|
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": "
|
|
11
|
+
"sha256": "3c5f90061df675f1e06408ce5e1f3c9b6350e5a237216b0dbc11d71d656a1b35",
|
|
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": "
|
|
18
|
+
"sha256": "a5a474195eca0a8b57ddf8c5d50cfddb88f083a57bc99e80dc4f1a7d10e99535",
|
|
19
19
|
"mode": "0755"
|
|
20
20
|
}
|
|
21
21
|
}
|
package/package.json
CHANGED
package/src/pw/staging-auth.mjs
CHANGED
|
@@ -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
|
-
|
|
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);
|
package/src/stack.mjs
CHANGED
|
@@ -1356,7 +1356,7 @@ export function leaseRefusalExit(code) {
|
|
|
1356
1356
|
// server refusing the credential — never BACKEND, which a gate reads as "down".
|
|
1357
1357
|
if (CALL_AUTH_ERROR_CODES.has(code)) return EXIT.AUTH;
|
|
1358
1358
|
// BOT-1926: a reused lease requested in the other execution mode is fixable by the caller.
|
|
1359
|
-
return code === STACK_PATH_ROOT_REFUSED_CODE || code === "STACK_EXECUTION_MODE_MISMATCH" ? EXIT.INVALID : EXIT.BACKEND;
|
|
1359
|
+
return code === STACK_PATH_ROOT_REFUSED_CODE || code === "STACK_EXECUTION_MODE_MISMATCH" || code === "AGENT_DEEPLINK_REQUIRED" ? EXIT.INVALID : EXIT.BACKEND;
|
|
1360
1360
|
}
|
|
1361
1361
|
|
|
1362
1362
|
/**
|
|
@@ -1386,6 +1386,8 @@ export const REMOTE_REFUSAL_GUIDANCE = Object.freeze({
|
|
|
1386
1386
|
"re-run (the bundle is built automatically unless it failed — see the warning above) or use --local-only.",
|
|
1387
1387
|
});
|
|
1388
1388
|
|
|
1389
|
+
const AGENT_DEEPLINK_REQUIRED_GUIDANCE = "the control plane refused this stack because its owning agent has no canonical profile or native session link. Reconnect with `bb register`, then retry. No lease was created.";
|
|
1390
|
+
|
|
1389
1391
|
/** The refusal text for a receipt: the server's message, else the CLI's guidance, else the code. */
|
|
1390
1392
|
export function leaseRefusalError(data) {
|
|
1391
1393
|
if (data?.lease_id && ["STACK_CONTEXT_BIND_FAILED", "STACK_CONTEXT_ALREADY_BOUND"].includes(data?.code)) {
|
|
@@ -1398,7 +1400,7 @@ export function leaseRefusalError(data) {
|
|
|
1398
1400
|
return `the server's identified admission failed in the database${data.sqlstate ? ` (SQLSTATE ${data.sqlstate})` : ""} and rolled back, ` +
|
|
1399
1401
|
"so no lease was created and this stack's port claim is released. This is a BotBuddy server fault: report it with this run's admission request id.";
|
|
1400
1402
|
}
|
|
1401
|
-
return data?.message || REMOTE_REFUSAL_GUIDANCE[data?.code] || data?.code || "lease request refused";
|
|
1403
|
+
return data?.message || (data?.code === "AGENT_DEEPLINK_REQUIRED" ? AGENT_DEEPLINK_REQUIRED_GUIDANCE : null) || REMOTE_REFUSAL_GUIDANCE[data?.code] || data?.code || "lease request refused";
|
|
1402
1404
|
}
|
|
1403
1405
|
|
|
1404
1406
|
/** One stderr line naming a remote-lease refusal (null for every other code). */
|
package/src/staging-pool/cli.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
"{
|
|
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
|
-
"
|
|
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";
|