@botbuddy/cli 1.64.28 → 1.64.30
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/stack-run-outcome.mjs +8 -5
- package/src/stack.mjs +56 -5
|
Binary file
|
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"release_version": "1.64.
|
|
3
|
-
"source_commit": "
|
|
4
|
-
"source_sha256": "
|
|
2
|
+
"release_version": "1.64.30",
|
|
3
|
+
"source_commit": "cea90b48937b714dae31540ddf0559943b9d87a2",
|
|
4
|
+
"source_sha256": "0ed4fcd3314212258923964ddc437034105fc914901e9bc055668ef650878cd2",
|
|
5
5
|
"target": "darwin-arm64",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "5b8dff2916c1bd2210c91631d93bbe376a15c4f1c3d1720d5b908cd6e2745385"
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"release_version": "1.64.
|
|
3
|
-
"source_commit": "
|
|
4
|
-
"source_sha256": "
|
|
2
|
+
"release_version": "1.64.30",
|
|
3
|
+
"source_commit": "cea90b48937b714dae31540ddf0559943b9d87a2",
|
|
4
|
+
"source_sha256": "0ed4fcd3314212258923964ddc437034105fc914901e9bc055668ef650878cd2",
|
|
5
5
|
"target": "linux-x64",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "711103b10bce9107321a8d9ad0e1e6d5cc86158d91234dcebfa952b7f0819f71"
|
|
7
7
|
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
|
-
"release_version": "1.64.
|
|
4
|
-
"source_commit": "
|
|
5
|
-
"source_sha256": "
|
|
3
|
+
"release_version": "1.64.30",
|
|
4
|
+
"source_commit": "cea90b48937b714dae31540ddf0559943b9d87a2",
|
|
5
|
+
"source_sha256": "0ed4fcd3314212258923964ddc437034105fc914901e9bc055668ef650878cd2",
|
|
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": "5b8dff2916c1bd2210c91631d93bbe376a15c4f1c3d1720d5b908cd6e2745385",
|
|
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": "711103b10bce9107321a8d9ad0e1e6d5cc86158d91234dcebfa952b7f0819f71",
|
|
19
19
|
"mode": "0755"
|
|
20
20
|
}
|
|
21
21
|
}
|
package/package.json
CHANGED
|
@@ -188,6 +188,8 @@ export function buildStackRunOutcomePayload({ fields = {}, exitCode, runId = ran
|
|
|
188
188
|
const failureMechanism = code === "REQUESTER_ORIGIN_DIGEST_UNAVAILABLE" &&
|
|
189
189
|
["origin_config_missing", "origin_digest_missing"].includes(fields.failure_mechanism)
|
|
190
190
|
? fields.failure_mechanism : null;
|
|
191
|
+
// BOT-2977: sent only on a stale-context refusal, so every other payload keeps its shape.
|
|
192
|
+
const fenceScope = code === "STALE_AGENT_CONTEXT" && ["session", "agent"].includes(fields.fence_scope) ? fields.fence_scope : null;
|
|
191
193
|
return {
|
|
192
194
|
run_id: runId,
|
|
193
195
|
lease_id: validStackLeaseId(fields.lease_id),
|
|
@@ -212,6 +214,7 @@ export function buildStackRunOutcomePayload({ fields = {}, exitCode, runId = ran
|
|
|
212
214
|
} : {}),
|
|
213
215
|
...(callbackAuthorizationReason ? { callback_authorization_reason: callbackAuthorizationReason } : {}),
|
|
214
216
|
...(failureMechanism ? { failure_mechanism: failureMechanism } : {}),
|
|
217
|
+
...(fenceScope ? { fence_scope: fenceScope } : {}),
|
|
215
218
|
};
|
|
216
219
|
}
|
|
217
220
|
|
|
@@ -244,11 +247,11 @@ export async function reportStackRunOutcome({ fields, exitCode, runId, opts, env
|
|
|
244
247
|
const first = await channel.callTool(STACK_RUN_OUTCOME_TOOL, payload, { auth: channel.auth, signal: ac.signal });
|
|
245
248
|
// Older servers reject unknown keys before writing. Retry only that exact,
|
|
246
249
|
// explicit rejection with the same run id; never retry an uncertain send.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
first
|
|
251
|
-
const {
|
|
250
|
+
const refusedKey = ["failure_mechanism", "fence_scope"].find((key) => payload[key] &&
|
|
251
|
+
typeof first?.data?.message === "string" && first.data.message.startsWith(`unknown key '${key}'`));
|
|
252
|
+
if (!ac.signal.aborted && refusedKey && first?.ok && first?.isError &&
|
|
253
|
+
first?.data?.code === "INVALID_STACK_RUN_OUTCOME") {
|
|
254
|
+
const { [refusedKey]: _omitted, ...legacyPayload } = payload;
|
|
252
255
|
return await channel.callTool(STACK_RUN_OUTCOME_TOOL, legacyPayload, { auth: channel.auth, signal: ac.signal });
|
|
253
256
|
}
|
|
254
257
|
return first;
|
package/src/stack.mjs
CHANGED
|
@@ -1488,13 +1488,52 @@ export const REMOTE_REFUSAL_GUIDANCE = Object.freeze({
|
|
|
1488
1488
|
|
|
1489
1489
|
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.";
|
|
1490
1490
|
|
|
1491
|
+
const STALE_CONTEXT_TICKET_RE = /^[A-Z][A-Z0-9]{1,15}-[0-9]{1,12}$/;
|
|
1492
|
+
|
|
1493
|
+
/**
|
|
1494
|
+
* BOT-2977: the one command that re-registers this worktree for the requested
|
|
1495
|
+
* ticket. `bb doctor --fix` registers the ticket its BRANCH names, so it is only
|
|
1496
|
+
* offered when that is the requested ticket. `branchTicket` undefined means the
|
|
1497
|
+
* branch is unknown: say nothing rather than guess. Never retried automatically.
|
|
1498
|
+
*/
|
|
1499
|
+
function staleContextRecovery(requested, branchTicket) {
|
|
1500
|
+
if (branchTicket === undefined || requested === "unknown") return "";
|
|
1501
|
+
const branch = typeof branchTicket === "string" && STALE_CONTEXT_TICKET_RE.test(branchTicket) ? branchTicket : null;
|
|
1502
|
+
if (branch === requested) return `Run \`bb doctor --fix\` in this worktree to re-register it for ${requested}, then retry. `;
|
|
1503
|
+
return `${branch ? `This worktree's branch is for ${branch}` : "This worktree's branch names no ticket"}, so \`bb doctor --fix\` here would not register ${requested}: `
|
|
1504
|
+
+ `run \`bb doctor --fix\` in ${requested}'s worktree, or call register_agent with ticket_id ${requested} in this session, then retry. `;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
/** BOT-2977: the fence the server applied (`session` for a tier-3 session, else `agent`). */
|
|
1508
|
+
export function staleContextFenceScope(data) {
|
|
1509
|
+
return data?.code === "STALE_AGENT_CONTEXT" && (data.fence_scope === "session" || data.fence_scope === "agent") ? data.fence_scope : null;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
/** BOT-2977: the ticket this checkout's branch names (null when none), read only for a stale-context refusal. */
|
|
1513
|
+
export async function currentBranchTicket(cwd = process.cwd()) {
|
|
1514
|
+
try {
|
|
1515
|
+
const git = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5000 });
|
|
1516
|
+
if (git.status !== 0 || typeof git.stdout !== "string") return undefined;
|
|
1517
|
+
const branch = git.stdout.trim();
|
|
1518
|
+
// A detached HEAD names no branch, so the branch's ticket is unknown, not absent.
|
|
1519
|
+
if (!branch || branch === "HEAD") return undefined;
|
|
1520
|
+
const match = branch.match(/(?:^|\/)\s*(bot|ent)-(\d+)(?:-|$)/i);
|
|
1521
|
+
return match ? `${match[1].toUpperCase()}-${match[2]}` : null;
|
|
1522
|
+
} catch {
|
|
1523
|
+
return undefined;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1491
1527
|
/** The refusal text for a receipt: the server's message, else the CLI's guidance, else the code. */
|
|
1492
|
-
export function leaseRefusalError(data) {
|
|
1528
|
+
export function leaseRefusalError(data, { branchTicket } = {}) {
|
|
1493
1529
|
if (data?.code === "STALE_AGENT_CONTEXT") {
|
|
1494
1530
|
// These identifiers are shown only in the local receipt; never copy an
|
|
1495
1531
|
// arbitrary server string into it or into the separate telemetry envelope.
|
|
1496
|
-
const ticket = (value) => typeof value === "string" &&
|
|
1497
|
-
|
|
1532
|
+
const ticket = (value) => typeof value === "string" && STALE_CONTEXT_TICKET_RE.test(value) ? value : "unknown";
|
|
1533
|
+
const requested = ticket(data.requested_ticket_id);
|
|
1534
|
+
return `STALE_AGENT_CONTEXT: registered ticket ${ticket(data.registered_ticket_id)}; requested ticket ${requested}. No lease was created. `
|
|
1535
|
+
+ staleContextRecovery(requested, branchTicket)
|
|
1536
|
+
+ "Run `bb help register_agent` to inspect the supported session registration options before retrying.";
|
|
1498
1537
|
}
|
|
1499
1538
|
if (data?.lease_id && ["STACK_CONTEXT_BIND_FAILED", "STACK_CONTEXT_ALREADY_BOUND"].includes(data?.code)) {
|
|
1500
1539
|
return `${data.message || data.code}; lease ${data.lease_id} is ${data.state ?? "unknown"}. ` +
|
|
@@ -2651,6 +2690,8 @@ async function cmdUpOnce(opts, {
|
|
|
2651
2690
|
machineUuidFn = machineUuid,
|
|
2652
2691
|
assertIsolated = validateLocalExecTarget,
|
|
2653
2692
|
refreshAuth = refreshRejectedCacheAuth,
|
|
2693
|
+
// BOT-2977: the ticket this checkout's branch names, read only for a stale-context refusal.
|
|
2694
|
+
branchTicket = currentBranchTicket,
|
|
2654
2695
|
reportLaneStatus = writeLaneStatus,
|
|
2655
2696
|
waitCapacity = defaultWaitCapacity,
|
|
2656
2697
|
reauth = (current, kind) => reauthenticateParkedLease(current, kind),
|
|
@@ -2791,8 +2832,11 @@ async function cmdUpOnce(opts, {
|
|
|
2791
2832
|
if (!d.success) {
|
|
2792
2833
|
const hint = worktreeRegistrationHint(d.code, auth) ?? stackPathRootRefusalNotice(d.code, d.message);
|
|
2793
2834
|
if (hint) process.stderr.write(`${hint}\n`);
|
|
2835
|
+
const fenceScope = staleContextFenceScope(d);
|
|
2836
|
+
const staleBranchTicket = d.code === "STALE_AGENT_CONTEXT" ? await branchTicket() : undefined;
|
|
2794
2837
|
return emitResult(buildReceipt({
|
|
2795
|
-
command: "up", outcome: "error", code: d.code, ...capacityStaleReceiptFields(d), error: leaseRefusalError(d), slot,
|
|
2838
|
+
command: "up", outcome: "error", code: d.code, ...capacityStaleReceiptFields(d), error: leaseRefusalError(d, { branchTicket: staleBranchTicket }), slot,
|
|
2839
|
+
...(fenceScope ? { fence_scope: fenceScope } : {}),
|
|
2796
2840
|
}), opts, leaseRefusalExit(d.code));
|
|
2797
2841
|
}
|
|
2798
2842
|
let leaseId = d.lease_id;
|
|
@@ -4762,9 +4806,13 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
|
|
|
4762
4806
|
// an idle mode mismatch) is not proof that its ports are free. Keep the
|
|
4763
4807
|
// claim until owner-safe cleanup is verified; never reap a possibly live batch.
|
|
4764
4808
|
if (request.data?.lease_id) retainBlock = true;
|
|
4809
|
+
// BOT-2977: name the re-registration command; the refusal itself is never retried here.
|
|
4810
|
+
const fenceScope = staleContextFenceScope(request.data);
|
|
4811
|
+
const branchTicket = request.data?.code === "STALE_AGENT_CONTEXT" ? await (adapters.branchTicket ?? currentBranchTicket)() : undefined;
|
|
4765
4812
|
return {
|
|
4766
4813
|
exitCode: leaseRefusalExit(request.data?.code), outcome: "error", code: request.data?.code,
|
|
4767
|
-
...
|
|
4814
|
+
...(fenceScope ? { fenceScope } : {}),
|
|
4815
|
+
...capacityStaleReceiptFields(request.data), error: leaseRefusalError(request.data, { branchTicket }),
|
|
4768
4816
|
...(request.data?.sqlstate ? { sqlstate: request.data.sqlstate } : {}),
|
|
4769
4817
|
...(request.data.lease_id ? { leaseId: request.data.lease_id, state: request.data.state ?? null } : {}),
|
|
4770
4818
|
};
|
|
@@ -5455,6 +5503,9 @@ export function runReceiptFields(result) {
|
|
|
5455
5503
|
...(result.callbackAuthorizationReason ? { callback_authorization_reason: result.callbackAuthorizationReason } : {}),
|
|
5456
5504
|
...(result.code === "REQUESTER_ORIGIN_DIGEST_UNAVAILABLE" && ["origin_config_missing", "origin_digest_missing"].includes(result.failureMechanism)
|
|
5457
5505
|
? { failure_mechanism: result.failureMechanism } : {}),
|
|
5506
|
+
// BOT-2977: which fence refused a STALE_AGENT_CONTEXT run (session | agent).
|
|
5507
|
+
...(result.code === "STALE_AGENT_CONTEXT" && (result.fenceScope === "session" || result.fenceScope === "agent")
|
|
5508
|
+
? { fence_scope: result.fenceScope } : {}),
|
|
5458
5509
|
// BOT-2957: the control-plane gaps this run's callback route rode through (never on a run that had none).
|
|
5459
5510
|
...(result.callbackOutage ? { callback_outage: result.callbackOutage } : {}),
|
|
5460
5511
|
// BOT-2780: the reserved requester origin generated auth links name (never a credential).
|