@botbuddy/cli 1.45.6 → 1.45.7
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/agent-session.mjs +0 -0
- package/src/stack.mjs +29 -35
package/package.json
CHANGED
package/src/agent-session.mjs
CHANGED
|
Binary file
|
package/src/stack.mjs
CHANGED
|
@@ -33,8 +33,7 @@ import { callToolJson, CALL_AUTH_ERROR_CODES } from "./api.mjs";
|
|
|
33
33
|
import { SERVER_URL, getConfig } from "./config.mjs";
|
|
34
34
|
import { resolveOwnerToken, resolveAgentKey } from "./cli-credentials.mjs";
|
|
35
35
|
import { AGENT_KEY_RE, readAgentSessionTokenEnv } from "./agent-key.mjs";
|
|
36
|
-
import { resolveAgentSessionCredential } from "./agent-session.mjs";
|
|
37
|
-
import { clearAgentState } from "./agent-state.mjs";
|
|
36
|
+
import { resolveAgentSessionCredential, selfHealAgentSession } from "./agent-session.mjs";
|
|
38
37
|
import { runDockerCommand, runDockerWorkflow, ADMITTED_DOCKER_VALIDATIONS } from "./docker-hygiene.mjs";
|
|
39
38
|
import { acquireStackLock, lockPathForProject, projectIdFromConfig } from "./stack-file-lock.mjs";
|
|
40
39
|
import { assertLeasedStackIsolated, leasedStackIdentity, materializeRunStackDir, portMapInConfig, refreshLeasedStackRetention, releaseLeasedStackClaim, reservePortBlocks } from "./stack-isolation.mjs";
|
|
@@ -828,7 +827,7 @@ export async function stackAuthHeader({
|
|
|
828
827
|
// and silently authenticate as the OAuth/MCP-key principal, which is the exact
|
|
829
828
|
// principal switch the catch above guards against.
|
|
830
829
|
if (typeof credential.token === "string" && AGENT_KEY_RE.test(credential.token)) {
|
|
831
|
-
return withSource({ "x-agent-api-key": credential.token }, credential.source);
|
|
830
|
+
return withSource({ "x-agent-api-key": credential.token }, credential.source, credential.identity);
|
|
832
831
|
}
|
|
833
832
|
throw Object.assign(new Error(`resolved agent session token has an unexpected shape (source: ${credential.source ?? "unknown"}); re-run \`bb doctor --fix\``), { code: "session_token_malformed" });
|
|
834
833
|
}
|
|
@@ -848,37 +847,39 @@ export async function stackAuthHeader({
|
|
|
848
847
|
}
|
|
849
848
|
|
|
850
849
|
// BOT-1750 (Codex R7 P2): carry the credential SOURCE on the returned headers as a
|
|
851
|
-
// NON-enumerable property, so callers can
|
|
850
|
+
// NON-enumerable property, so callers can renew+retry once on a
|
|
852
851
|
// server-rejected cached session (parity with run/test/pw/wait) without the source
|
|
853
852
|
// ever leaking into the spread fetch headers.
|
|
854
|
-
function withSource(headers, source) {
|
|
853
|
+
function withSource(headers, source, identity = null) {
|
|
855
854
|
Object.defineProperty(headers, "__source", { value: source ?? null, enumerable: false });
|
|
855
|
+
Object.defineProperty(headers, "__identity", { value: identity, enumerable: false });
|
|
856
856
|
return headers;
|
|
857
857
|
}
|
|
858
858
|
|
|
859
859
|
/**
|
|
860
860
|
* BOT-1750 (Codex R7 P2): a locally-fresh cache whose token was revoked server-side
|
|
861
861
|
* (e.g. a concurrent re-registration rotated the session) is rejected at the lease
|
|
862
|
-
* RPC.
|
|
863
|
-
*
|
|
864
|
-
*
|
|
865
|
-
* null when no retry applies (not cache-sourced, or the refresh could not authenticate).
|
|
862
|
+
* RPC. Renew once without deleting the cache or losing lease ownership identity.
|
|
863
|
+
* The shared lock re-reads and verifies the cache; a concurrent renewal wins.
|
|
864
|
+
* Failure leaves the existing cache untouched and propagates its typed error.
|
|
866
865
|
*/
|
|
867
866
|
export async function refreshRejectedCacheAuth(auth, {
|
|
868
|
-
cwd = process.cwd(),
|
|
867
|
+
cwd = process.cwd(), renew = selfHealAgentSession,
|
|
869
868
|
} = {}) {
|
|
870
869
|
if (auth?.__source !== "cache") return null;
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
//
|
|
878
|
-
|
|
879
|
-
|
|
870
|
+
return renewStackSession(auth, { cwd, renew });
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
async function renewStackSession(auth, { cwd, renew }) {
|
|
874
|
+
const credential = await renew({ cwd, force: true,
|
|
875
|
+
rejectedCredential: { token: auth["x-agent-api-key"], identity: auth.__identity } });
|
|
876
|
+
// BOT-1750 (Codex R8 P2): the renewal adapter must never yield an operator
|
|
877
|
+
// principal. "cache" is permitted
|
|
878
|
+
// only for a newer same-identity session verified inside the renewal lock.
|
|
879
|
+
if (credential?.source !== "minted" && credential?.source !== "cache") return null;
|
|
880
|
+
const token = credential.token;
|
|
880
881
|
if (typeof token !== "string" || !AGENT_KEY_RE.test(token) || token === auth["x-agent-api-key"]) return null;
|
|
881
|
-
return
|
|
882
|
+
return withSource({ "x-agent-api-key": token }, credential.source, credential.identity);
|
|
882
883
|
}
|
|
883
884
|
|
|
884
885
|
/**
|
|
@@ -886,8 +887,8 @@ export async function refreshRejectedCacheAuth(auth, {
|
|
|
886
887
|
* server-rejected cached session ONCE — the owner-scoped management commands
|
|
887
888
|
* (`status`/`touch`/`done`) need the same recovery as the lease-creating paths, or a
|
|
888
889
|
* revoked-but-locally-fresh cache leaves an active lease unreleasable. `init()`
|
|
889
|
-
* resolves auth; `call()` runs the RPC and, on the FIRST auth rejection,
|
|
890
|
-
*
|
|
890
|
+
* resolves auth; `call()` runs the RPC and, on the FIRST auth rejection, renews +
|
|
891
|
+
* retries with the fresh credential (every later `call` reuses it).
|
|
891
892
|
*/
|
|
892
893
|
export function retryingStackCall({
|
|
893
894
|
callTool = callToolJson, authProvider = stackAuthHeader,
|
|
@@ -1362,25 +1363,18 @@ function abortableSleep(ms, signal) {
|
|
|
1362
1363
|
* BOT-2099: re-establish the credential a parked lease waits with. On a transport failure
|
|
1363
1364
|
* the credential is fine — keep it. On an auth rejection, first re-read (another process in
|
|
1364
1365
|
* this worktree may already have re-minted, and minting again would supersede it), then
|
|
1365
|
-
*
|
|
1366
|
+
* renew under the shared cache lock without removing recovery identity. Only a well-formed agent SESSION token
|
|
1366
1367
|
* replaces a session credential: the operator fallback is a different principal that
|
|
1367
1368
|
* cannot see the lease (BOT-1750 R8), so it is never adopted. Throws the mint error, which
|
|
1368
1369
|
* the park treats as one more failed attempt.
|
|
1369
1370
|
*/
|
|
1370
1371
|
export async function reauthenticateParkedLease(current, kind, {
|
|
1371
|
-
cwd = process.cwd(),
|
|
1372
|
+
cwd = process.cwd(), renew = selfHealAgentSession,
|
|
1372
1373
|
} = {}) {
|
|
1373
1374
|
if (kind !== "auth") return current;
|
|
1374
1375
|
const source = current?.__source ?? null;
|
|
1375
1376
|
if (source !== "cache" && source !== "minted") return current;
|
|
1376
|
-
|
|
1377
|
-
&& typeof h["x-agent-api-key"] === "string" && AGENT_KEY_RE.test(h["x-agent-api-key"])
|
|
1378
|
-
&& h["x-agent-api-key"] !== current["x-agent-api-key"];
|
|
1379
|
-
const reread = await authProvider();
|
|
1380
|
-
if (isFreshSession(reread)) return reread;
|
|
1381
|
-
await clear(cwd);
|
|
1382
|
-
const minted = await authProvider();
|
|
1383
|
-
return isFreshSession(minted) ? minted : current;
|
|
1377
|
+
return (await renewStackSession(current, { cwd, renew })) ?? current;
|
|
1384
1378
|
}
|
|
1385
1379
|
|
|
1386
1380
|
/**
|
|
@@ -2482,7 +2476,7 @@ export async function cmdDone(leaseId, opts, {
|
|
|
2482
2476
|
} = {}) {
|
|
2483
2477
|
// BOT-1750 (Codex R10 P2): `done` must recover a server-rejected cached session on
|
|
2484
2478
|
// its own — otherwise an active lease can't be released until the cache is manually
|
|
2485
|
-
// repaired. The retrying caller
|
|
2479
|
+
// repaired. The retrying caller renews+retries the FIRST auth rejection.
|
|
2486
2480
|
const rpc = retryingStackCall({ callTool, authProvider, refresh: refreshAuth });
|
|
2487
2481
|
const auth = await rpc.init();
|
|
2488
2482
|
if (!auth) return emitResult(buildReceipt({ command: "done", outcome: "error", lease_id: leaseId, error: "not authenticated — run `botbuddy login`" }), opts, EXIT.AUTH);
|
|
@@ -2839,7 +2833,7 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
|
|
|
2839
2833
|
// BOT-1750 (Codex R9/R11 P2): `stack run` is a lease-creating entry point AND a
|
|
2840
2834
|
// long-lived one (queue → provision → child → heartbeat → cleanup). A cached token
|
|
2841
2835
|
// can be rotated server-side at ANY of those points, so EVERY RPC — not just the
|
|
2842
|
-
// initial request — must be able to
|
|
2836
|
+
// initial request — must be able to renew + retry once. Route them all
|
|
2843
2837
|
// through one retryingStackCall so a single self-heal re-authenticates the rest
|
|
2844
2838
|
// (heartbeat, get, cleanup release), instead of fencing a healthy child or leaking
|
|
2845
2839
|
// an unreleasable lease. Retry only when we own the credential or a test provides an
|
|
@@ -3110,7 +3104,7 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
|
|
|
3110
3104
|
...(bundle?.included?.length ? { bundle_included: bundle.included } : {}),
|
|
3111
3105
|
...(opts.callbackPort != null ? { callback_port: opts.callbackPort } : {}),
|
|
3112
3106
|
};
|
|
3113
|
-
// The retrying caller (api → rpc.call) transparently
|
|
3107
|
+
// The retrying caller (api → rpc.call) transparently renews+retries the
|
|
3114
3108
|
// FIRST auth rejection among any of request/get/touch/release.
|
|
3115
3109
|
// BOT-2024: a stale-capacity refusal prints phase-aware guidance; with the opt-in
|
|
3116
3110
|
// --wait-capacity a TRANSIENT one parks on the server's capacity wait and re-admits once.
|