chore(cli): drop dead classifiers and single-use wrappers left by fallback removal (#112191)

Follow-up to c5254f13ee (#112074): delete the legacy string-match timeout
classifier (the CLI path only sees typed GatewayTransportError from
callGateway's wrapper timer), the duplicate JSON/blank-message validation
in the private gateway command, the now-pointless getGatewayDispatchConfig
wrapper, the single-use embedded-loader alias, and the cross-os release
guard for the EMBEDDED FALLBACK marker no build can emit anymore.
This commit is contained in:
Peter Steinberger
2026-07-21 00:21:15 -07:00
committed by GitHub
parent ae27ee98f0
commit e1ff65fb77
5 changed files with 13 additions and 88 deletions

View File

@@ -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<AgentTurnResult, "stdout" | "stderr">,
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")) {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<OpenClawConfig> {
@@ -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<OpenClawConfig> {
// 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<typeof createAgentCliSignalBridge>,
) {
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 <id>, --session-key <key>, --session-id <id>, or --to <E.164>. 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,

View File

@@ -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 {