diff --git a/src/agents/agent-command.ts b/src/agents/agent-command.ts index 1ab90997a55..ff718d4506a 100644 --- a/src/agents/agent-command.ts +++ b/src/agents/agent-command.ts @@ -27,6 +27,7 @@ import { emitAgentEvent, registerAgentRunContext, } from "../infra/agent-events.js"; +import { formatErrorMessage } from "../infra/errors.js"; import { buildOutboundSessionContext } from "../infra/outbound/session-context.js"; import { getRemoteSkillEligibility } from "../infra/skills-remote.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; @@ -480,7 +481,7 @@ async function agentCommandInternal( }); } catch (error) { log.warn( - `ACP transcript persistence failed for ${sessionKey}: ${error instanceof Error ? error.message : String(error)}`, + `ACP transcript persistence failed for ${sessionKey}: ${formatErrorMessage(error)}`, ); } diff --git a/src/agents/auth-profiles/oauth.ts b/src/agents/auth-profiles/oauth.ts index 39ff15adf69..14b3d39ca42 100644 --- a/src/agents/auth-profiles/oauth.ts +++ b/src/agents/auth-profiles/oauth.ts @@ -6,6 +6,7 @@ import { } from "@mariozechner/pi-ai/oauth"; import { loadConfig, type OpenClawConfig } from "../../config/config.js"; import { coerceSecretRef } from "../../config/types.secrets.js"; +import { formatErrorMessage } from "../../infra/errors.js"; import { withFileLock } from "../../infra/file-lock.js"; import { formatProviderAuthProfileApiKeyWithPlugin, @@ -119,7 +120,7 @@ async function buildOAuthProfileResult(params: { } function extractErrorMessage(error: unknown): string { - return error instanceof Error ? error.message : String(error); + return formatErrorMessage(error); } function isRefreshTokenReusedError(error: unknown): boolean { @@ -207,7 +208,7 @@ function adoptNewerMainOAuthCredential(params: { // Best-effort: don't crash if main agent store is missing or unreadable. log.debug("adoptNewerMainOAuthCredential failed", { profileId: params.profileId, - error: err instanceof Error ? err.message : String(err), + error: formatErrorMessage(err), }); } return null; @@ -403,7 +404,7 @@ async function resolveProfileSecretString(params: { log.debug(params.inlineFailureMessage, { profileId: params.profileId, provider: params.provider, - error: err instanceof Error ? err.message : String(err), + error: formatErrorMessage(err), }); } } @@ -421,7 +422,7 @@ async function resolveProfileSecretString(params: { log.debug(params.refFailureMessage, { profileId: params.profileId, provider: params.provider, - error: err instanceof Error ? err.message : String(err), + error: formatErrorMessage(err), }); } } diff --git a/src/agents/cli-credentials.ts b/src/agents/cli-credentials.ts index ff802510fb6..d2b906e2897 100644 --- a/src/agents/cli-credentials.ts +++ b/src/agents/cli-credentials.ts @@ -2,6 +2,7 @@ import { execFileSync, execSync } from "node:child_process"; import { createHash } from "node:crypto"; import fs from "node:fs"; import path from "node:path"; +import { formatErrorMessage } from "../infra/errors.js"; import { loadJsonFile, saveJsonFile } from "../infra/json-file.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import { resolveUserPath } from "../utils.js"; @@ -461,7 +462,7 @@ export function writeClaudeCliKeychainCredentials( return true; } catch (error) { log.warn("failed to write credentials to claude cli keychain", { - error: error instanceof Error ? error.message : String(error), + error: formatErrorMessage(error), }); return false; } @@ -503,7 +504,7 @@ export function writeClaudeCliFileCredentials( return true; } catch (error) { log.warn("failed to write credentials to claude cli file", { - error: error instanceof Error ? error.message : String(error), + error: formatErrorMessage(error), }); return false; } @@ -584,7 +585,7 @@ export function writeCodexCliKeychainCredentials( return true; } catch (error) { log.warn("failed to write credentials to codex cli keychain", { - error: error instanceof Error ? error.message : String(error), + error: formatErrorMessage(error), }); return false; } @@ -614,7 +615,7 @@ export function writeCodexCliFileCredentials( return true; } catch (error) { log.warn("failed to write credentials to codex cli file", { - error: error instanceof Error ? error.message : String(error), + error: formatErrorMessage(error), }); return false; } diff --git a/src/agents/cli-runner.ts b/src/agents/cli-runner.ts index d2eedf06401..c204eff66e4 100644 --- a/src/agents/cli-runner.ts +++ b/src/agents/cli-runner.ts @@ -1,6 +1,7 @@ import type { ImageContent } from "@mariozechner/pi-ai"; import type { ThinkLevel } from "../auto-reply/thinking.js"; import type { OpenClawConfig } from "../config/config.js"; +import { formatErrorMessage } from "../infra/errors.js"; import { executePreparedCliRun } from "./cli-runner/execute.js"; import { prepareCliRunContext } from "./cli-runner/prepare.js"; import type { RunCliAgentParams } from "./cli-runner/types.js"; @@ -73,7 +74,7 @@ export async function runCliAgent(params: RunCliAgentParams): Promise(params: { // compaction/retry logic, not by model fallback. If one escapes as a // throw, rethrow it immediately rather than trying a different model // that may have a smaller context window and fail worse. - const errMessage = err instanceof Error ? err.message : String(err); + const errMessage = formatErrorMessage(err); if (isLikelyContextOverflowError(errMessage)) { throw err; } @@ -937,7 +938,7 @@ export async function runWithImageModelFallback(params: { attempts.push({ provider: candidate.provider, model: candidate.model, - error: err instanceof Error ? err.message : String(err), + error: formatErrorMessage(err), }); await params.onError?.({ provider: candidate.provider, diff --git a/src/agents/model-scan.ts b/src/agents/model-scan.ts index 5b30d8d4478..570837882a6 100644 --- a/src/agents/model-scan.ts +++ b/src/agents/model-scan.ts @@ -8,6 +8,7 @@ import { type Tool, } from "@mariozechner/pi-ai"; import { Type } from "@sinclair/typebox"; +import { formatErrorMessage } from "../infra/errors.js"; import { inferParamBFromIdOrName } from "../shared/model-param-b.js"; import { normalizeProviderId } from "./provider-id.js"; @@ -284,7 +285,7 @@ async function probeTool( return { ok: false, latencyMs: Date.now() - startedAt, - error: err instanceof Error ? err.message : String(err), + error: formatErrorMessage(err), }; } } @@ -321,7 +322,7 @@ async function probeImage( return { ok: false, latencyMs: Date.now() - startedAt, - error: err instanceof Error ? err.message : String(err), + error: formatErrorMessage(err), }; } } diff --git a/src/agents/models-config.providers.implicit.ts b/src/agents/models-config.providers.implicit.ts index 88b7abb9c44..37965ad3cf2 100644 --- a/src/agents/models-config.providers.implicit.ts +++ b/src/agents/models-config.providers.implicit.ts @@ -1,4 +1,5 @@ import type { OpenClawConfig } from "../config/config.js"; +import { formatErrorMessage } from "../infra/errors.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import { groupPluginDiscoveryProvidersByOrder, @@ -322,7 +323,7 @@ async function runProviderCatalogWithTimeout( }), ]); } catch (error) { - const message = error instanceof Error ? error.message : String(error); + const message = formatErrorMessage(error); if (message.includes("provider catalog timed out after")) { log.warn(`${message}; skipping provider discovery`); return undefined; diff --git a/src/agents/openai-ws-stream.ts b/src/agents/openai-ws-stream.ts index 890f453c358..025afd08091 100644 --- a/src/agents/openai-ws-stream.ts +++ b/src/agents/openai-ws-stream.ts @@ -1,3 +1,12 @@ +import { randomUUID } from "node:crypto"; +import type { StreamFn } from "@mariozechner/pi-agent-core"; +import type { + AssistantMessage, + AssistantMessageEvent, + AssistantMessageEventStream, + StopReason, +} from "@mariozechner/pi-ai"; +import * as piAi from "@mariozechner/pi-ai"; /** * OpenAI WebSocket StreamFn Integration * @@ -20,16 +29,7 @@ * * @see src/agents/openai-ws-connection.ts for the connection manager */ - -import { randomUUID } from "node:crypto"; -import type { StreamFn } from "@mariozechner/pi-agent-core"; -import type { - AssistantMessage, - AssistantMessageEvent, - AssistantMessageEventStream, - StopReason, -} from "@mariozechner/pi-ai"; -import * as piAi from "@mariozechner/pi-ai"; +import { formatErrorMessage } from "../infra/errors.js"; import { resolveProviderTransportTurnStateWithPlugin, resolveProviderWebSocketSessionPolicyWithPlugin, @@ -550,7 +550,7 @@ function normalizeWsRunError(err: unknown): OpenAIWebSocketRuntimeError { if (err instanceof OpenAIWebSocketRuntimeError) { return err; } - return new OpenAIWebSocketRuntimeError(err instanceof Error ? err.message : String(err), { + return new OpenAIWebSocketRuntimeError(formatErrorMessage(err), { kind: "server", retryable: false, }); @@ -1177,7 +1177,7 @@ export function createOpenAIWebSocketStreamFn( queueMicrotask(() => run().catch((err) => { - const errorMessage = err instanceof Error ? err.message : String(err); + const errorMessage = formatErrorMessage(err); log.warn(`[ws-stream] session=${sessionId} run error: ${errorMessage}`); eventStream.push({ type: "error", diff --git a/src/agents/run-wait.ts b/src/agents/run-wait.ts index 49fe6762170..72b6e8d05bc 100644 --- a/src/agents/run-wait.ts +++ b/src/agents/run-wait.ts @@ -1,4 +1,5 @@ import { callGateway } from "../gateway/call.js"; +import { formatErrorMessage } from "../infra/errors.js"; import { extractAssistantText, stripToolMessages } from "./tools/chat-history-text.js"; type GatewayCaller = typeof callGateway; @@ -137,7 +138,7 @@ export async function waitForAgentRun(params: { } return normalizeAgentWaitResult("ok", wait); } catch (err) { - const error = err instanceof Error ? err.message : String(err); + const error = formatErrorMessage(err); return { status: error.includes("gateway timeout") ? "timeout" : "error", error, diff --git a/src/agents/simple-completion-runtime.ts b/src/agents/simple-completion-runtime.ts index d49f8a73498..d7bb4bd2c2e 100644 --- a/src/agents/simple-completion-runtime.ts +++ b/src/agents/simple-completion-runtime.ts @@ -1,5 +1,6 @@ import { complete, type Api, type Model } from "@mariozechner/pi-ai"; import type { OpenClawConfig } from "../config/config.js"; +import { formatErrorMessage } from "../infra/errors.js"; import { resolveAgentDir, resolveAgentEffectiveModelPrimary } from "./agent-scope.js"; import { DEFAULT_PROVIDER } from "./defaults.js"; import { @@ -152,7 +153,7 @@ export async function prepareSimpleCompletionModel(params: { }); } catch (err) { return { - error: `Auth lookup failed for provider "${resolved.model.provider}": ${err instanceof Error ? err.message : String(err)}`, + error: `Auth lookup failed for provider "${resolved.model.provider}": ${formatErrorMessage(err)}`, }; } const rawApiKey = auth.apiKey?.trim(); diff --git a/src/agents/skills-clawhub.ts b/src/agents/skills-clawhub.ts index 76d978a8c71..d47b0f51d45 100644 --- a/src/agents/skills-clawhub.ts +++ b/src/agents/skills-clawhub.ts @@ -10,6 +10,7 @@ import { type ClawHubSkillDetail, type ClawHubSkillSearchResult, } from "../infra/clawhub.js"; +import { formatErrorMessage } from "../infra/errors.js"; import { withExtractedArchiveRoot } from "../infra/install-flow.js"; import { installPackageDir } from "../infra/install-package-dir.js"; import { resolveSafeInstallDir } from "../infra/install-safe-path.js"; @@ -336,7 +337,7 @@ async function performClawHubSkillInstall( } catch (err) { return { ok: false, - error: err instanceof Error ? err.message : String(err), + error: formatErrorMessage(err), }; } } @@ -352,7 +353,7 @@ async function installRequestedSkillFromClawHub( } catch (err) { return { ok: false, - error: err instanceof Error ? err.message : String(err), + error: formatErrorMessage(err), }; } } @@ -368,7 +369,7 @@ async function installTrackedSkillFromClawHub( } catch (err) { return { ok: false, - error: err instanceof Error ? err.message : String(err), + error: formatErrorMessage(err), }; } } diff --git a/src/agents/skills-install-download.ts b/src/agents/skills-install-download.ts index f5c62ceb0e8..2b2ee98bb7a 100644 --- a/src/agents/skills-install-download.ts +++ b/src/agents/skills-install-download.ts @@ -5,6 +5,7 @@ import { Readable } from "node:stream"; import { pipeline } from "node:stream/promises"; import type { ReadableStream as NodeReadableStream } from "node:stream/web"; import { isWindowsDrivePath } from "../infra/archive-path.js"; +import { formatErrorMessage } from "../infra/errors.js"; import { writeFileFromPathWithinRoot } from "../infra/fs-safe.js"; import { assertCanonicalPathWithinBase } from "../infra/install-safe-path.js"; import { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js"; @@ -151,7 +152,7 @@ export async function installDownloadSpec(params: { const targetRelativePath = path.relative(safeRoot, requestedTargetDir); targetDir = path.join(canonicalSafeRoot, targetRelativePath); } catch (err) { - const message = err instanceof Error ? err.message : String(err); + const message = formatErrorMessage(err); return { ok: false, message, stdout: "", stderr: message, code: null }; } @@ -181,7 +182,7 @@ export async function installDownloadSpec(params: { }); downloaded = result.bytes; } catch (err) { - const message = err instanceof Error ? err.message : String(err); + const message = formatErrorMessage(err); return { ok: false, message, stdout: "", stderr: message, code: null }; } @@ -214,7 +215,7 @@ export async function installDownloadSpec(params: { boundaryLabel: "skill tools directory", }); } catch (err) { - const message = err instanceof Error ? err.message : String(err); + const message = formatErrorMessage(err); return { ok: false, message, stdout: "", stderr: message, code: null }; } diff --git a/src/agents/skills-install-extract.ts b/src/agents/skills-install-extract.ts index 02a5b22c3d5..8270eb28c36 100644 --- a/src/agents/skills-install-extract.ts +++ b/src/agents/skills-install-extract.ts @@ -7,6 +7,7 @@ import { prepareArchiveDestinationDir, withStagedArchiveDestination, } from "../infra/archive.js"; +import { formatErrorMessage } from "../infra/errors.js"; import { runCommandWithTimeout } from "../process/exec.js"; import { parseTarVerboseMetadata } from "./skills-install-tar-verbose.js"; import { hasBinary } from "./skills.js"; @@ -227,7 +228,7 @@ export async function extractArchive(params: { return { stdout: "", stderr: `unsupported archive type: ${archiveType}`, code: null }; } catch (err) { - const message = err instanceof Error ? err.message : String(err); + const message = formatErrorMessage(err); return { stdout: "", stderr: message, code: 1 }; } } diff --git a/src/agents/skills-install.ts b/src/agents/skills-install.ts index 2ec7ea0a126..a594014cb5b 100644 --- a/src/agents/skills-install.ts +++ b/src/agents/skills-install.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import path from "node:path"; import type { OpenClawConfig } from "../config/config.js"; import { resolveBrewExecutable } from "../infra/brew.js"; +import { formatErrorMessage } from "../infra/errors.js"; import { type InstallSafetyOverrides, scanSkillInstallSource, @@ -248,7 +249,7 @@ async function runCommandSafely( return { code: null, stdout: "", - stderr: err instanceof Error ? err.message : String(err), + stderr: formatErrorMessage(err), }; } } diff --git a/src/agents/subagent-control.ts b/src/agents/subagent-control.ts index 586af966162..44bb4cd5521 100644 --- a/src/agents/subagent-control.ts +++ b/src/agents/subagent-control.ts @@ -11,6 +11,7 @@ import type { SessionEntry } from "../config/sessions.js"; import { loadSessionStore, resolveStorePath, updateSessionStore } from "../config/sessions.js"; import { callGateway } from "../gateway/call.js"; import { logVerbose } from "../globals.js"; +import { formatErrorMessage } from "../infra/errors.js"; import { isSubagentSessionKey, parseAgentSessionKey } from "../routing/session-key.js"; import { INTERNAL_MESSAGE_CHANNEL } from "../utils/message-channel.js"; import { AGENT_LANE_SUBAGENT } from "./lanes.js"; @@ -171,9 +172,7 @@ async function killSubagentRun(params: { }); } catch (error) { logVerbose( - `subagents control kill: failed to persist abortedLastRun for ${childSessionKey}: ${ - error instanceof Error ? error.message : String(error) - }`, + `subagents control kill: failed to persist abortedLastRun for ${childSessionKey}: ${formatErrorMessage(error)}`, ); } } @@ -557,7 +556,7 @@ export async function steerControlledSubagentRun(params: { } } catch (err) { clearSubagentRunSteerRestart(params.entry.runId); - const error = err instanceof Error ? err.message : String(err); + const error = formatErrorMessage(err); return { status: "error", runId, @@ -681,7 +680,7 @@ export async function sendControlledSubagentMessage(params: { } return { status: "ok" as const, runId, replyText: result.replyText }; } catch (err) { - const error = err instanceof Error ? err.message : String(err); + const error = formatErrorMessage(err); return { status: "error" as const, runId, error }; } } diff --git a/src/agents/tools/gateway.ts b/src/agents/tools/gateway.ts index 88067e7d928..41b377a436b 100644 --- a/src/agents/tools/gateway.ts +++ b/src/agents/tools/gateway.ts @@ -5,6 +5,7 @@ import { resolveLeastPrivilegeOperatorScopesForMethod, type OperatorScope, } from "../../gateway/method-scopes.js"; +import { formatErrorMessage } from "../../infra/errors.js"; import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../../utils/message-channel.js"; import { readStringParam } from "./common.js"; @@ -32,7 +33,7 @@ function canonicalizeToolGatewayWsUrl(raw: string): { origin: string; key: strin try { url = new URL(input); } catch (error) { - const message = error instanceof Error ? error.message : String(error); + const message = formatErrorMessage(error); throw new Error(`invalid gatewayUrl: ${input} (${message})`, { cause: error }); } diff --git a/src/agents/tools/nodes-tool-commands.ts b/src/agents/tools/nodes-tool-commands.ts index 59b56d1e73c..cede378579d 100644 --- a/src/agents/tools/nodes-tool-commands.ts +++ b/src/agents/tools/nodes-tool-commands.ts @@ -1,5 +1,6 @@ import crypto from "node:crypto"; import { parseTimeoutMs } from "../../cli/parse-timeout.js"; +import { formatErrorMessage } from "../../infra/errors.js"; import { jsonResult, readStringParam } from "./common.js"; import type { GatewayCallOptions } from "./gateway.js"; import { callGatewayTool } from "./gateway.js"; @@ -138,7 +139,7 @@ export async function executeNodeCommandAction(params: { try { invokeParams = JSON.parse(invokeParamsJson); } catch (err) { - const message = err instanceof Error ? err.message : String(err); + const message = formatErrorMessage(err); throw new Error(`invokeParamsJson must be valid JSON: ${message}`, { cause: err, }); diff --git a/src/agents/tools/sessions-resolution.ts b/src/agents/tools/sessions-resolution.ts index 243cd16d32d..d3fb345bee9 100644 --- a/src/agents/tools/sessions-resolution.ts +++ b/src/agents/tools/sessions-resolution.ts @@ -1,5 +1,6 @@ import type { OpenClawConfig } from "../../config/config.js"; import { callGateway } from "../../gateway/call.js"; +import { formatErrorMessage } from "../../infra/errors.js"; import { isAcpSessionKey, normalizeMainKey } from "../../routing/session-key.js"; import { looksLikeSessionId } from "../../sessions/session-id.js"; @@ -274,7 +275,7 @@ async function resolveSessionKeyFromSessionId(params: { error: `Session not visible from this sandboxed agent session: ${params.sessionId}`, }; } - const message = err instanceof Error ? err.message : String(err); + const message = formatErrorMessage(err); return { ok: false, status: "error", diff --git a/src/agents/tools/sessions-send-tool.ts b/src/agents/tools/sessions-send-tool.ts index 821ae3e1a15..05c792a0769 100644 --- a/src/agents/tools/sessions-send-tool.ts +++ b/src/agents/tools/sessions-send-tool.ts @@ -2,6 +2,7 @@ import crypto from "node:crypto"; import { Type } from "@sinclair/typebox"; import type { OpenClawConfig } from "../../config/config.js"; import { callGateway } from "../../gateway/call.js"; +import { formatErrorMessage } from "../../infra/errors.js"; import { normalizeAgentId, resolveAgentIdFromSessionKey } from "../../routing/session-key.js"; import { SESSION_LABEL_MAX_LENGTH } from "../../sessions/session-label.js"; import { @@ -156,7 +157,7 @@ export function createSessionsSendTool(opts?: { }); resolvedKey = typeof resolved?.key === "string" ? resolved.key.trim() : ""; } catch (err) { - const msg = err instanceof Error ? err.message : String(err); + const msg = formatErrorMessage(err); if (restrictToSpawned) { return jsonResult({ runId: crypto.randomUUID(),