Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/lib/actions/sandbox/connect-autopair-budget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
31 changes: 18 additions & 13 deletions test/sandbox-connect-inference/auto-pair-approval.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"]);
},
);

Expand Down
Loading