diff --git a/src/lib/actions/sandbox/connect-autopair-budget.ts b/src/lib/actions/sandbox/connect-autopair-budget.ts index 55fe761f6d5..7bc4641b9c4 100644 --- a/src/lib/actions/sandbox/connect-autopair-budget.ts +++ b/src/lib/actions/sandbox/connect-autopair-budget.ts @@ -6,7 +6,10 @@ // so tests can import and assert the invariant on the real values without // pulling in connect.ts's heavy transitive requires (#4504). -export const CONNECT_AUTO_PAIR_MAX_APPROVALS = 1; +// Fresh OpenClaw finalization can observe the initial CLI pairing request and +// its immediately following operator.write upgrade in the same pass. Keep the +// budget at those two bounded transitions so neither request is left pending. +export const CONNECT_AUTO_PAIR_MAX_APPROVALS = 2; // `openclaw devices list` budget (seconds), interpolated into the in-sandbox // script so the invariant below is asserted on real values, not source text. // A cold OpenClaw 2026.6.10 CLI can take just over 2s to load its runtime @@ -30,4 +33,4 @@ export const CONNECT_AUTO_PAIR_POST_TIMEOUT_OBSERVE_S = 4; // sources the proxy environment and launches Python. Keep 10s beyond the longer // inner path so the outer timer cannot terminate a legitimate approval before // its fixed receipt is returned. -export const CONNECT_AUTO_PAIR_TIMEOUT_MS = 25_000; +export const CONNECT_AUTO_PAIR_TIMEOUT_MS = 35_000; diff --git a/test/sandbox-connect-inference/auto-pair-approval.test.ts b/test/sandbox-connect-inference/auto-pair-approval.test.ts index 20d8413b8a6..f8bf99c19d1 100644 --- a/test/sandbox-connect-inference/auto-pair-approval.test.ts +++ b/test/sandbox-connect-inference/auto-pair-approval.test.ts @@ -115,10 +115,11 @@ describe("sandbox connect auto-pair approval pass (#4263)", () => { const script = extractApprovalPassScript(stateFile, sandboxName); // Disallowed/malformed/unknown requests are skipped by the policy before // an approve is even attempted (they `continue` before the attempt - // counter increments), so they do not consume the MAX_APPROVALS=1 budget - // (#4504). They are ordered first here to prove the rejection path runs; - // the single allowed request (`ok-cli`) is then approved and exhausts the - // one-attempt budget, so the trailing duplicate `ok-cli` is never reached. + // counter increments), so they do not consume the bounded approval + // budget (#4504). They are ordered first here to prove the rejection path + // runs. The initial CLI pairing and its write-scope upgrade are then both + // approved, while the trailing distinct request proves the two-approval + // cap stops the pass. const run = runApprovalPassScript(script, [ { requestId: "admin-cli", @@ -139,24 +140,28 @@ describe("sandbox connect auto-pair approval pass (#4263)", () => { scopes: ["operator.read"], }, { - requestId: "ok-cli", - clientId: "openclaw-cli", + requestId: "initial-cli-pairing", + clientId: "cli", clientMode: "cli", - scopes: ["operator.read", "operator.write"], + scopes: ["operator.pairing"], }, { - requestId: "ok-cli", - clientId: "openclaw-cli", + requestId: "cli-write-upgrade", + clientId: "cli", clientMode: "cli", + scopes: ["operator.pairing", "operator.write"], + }, + { + requestId: "later-webchat-upgrade", + clientId: "openclaw-control-ui", + clientMode: "webchat", scopes: ["operator.read", "operator.write"], }, ]); expect(run.result.status).toBe(0); - // Only the first allowed request is approved — MAX_APPROVALS is 1 (#4504), - // the realistic single pending CLI/webchat scope upgrade. - expect(run.approvals).toEqual(["ok-cli"]); - expect(run.approvalEnv).toEqual(["unset:unset:unset"]); + expect(run.approvals).toEqual(["initial-cli-pairing", "cli-write-upgrade"]); + expect(run.approvalEnv).toEqual(["unset:unset:unset", "unset:unset:unset"]); }, );