mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(release): fail closed on cross-os agent turns
This commit is contained in:
@@ -41,7 +41,7 @@ export const CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS = parsePositiveIntegerEnv(
|
||||
"OPENCLAW_CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS",
|
||||
600,
|
||||
);
|
||||
const CROSS_OS_AGENT_TURN_OPTIONAL = parseBooleanEnv("OPENCLAW_CROSS_OS_AGENT_TURN_OPTIONAL", true);
|
||||
const CROSS_OS_AGENT_TURN_OPTIONAL = resolveCrossOsAgentTurnOptional();
|
||||
|
||||
const providerConfig = {
|
||||
openai: {
|
||||
@@ -180,8 +180,8 @@ function parsePositiveIntegerEnv(name, fallback) {
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseBooleanEnv(name, fallback) {
|
||||
const raw = process.env[name]?.trim();
|
||||
function parseBooleanEnv(name, fallback, env = process.env) {
|
||||
const raw = env[name]?.trim();
|
||||
if (!raw) {
|
||||
return fallback;
|
||||
}
|
||||
@@ -194,6 +194,10 @@ function parseBooleanEnv(name, fallback) {
|
||||
throw new Error(`${name} must be a boolean. Got: ${JSON.stringify(raw)}`);
|
||||
}
|
||||
|
||||
export function resolveCrossOsAgentTurnOptional(env = process.env) {
|
||||
return parseBooleanEnv("OPENCLAW_CROSS_OS_AGENT_TURN_OPTIONAL", false, env);
|
||||
}
|
||||
|
||||
export function looksLikeReleaseVersionRef(ref) {
|
||||
const trimmed = normalizeRequestedRef(ref);
|
||||
return /^v?[0-9]{4}\.[0-9]+\.[0-9]+(?:-(?:[1-9][0-9]*)|[-.](?:alpha|beta|rc)[-.]?[0-9]+)?$/iu.test(
|
||||
@@ -2274,7 +2278,10 @@ async function runInstalledAgentTurn(params) {
|
||||
return result;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
const skipped = maybeBuildOptionalAgentTurnSkipResult(error, params.logPath);
|
||||
const skipped = maybeBuildOptionalAgentTurnSkipResult(error, params.logPath, {
|
||||
attempt,
|
||||
maxAttempts: 2,
|
||||
});
|
||||
if (skipped) {
|
||||
return skipped;
|
||||
}
|
||||
@@ -3090,7 +3097,10 @@ async function runAgentTurn(params) {
|
||||
return result;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
const skipped = maybeBuildOptionalAgentTurnSkipResult(error, params.logPath);
|
||||
const skipped = maybeBuildOptionalAgentTurnSkipResult(error, params.logPath, {
|
||||
attempt,
|
||||
maxAttempts: 2,
|
||||
});
|
||||
if (skipped) {
|
||||
return skipped;
|
||||
}
|
||||
@@ -3108,8 +3118,15 @@ async function runAgentTurn(params) {
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
function maybeBuildOptionalAgentTurnSkipResult(error, logPath) {
|
||||
if (!CROSS_OS_AGENT_TURN_OPTIONAL || !shouldSkipOptionalCrossOsAgentTurnError(error, logPath)) {
|
||||
export function maybeBuildOptionalAgentTurnSkipResult(error, logPath, options = {}) {
|
||||
const attempt = options.attempt ?? 1;
|
||||
const maxAttempts = options.maxAttempts ?? 2;
|
||||
const optional = options.optional ?? CROSS_OS_AGENT_TURN_OPTIONAL;
|
||||
if (
|
||||
attempt < maxAttempts ||
|
||||
!optional ||
|
||||
!shouldSkipOptionalCrossOsAgentTurnError(error, logPath)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
|
||||
@@ -45,11 +45,13 @@ import {
|
||||
normalizeRequestedRef,
|
||||
normalizeWindowsCommandShimPath,
|
||||
normalizeWindowsInstalledCliPath,
|
||||
maybeBuildOptionalAgentTurnSkipResult,
|
||||
parseCrossOsSuiteFilter,
|
||||
parseArgs,
|
||||
packageHasScript,
|
||||
readInstalledVersion,
|
||||
readRunnerOverrideEnv,
|
||||
resolveCrossOsAgentTurnOptional,
|
||||
runCommand,
|
||||
resolveCommandSpawnInvocation,
|
||||
resolveExplicitBaselineVersion,
|
||||
@@ -174,6 +176,16 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("requires explicit opt-in before cross-OS agent turns become optional", () => {
|
||||
expect(resolveCrossOsAgentTurnOptional({})).toBe(false);
|
||||
expect(resolveCrossOsAgentTurnOptional({ OPENCLAW_CROSS_OS_AGENT_TURN_OPTIONAL: "1" })).toBe(
|
||||
true,
|
||||
);
|
||||
expect(resolveCrossOsAgentTurnOptional({ OPENCLAW_CROSS_OS_AGENT_TURN_OPTIONAL: "false" })).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("detects embedded fallback agent turns as non-gateway proof", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "openclaw-cross-os-agent-fallback-"));
|
||||
const logPath = join(dir, "agent.log");
|
||||
@@ -244,6 +256,46 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("only skips opted-in cross-OS live agent turns after retry exhaustion", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "openclaw-cross-os-agent-skip-retry-"));
|
||||
try {
|
||||
const logPath = join(dir, "agent.log");
|
||||
const error = new Error("gateway request timeout for agent after 210000ms");
|
||||
|
||||
expect(
|
||||
maybeBuildOptionalAgentTurnSkipResult(error, logPath, {
|
||||
attempt: 1,
|
||||
maxAttempts: 2,
|
||||
optional: true,
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(
|
||||
maybeBuildOptionalAgentTurnSkipResult(error, logPath, {
|
||||
attempt: 2,
|
||||
maxAttempts: 2,
|
||||
optional: false,
|
||||
}),
|
||||
).toBeNull();
|
||||
|
||||
const skipped = maybeBuildOptionalAgentTurnSkipResult(error, logPath, {
|
||||
attempt: 2,
|
||||
maxAttempts: 2,
|
||||
optional: true,
|
||||
});
|
||||
|
||||
expect(skipped?.status).toBe(0);
|
||||
expect(JSON.parse(skipped?.stdout ?? "{}")).toEqual({
|
||||
status: "skipped",
|
||||
reason: "cross-os live agent turn unavailable after retry",
|
||||
});
|
||||
expect(readFileSync(logPath, "utf8")).toContain(
|
||||
"skipping optional cross-OS live agent turn after retryable failure",
|
||||
);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("allows cross-OS provider smoke models to use faster CI overrides", () => {
|
||||
expect(
|
||||
resolveProviderConfig("openai", {
|
||||
|
||||
Reference in New Issue
Block a user