diff --git a/scripts/lib/cross-os-release-checks/agent.ts b/scripts/lib/cross-os-release-checks/agent.ts index 47720311ebc9..a2b4e790bed2 100644 --- a/scripts/lib/cross-os-release-checks/agent.ts +++ b/scripts/lib/cross-os-release-checks/agent.ts @@ -1,6 +1,6 @@ import { randomUUID } from "node:crypto"; import { appendFileSync } from "node:fs"; -import type { AgentOutputOptions, AgentTurnResult } from "./config.ts"; +import type { AgentOutputOptions } from "./config.ts"; import { CROSS_OS_AGENT_TURN_OPTIONAL, CROSS_OS_AGENT_TURN_TIMEOUT_SECONDS } from "./config.ts"; import { readLogTextTail } from "./logs.ts"; @@ -73,24 +73,11 @@ export function buildReleaseAgentTurnArgs(sessionId: string) { export function shouldRetryCrossOsAgentTurnError(error: unknown) { const message = error instanceof Error ? error.message : String(error); - return /Agent output did not contain the expected OK marker|Agent turn used embedded fallback instead of gateway|model idle timeout|did not produce a response before the model idle timeout|gateway request timeout for agent|Command timed out|timed out and could not be terminated cleanly|rate limit reached|rate_limit_exceeded|HTTP 429|HTTP 503|upstream connect error|disconnect\/reset before headers|connection timeout/u.test( + return /Agent output did not contain the expected OK marker|model idle timeout|did not produce a response before the model idle timeout|gateway request timeout for agent|Command timed out|timed out and could not be terminated cleanly|rate limit reached|rate_limit_exceeded|HTTP 429|HTTP 503|upstream connect error|disconnect\/reset before headers|connection timeout/u.test( message, ); } -export function agentTurnUsedEmbeddedFallback( - result: Pick, - options: AgentOutputOptions = {}, -) { - const logText = - typeof options.logText === "string" - ? options.logText - : typeof options.logPath === "string" - ? readLogTextTail(options.logPath) - : ""; - return /EMBEDDED FALLBACK:/u.test(`${result.stdout ?? ""}\n${result.stderr ?? ""}\n${logText}`); -} - export function agentOutputHasExpectedOkMarker(stdout: string, options: AgentOutputOptions = {}) { const payloadTexts = parseAgentPayloadTexts(stdout); if (payloadTexts.some((text) => text.trim() === "OK")) { diff --git a/scripts/lib/cross-os-release-checks/installed.ts b/scripts/lib/cross-os-release-checks/installed.ts index 0aad70888c55..27b9dd0a21c8 100644 --- a/scripts/lib/cross-os-release-checks/installed.ts +++ b/scripts/lib/cross-os-release-checks/installed.ts @@ -3,7 +3,6 @@ import { appendFileSync, createWriteStream, existsSync, mkdirSync } from "node:f import { dirname, join } from "node:path"; import { agentOutputHasExpectedOkMarker, - agentTurnUsedEmbeddedFallback, buildCrossOsReleaseAgentSessionId, buildReleaseAgentTurnArgs, maybeBuildOptionalAgentTurnSkipResult, @@ -766,9 +765,6 @@ export async function runInstalledAgentTurn(params: { if (!agentOutputHasExpectedOkMarker(result.stdout, { logText })) { throw new Error("Agent output did not contain the expected OK marker."); } - if (agentTurnUsedEmbeddedFallback(result, { logText })) { - throw new Error("Agent turn used embedded fallback instead of gateway."); - } return result; } catch (error) { lastError = error; diff --git a/scripts/lib/cross-os-release-checks/runtime.ts b/scripts/lib/cross-os-release-checks/runtime.ts index 594e8fa06277..275b4c738e90 100644 --- a/scripts/lib/cross-os-release-checks/runtime.ts +++ b/scripts/lib/cross-os-release-checks/runtime.ts @@ -2,7 +2,6 @@ import { spawn } from "node:child_process"; import { appendFileSync, createWriteStream } from "node:fs"; import { agentOutputHasExpectedOkMarker, - agentTurnUsedEmbeddedFallback, buildCrossOsReleaseAgentSessionId, buildReleaseAgentTurnArgs, maybeBuildOptionalAgentTurnSkipResult, @@ -308,9 +307,6 @@ export async function runAgentTurn( if (!agentOutputHasExpectedOkMarker(result.stdout, { logText })) { throw new Error("Agent output did not contain the expected OK marker."); } - if (agentTurnUsedEmbeddedFallback(result, { logText })) { - throw new Error("Agent turn used embedded fallback instead of gateway."); - } return result; } catch (error) { lastError = error; diff --git a/src/commands/agent-via-gateway.ts b/src/commands/agent-via-gateway.ts index ed8a172ea383..913c90b70a86 100644 --- a/src/commands/agent-via-gateway.ts +++ b/src/commands/agent-via-gateway.ts @@ -136,7 +136,6 @@ function resolveGatewayAbortRetryDelaysMs(): readonly number[] { return gatewayAbortRetryDelaysMsForTests ?? GATEWAY_ABORT_RETRY_DELAYS_MS; } -const loadEmbeddedAgentCommand = embeddedAgentCommandLoader.load; const loadAgentSessionModule = agentSessionModuleCache.load; async function loadRuntimeConfig(): Promise { @@ -273,17 +272,6 @@ function resolveGatewayAgentTimeoutMs(timeoutSeconds: number): number { return resolveTimerTimeoutMs((timeoutSeconds + 30) * 1000, 10_000, 10_000); } -async function getGatewayDispatchConfig(options?: { - skipShellEnvFallback?: boolean; -}): Promise { - // Scoped gateway turns need core agent/session/gateway fields only. The - // running gateway owns plugin validation and plugin metadata freshness. - if (options?.skipShellEnvFallback === false) { - return await readGatewayDispatchConfigWithShellEnvFallback(); - } - return readGatewayDispatchConfig(); -} - async function formatPayloadForLog(payload: { text?: string; mediaUrls?: string[]; @@ -305,13 +293,6 @@ async function formatPayloadForLog(payload: { return lines.join("\n").trimEnd(); } -function isGatewayAgentTimeoutError(err: unknown): boolean { - if (isGatewayTransportError(err)) { - return err.kind === "timeout"; - } - return err instanceof Error && err.message.includes("gateway request timeout for agent"); -} - function isCompactControlCommand(message: string): boolean { return /^\/compact(?:\s|:|$)/iu.test(message.trim()); } @@ -331,10 +312,12 @@ function shouldRetryGatewayDispatchWithShellEnvFallback(err: unknown): boolean { function resolveGatewayAgentFailureHint( err: unknown, ): "timed out" | "connection closed" | undefined { - if (isGatewayAgentTimeoutError(err)) { - return "timed out"; + if (!isGatewayTransportError(err)) { + return undefined; } - return isGatewayTransportError(err) && err.kind === "closed" ? "connection closed" : undefined; + // callGateway's wrapper timer gives this CLI path typed transport errors. + // Legacy request-timeout strings belong to lower-level and in-process callers. + return err.kind === "timeout" ? "timed out" : "connection closed"; } function isTransientGatewayAgentConnectClose(err: unknown): boolean { @@ -394,7 +377,7 @@ async function normalizeSessionKeyOptsForDispatch( isLegacySessionKey && (agentIdRaw || shouldScopeDefaultAgentKey) ? opts.local === true ? await loadRuntimeConfig() - : await getGatewayDispatchConfig() + : readGatewayDispatchConfig() : undefined; const sessionKey = scopeLegacySessionKeyToAgent({ agentId: agentIdRaw ?? (shouldScopeDefaultAgentKey ? resolveDefaultAgentId(cfg!) : undefined), @@ -663,19 +646,17 @@ async function agentViaGatewayCommand( runtime: RuntimeEnv, signalBridge: ReturnType, ) { - protectJsonStdout(opts); const body = opts.message; const explicitSessionKey = opts.sessionKey?.trim(); - if (!body.trim()) { - throw missingAgentMessageError(); - } if (!opts.to && !opts.sessionId && !opts.agent && !explicitSessionKey) { throw new Error( `No target session selected. Use --agent , --session-key , --session-id , or --to . Run ${formatCliCommand("openclaw agents list")} to see agents.`, ); } - let cfg = await getGatewayDispatchConfig(); + // Scoped gateway turns need core agent/session/gateway fields only. The + // running gateway owns plugin validation and plugin metadata freshness. + let cfg: OpenClawConfig = readGatewayDispatchConfig(); const agentIdRaw = opts.agent?.trim(); const agentId = agentIdRaw ? normalizeAgentId(agentIdRaw) : undefined; if (agentId) { @@ -806,7 +787,7 @@ async function agentViaGatewayCommand( shouldRetryGatewayDispatchWithShellEnvFallback(err) && consumeShellEnvFallbackRetry() ) { - cfg = await getGatewayDispatchConfig({ skipShellEnvFallback: false }); + cfg = await readGatewayDispatchConfigWithShellEnvFallback(); continue; } if ( @@ -914,7 +895,7 @@ export async function agentCliCommand( const signalBridge = createAgentCliSignalBridge(resolveAgentCliProcessLike(deps)); try { if (dispatchOpts.local === true) { - const agentCommand = await loadEmbeddedAgentCommand(); + const agentCommand = await embeddedAgentCommandLoader.load(); const result = await agentCommand( { ...gatewayDispatchOpts, diff --git a/test/scripts/openclaw-cross-os-release-checks.test.ts b/test/scripts/openclaw-cross-os-release-checks.test.ts index 2f1de7c8bfc7..9a21ffe3a081 100644 --- a/test/scripts/openclaw-cross-os-release-checks.test.ts +++ b/test/scripts/openclaw-cross-os-release-checks.test.ts @@ -19,7 +19,6 @@ import { pathToFileURL } from "node:url"; import { describe, expect, it } from "vitest"; import { agentOutputHasExpectedOkMarker, - agentTurnUsedEmbeddedFallback, buildCrossOsDiscordRoundtripNonces, buildCrossOsReleaseAgentSessionId, buildCrossOsReleaseSmokePluginAllowlist, @@ -455,11 +454,6 @@ describe("scripts/openclaw-cross-os-release-checks", () => { new Error("Command timed out and could not be terminated cleanly"), ), ).toBe(true); - expect( - shouldRetryCrossOsAgentTurnError( - new Error("Agent turn used embedded fallback instead of gateway."), - ), - ).toBe(true); expect( shouldRetryCrossOsAgentTurnError( new Error( @@ -486,35 +480,6 @@ describe("scripts/openclaw-cross-os-release-checks", () => { ).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"); - expect( - agentTurnUsedEmbeddedFallback({ - stdout: JSON.stringify({ payloads: [{ text: "OK" }] }), - stderr: "EMBEDDED FALLBACK: Gateway agent failed; running embedded agent: gateway closed", - }), - ).toBe(true); - expect( - agentTurnUsedEmbeddedFallback({ - stdout: JSON.stringify({ payloads: [{ text: "OK" }] }), - stderr: "", - }), - ).toBe(false); - expect( - agentTurnUsedEmbeddedFallback( - { stdout: "", stderr: "" }, - { logText: 'EMBEDDED FALLBACK: Gateway agent failed\n{"payloads":[{"text":"OK"}]}' }, - ), - ).toBe(true); - try { - writeFileSync(logPath, "EMBEDDED FALLBACK: Gateway agent failed\n"); - expect(agentTurnUsedEmbeddedFallback({ stdout: "", stderr: "" }, { logPath })).toBe(true); - } finally { - rmSync(dir, { recursive: true, force: true }); - } - }); - it("skips optional live agent turns only for model availability failures", () => { const dir = mkdtempSync(join(tmpdir(), "openclaw-cross-os-agent-skip-")); try {