@botbuddy/cli 1.33.5 → 1.33.6
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 +1 -1
- package/src/stack.mjs +47 -9
package/package.json
CHANGED
package/src/stack.mjs
CHANGED
|
@@ -76,10 +76,12 @@ ${bold("up OPTIONS")}
|
|
|
76
76
|
--host <host_key> Pin to a canonical host. Omit to auto-select a beacon-fresh host.
|
|
77
77
|
--repo <repo> Repository the batch is for (e.g. botbuddy-web).
|
|
78
78
|
--ticket <BOT-123> Ticket the batch is for (also used to derive the slot).
|
|
79
|
-
--stack-path <relative>
|
|
80
|
-
|
|
81
|
-
supabase/config.toml
|
|
82
|
-
|
|
79
|
+
--stack-path <relative> ISOLATED stack directory inside the registered worktree. Required:
|
|
80
|
+
the control plane refuses the worktree root (stack_path_root_refused,
|
|
81
|
+
BOT-1797) because its supabase/config.toml is the repo's SHARED
|
|
82
|
+
canonical project. Point it at a slot-derived stack whose
|
|
83
|
+
supabase/config.toml declares a DISTINCT project_id + remapped ports,
|
|
84
|
+
or use 'stack run', which materializes one for you.
|
|
83
85
|
--purpose <text> Free-text purpose recorded on the lease.
|
|
84
86
|
--idle-ttl <seconds> Idle seconds before the reaper STOPS an unused stack (default 1800).
|
|
85
87
|
--timeout <seconds> Max seconds to park for capacity before giving up (default ${DEFAULT_TIMEOUT_SEC}).
|
|
@@ -669,6 +671,39 @@ export function worktreeRegistrationHint(code, auth) {
|
|
|
669
671
|
+ `Run \`bb doctor --fix\` here to register its host + machine id, then retry.`;
|
|
670
672
|
}
|
|
671
673
|
|
|
674
|
+
// BOT-1797: the control plane refuses a Helper-backed lease whose stack_path is the
|
|
675
|
+
// worktree root — its supabase/config.toml is the repo's SHARED canonical project, so
|
|
676
|
+
// the Helper would start (and on reap destroy) the shared dev stack. This CLI always
|
|
677
|
+
// materializes an isolated stack (BOT-1798), so seeing this code means a stale binary,
|
|
678
|
+
// an explicit root --stack-path, or a wrapper that hand-rolls the lease call.
|
|
679
|
+
export const STACK_PATH_ROOT_REFUSED_CODE = "stack_path_root_refused";
|
|
680
|
+
|
|
681
|
+
// Only used when an older/leaner server sends the code with no message; the server's
|
|
682
|
+
// own message is always preferred so the two can never drift.
|
|
683
|
+
const STACK_PATH_ROOT_REFUSED_FALLBACK =
|
|
684
|
+
"the control plane refused a stack lease at the worktree root (the repo's shared stack). " +
|
|
685
|
+
"Upgrade with `npm i -g @botbuddy/cli@latest`, or pass --stack-path <isolated dir>.";
|
|
686
|
+
|
|
687
|
+
/** The server's root refusal, verbatim, as one operator-facing line (or null). */
|
|
688
|
+
export function stackPathRootRefusalNotice(code, message) {
|
|
689
|
+
if (code !== STACK_PATH_ROOT_REFUSED_CODE) return null;
|
|
690
|
+
return `${yellow("⚠")} stack: ${message || STACK_PATH_ROOT_REFUSED_FALLBACK}`;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Exit code for a lease the server REFUSED (`success: false`). A refused root path is a
|
|
695
|
+
* bad argument the caller must fix — INVALID (4) — not a backend fault, which automation
|
|
696
|
+
* reads as "the control plane is unhealthy, retry". Every other refusal keeps BACKEND (5).
|
|
697
|
+
*/
|
|
698
|
+
export function leaseRefusalExit(code) {
|
|
699
|
+
return code === STACK_PATH_ROOT_REFUSED_CODE ? EXIT.INVALID : EXIT.BACKEND;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/** The refusal text for a receipt: the server's message, else its code. */
|
|
703
|
+
export function leaseRefusalError(data) {
|
|
704
|
+
return data?.message || data?.code || "lease request refused";
|
|
705
|
+
}
|
|
706
|
+
|
|
672
707
|
// The MCP lease endpoint accepts a session token in x-agent-api-key, whereas
|
|
673
708
|
// the event-stream relay requires that same token in its bearer form too. Keep
|
|
674
709
|
// the lease RPC shape unchanged and derive the relay-compatible form only at
|
|
@@ -1340,9 +1375,9 @@ export async function cmdUp(opts, {
|
|
|
1340
1375
|
}
|
|
1341
1376
|
const d = req.data;
|
|
1342
1377
|
if (!d.success) {
|
|
1343
|
-
const hint = worktreeRegistrationHint(d.code, auth);
|
|
1378
|
+
const hint = worktreeRegistrationHint(d.code, auth) ?? stackPathRootRefusalNotice(d.code, d.message);
|
|
1344
1379
|
if (hint) process.stderr.write(`${hint}\n`);
|
|
1345
|
-
return emitResult(buildReceipt({ command: "up", outcome: "error", code: d.code, error: d
|
|
1380
|
+
return emitResult(buildReceipt({ command: "up", outcome: "error", code: d.code, error: leaseRefusalError(d), slot }), opts, leaseRefusalExit(d.code));
|
|
1346
1381
|
}
|
|
1347
1382
|
let leaseId = d.lease_id;
|
|
1348
1383
|
let state = d.state;
|
|
@@ -2007,9 +2042,10 @@ export async function runStackLifecycle(opts, childArgv, adapters = {}) {
|
|
|
2007
2042
|
return { exitCode: request?.auth ? EXIT.AUTH : EXIT.BACKEND, outcome: "error", error: request?.error || "lease request failed" };
|
|
2008
2043
|
}
|
|
2009
2044
|
if (!request.data?.success) {
|
|
2010
|
-
const hint = worktreeRegistrationHint(request.data?.code, rpc.auth)
|
|
2045
|
+
const hint = worktreeRegistrationHint(request.data?.code, rpc.auth)
|
|
2046
|
+
?? stackPathRootRefusalNotice(request.data?.code, request.data?.message);
|
|
2011
2047
|
if (hint) process.stderr.write(`${hint}\n`);
|
|
2012
|
-
return { exitCode:
|
|
2048
|
+
return { exitCode: leaseRefusalExit(request.data?.code), outcome: "error", code: request.data?.code, error: leaseRefusalError(request.data) };
|
|
2013
2049
|
}
|
|
2014
2050
|
leaseId = request.data.lease_id;
|
|
2015
2051
|
if (request.data.reused) {
|
|
@@ -2154,7 +2190,9 @@ export async function runStackLifecycle(opts, childArgv, adapters = {}) {
|
|
|
2154
2190
|
async function cmdRun(opts, childArgv) {
|
|
2155
2191
|
const result = await runStackLifecycle(opts, childArgv);
|
|
2156
2192
|
return emit(buildReceipt({
|
|
2157
|
-
|
|
2193
|
+
// BOT-1797: carry the server's typed refusal code (e.g. stack_path_root_refused)
|
|
2194
|
+
// so automation can branch on it instead of matching the message text.
|
|
2195
|
+
command: "run", outcome: result.outcome, code: result.code ?? null, lease_id: result.leaseId ?? null,
|
|
2158
2196
|
child_exit_code: result.childExitCode ?? null, cleanup: result.cleanup?.ok ?? null,
|
|
2159
2197
|
fenced: result.fenced ?? false, error: result.error ?? result.cleanup?.error ?? null,
|
|
2160
2198
|
}), opts, result.exitCode);
|