From fef4e4621ae1a83db86b97cedf09691a309b4a3c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 7 Apr 2026 01:01:20 +0100 Subject: [PATCH] refactor: dedupe pi compaction error formatting --- src/agents/pi-embedded-runner/compact.ts | 7 ++++--- .../pi-embedded-runner/compaction-hooks.ts | 11 ++++++----- .../openrouter-model-capabilities.ts | 5 +++-- .../run/attempt.stop-reason-recovery.ts | 17 +++++------------ src/agents/pi-embedded-runner/run/images.ts | 9 ++++----- .../pi-embedded-runner/session-truncation.ts | 7 ++++--- src/agents/pi-embedded-runner/thinking.ts | 3 ++- .../tool-result-truncation.ts | 5 +++-- .../pi-embedded-runner/transcript-rewrite.ts | 3 ++- src/agents/pi-hooks/compaction-safeguard.ts | 15 +++++++-------- 10 files changed, 40 insertions(+), 42 deletions(-) diff --git a/src/agents/pi-embedded-runner/compact.ts b/src/agents/pi-embedded-runner/compact.ts index 06306b49874..a57ded1458f 100644 --- a/src/agents/pi-embedded-runner/compact.ts +++ b/src/agents/pi-embedded-runner/compact.ts @@ -22,6 +22,7 @@ import { resolveSessionCompactionCheckpointReason, type CapturedCompactionCheckpointSnapshot, } from "../../gateway/session-compaction-checkpoints.js"; +import { formatErrorMessage } from "../../infra/errors.js"; import { resolveHeartbeatSummaryForAgent } from "../../infra/heartbeat-summary.js"; import { getMachineDisplayName } from "../../infra/machine-name.js"; import { generateSecureToken } from "../../infra/secure-random.js"; @@ -1084,7 +1085,7 @@ export async function compactEmbeddedPiSessionDirect( } } catch (err) { log.warn("[compaction] post-compaction truncation failed", { - errorMessage: err instanceof Error ? err.message : String(err), + errorMessage: formatErrorMessage(err), errorStack: err instanceof Error ? err.stack : undefined, }); } @@ -1279,7 +1280,7 @@ export async function compactEmbeddedPiSession( ); } catch (err) { log.warn("before_compaction hook failed", { - errorMessage: err instanceof Error ? err.message : String(err), + errorMessage: formatErrorMessage(err), }); } } @@ -1356,7 +1357,7 @@ export async function compactEmbeddedPiSession( ); } catch (err) { log.warn("after_compaction hook failed", { - errorMessage: err instanceof Error ? err.message : String(err), + errorMessage: formatErrorMessage(err), }); } } diff --git a/src/agents/pi-embedded-runner/compaction-hooks.ts b/src/agents/pi-embedded-runner/compaction-hooks.ts index 7ee674e377b..bed1d1d2f6b 100644 --- a/src/agents/pi-embedded-runner/compaction-hooks.ts +++ b/src/agents/pi-embedded-runner/compaction-hooks.ts @@ -1,6 +1,7 @@ import type { AgentMessage } from "@mariozechner/pi-agent-core"; import type { OpenClawConfig } from "../../config/config.js"; import { createInternalHookEvent, triggerInternalHook } from "../../hooks/internal-hooks.js"; +import { formatErrorMessage } from "../../infra/errors.js"; import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js"; import { getActiveMemorySearchManager } from "../../plugins/memory-runtime.js"; import { emitSessionTranscriptUpdate } from "../../sessions/transcript-events.js"; @@ -52,7 +53,7 @@ async function runPostCompactionSessionMemorySync(params: { sessionFiles: [sessionFile], }); } catch (err) { - log.warn(`memory sync skipped (post-compaction): ${String(err)}`); + log.warn(`memory sync skipped (post-compaction): ${formatErrorMessage(err)}`); } } @@ -192,7 +193,7 @@ export async function runBeforeCompactionHooks(params: { await triggerInternalHook(hookEvent); } catch (err) { log.warn("session:compact:before hook failed", { - errorMessage: err instanceof Error ? err.message : String(err), + errorMessage: formatErrorMessage(err), errorStack: err instanceof Error ? err.stack : undefined, }); } @@ -213,7 +214,7 @@ export async function runBeforeCompactionHooks(params: { ); } catch (err) { log.warn("before_compaction hook failed", { - errorMessage: err instanceof Error ? err.message : String(err), + errorMessage: formatErrorMessage(err), errorStack: err instanceof Error ? err.stack : undefined, }); } @@ -276,7 +277,7 @@ export async function runAfterCompactionHooks(params: { await triggerInternalHook(hookEvent); } catch (err) { log.warn("session:compact:after hook failed", { - errorMessage: err instanceof Error ? err.message : String(err), + errorMessage: formatErrorMessage(err), errorStack: err instanceof Error ? err.stack : undefined, }); } @@ -299,7 +300,7 @@ export async function runAfterCompactionHooks(params: { ); } catch (err) { log.warn("after_compaction hook failed", { - errorMessage: err instanceof Error ? err.message : String(err), + errorMessage: formatErrorMessage(err), errorStack: err instanceof Error ? err.stack : undefined, }); } diff --git a/src/agents/pi-embedded-runner/openrouter-model-capabilities.ts b/src/agents/pi-embedded-runner/openrouter-model-capabilities.ts index 931826ef033..cf55454ee1c 100644 --- a/src/agents/pi-embedded-runner/openrouter-model-capabilities.ts +++ b/src/agents/pi-embedded-runner/openrouter-model-capabilities.ts @@ -21,6 +21,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { resolveStateDir } from "../../config/paths.js"; +import { formatErrorMessage } from "../../infra/errors.js"; import { resolveProxyFetchFromEnv } from "../../infra/net/proxy-fetch.js"; import { createSubsystemLogger } from "../../logging/subsystem.js"; @@ -97,7 +98,7 @@ function writeDiskCache(map: Map): void { }; writeFileSync(resolveDiskCachePath(), JSON.stringify(payload), "utf-8"); } catch (err: unknown) { - const message = err instanceof Error ? err.message : String(err); + const message = formatErrorMessage(err); log.debug(`Failed to write OpenRouter disk cache: ${message}`); } } @@ -212,7 +213,7 @@ async function doFetch(): Promise { writeDiskCache(map); log.debug(`Cached ${map.size} OpenRouter models from API`); } catch (err: unknown) { - const message = err instanceof Error ? err.message : String(err); + const message = formatErrorMessage(err); log.warn(`Failed to fetch OpenRouter models: ${message}`); } finally { clearTimeout(timeout); diff --git a/src/agents/pi-embedded-runner/run/attempt.stop-reason-recovery.ts b/src/agents/pi-embedded-runner/run/attempt.stop-reason-recovery.ts index da20cbe1516..c3437f9cb8a 100644 --- a/src/agents/pi-embedded-runner/run/attempt.stop-reason-recovery.ts +++ b/src/agents/pi-embedded-runner/run/attempt.stop-reason-recovery.ts @@ -1,5 +1,6 @@ import type { StreamFn } from "@mariozechner/pi-agent-core"; import { createAssistantMessageEventStream, streamSimple } from "@mariozechner/pi-ai"; +import { formatErrorMessage } from "../../../infra/errors.js"; import { buildStreamErrorAssistantMessage } from "../../stream-message-shared.js"; const UNHANDLED_STOP_REASON_RE = /^Unhandled stop reason:\s*(.+)$/i; @@ -69,9 +70,7 @@ function wrapStreamHandleUnhandledStopReason( patchUnhandledStopReasonInAssistantMessage(message); return message; } catch (err) { - const normalizedMessage = normalizeUnhandledStopReasonMessage( - err instanceof Error ? err.message : String(err), - ); + const normalizedMessage = normalizeUnhandledStopReasonMessage(formatErrorMessage(err)); if (!normalizedMessage) { throw err; } @@ -105,9 +104,7 @@ function wrapStreamHandleUnhandledStopReason( } return result; } catch (err) { - const normalizedMessage = normalizeUnhandledStopReasonMessage( - err instanceof Error ? err.message : String(err), - ); + const normalizedMessage = normalizeUnhandledStopReasonMessage(formatErrorMessage(err)); if (!normalizedMessage) { throw err; } @@ -152,9 +149,7 @@ export function wrapStreamFnHandleSensitiveStopReason(baseFn: StreamFn): StreamF return Promise.resolve(maybeStream).then( (stream) => wrapStreamHandleUnhandledStopReason(model, stream), (err) => { - const normalizedMessage = normalizeUnhandledStopReasonMessage( - err instanceof Error ? err.message : String(err), - ); + const normalizedMessage = normalizeUnhandledStopReasonMessage(formatErrorMessage(err)); if (!normalizedMessage) { throw err; } @@ -164,9 +159,7 @@ export function wrapStreamFnHandleSensitiveStopReason(baseFn: StreamFn): StreamF } return wrapStreamHandleUnhandledStopReason(model, maybeStream); } catch (err) { - const normalizedMessage = normalizeUnhandledStopReasonMessage( - err instanceof Error ? err.message : String(err), - ); + const normalizedMessage = normalizeUnhandledStopReasonMessage(formatErrorMessage(err)); if (!normalizedMessage) { throw err; } diff --git a/src/agents/pi-embedded-runner/run/images.ts b/src/agents/pi-embedded-runner/run/images.ts index 531f79eafbc..9dfc6daf939 100644 --- a/src/agents/pi-embedded-runner/run/images.ts +++ b/src/agents/pi-embedded-runner/run/images.ts @@ -1,5 +1,6 @@ import path from "node:path"; import type { ImageContent } from "@mariozechner/pi-ai"; +import { formatErrorMessage } from "../../../infra/errors.js"; import { assertNoWindowsNetworkPath, safeFileURLToPath } from "../../../infra/local-file-access.js"; import type { PromptImageOrderEntry } from "../../../media/prompt-image-order.js"; import { resolveMediaBufferPath, getMediaDir } from "../../../media/store.js"; @@ -379,7 +380,7 @@ export async function loadImageFromRef( return { type: "image", data, mimeType }; } catch (err) { log.debug( - `Native image: failed to load media-uri ${ref.resolved}: ${err instanceof Error ? err.message : String(err)}`, + `Native image: failed to load media-uri ${ref.resolved}: ${formatErrorMessage(err)}`, ); return null; } @@ -402,7 +403,7 @@ export async function loadImageFromRef( targetPath = resolved.resolved; } catch (err) { log.debug( - `Native image: sandbox validation failed for ${ref.resolved}: ${err instanceof Error ? err.message : String(err)}`, + `Native image: sandbox validation failed for ${ref.resolved}: ${formatErrorMessage(err)}`, ); return null; } @@ -440,9 +441,7 @@ export async function loadImageFromRef( return { type: "image", data, mimeType }; } catch (err) { // Log the actual error for debugging (size limits, network failures, etc.) - log.debug( - `Native image: failed to load ${ref.resolved}: ${err instanceof Error ? err.message : String(err)}`, - ); + log.debug(`Native image: failed to load ${ref.resolved}: ${formatErrorMessage(err)}`); return null; } } diff --git a/src/agents/pi-embedded-runner/session-truncation.ts b/src/agents/pi-embedded-runner/session-truncation.ts index b3c0be8b941..00886156094 100644 --- a/src/agents/pi-embedded-runner/session-truncation.ts +++ b/src/agents/pi-embedded-runner/session-truncation.ts @@ -6,6 +6,7 @@ import { isHeartbeatOkResponse, isHeartbeatUserMessage, } from "../../auto-reply/heartbeat-filter.js"; +import { formatErrorMessage } from "../../infra/errors.js"; import { log } from "./logger.js"; /** @@ -47,7 +48,7 @@ export async function truncateSessionAfterCompaction(params: { try { sm = SessionManager.open(sessionFile); } catch (err) { - const reason = err instanceof Error ? err.message : String(err); + const reason = formatErrorMessage(err); log.warn(`[session-truncation] Failed to open session file: ${reason}`); return { truncated: false, entriesRemoved: 0, reason }; } @@ -205,7 +206,7 @@ export async function truncateSessionAfterCompaction(params: { await fs.copyFile(sessionFile, params.archivePath); log.info(`[session-truncation] Archived pre-truncation file to ${params.archivePath}`); } catch (err) { - const reason = err instanceof Error ? err.message : String(err); + const reason = formatErrorMessage(err); log.warn(`[session-truncation] Failed to archive: ${reason}`); } } @@ -225,7 +226,7 @@ export async function truncateSessionAfterCompaction(params: { } catch { // Ignore cleanup errors } - const reason = err instanceof Error ? err.message : String(err); + const reason = formatErrorMessage(err); log.warn(`[session-truncation] Failed to write truncated file: ${reason}`); return { truncated: false, entriesRemoved: 0, reason }; } diff --git a/src/agents/pi-embedded-runner/thinking.ts b/src/agents/pi-embedded-runner/thinking.ts index f92cbd7b1bf..037b4675ae4 100644 --- a/src/agents/pi-embedded-runner/thinking.ts +++ b/src/agents/pi-embedded-runner/thinking.ts @@ -1,5 +1,6 @@ import type { AgentMessage, StreamFn } from "@mariozechner/pi-agent-core"; import { createAssistantMessageEventStream } from "@mariozechner/pi-ai"; +import { formatErrorMessage } from "../../infra/errors.js"; import { log } from "./logger.js"; type AssistantContentBlock = Extract["content"][number]; @@ -218,7 +219,7 @@ function shouldRecoverAnthropicThinkingError( error: unknown, sessionMeta: RecoverySessionMeta, ): boolean { - const message = error instanceof Error ? error.message : String(error); + const message = formatErrorMessage(error); if (!THINKING_BLOCK_ERROR_PATTERN.test(message)) { return false; } diff --git a/src/agents/pi-embedded-runner/tool-result-truncation.ts b/src/agents/pi-embedded-runner/tool-result-truncation.ts index 9a87c1104e4..bd7bcf0b5b3 100644 --- a/src/agents/pi-embedded-runner/tool-result-truncation.ts +++ b/src/agents/pi-embedded-runner/tool-result-truncation.ts @@ -1,6 +1,7 @@ import type { AgentMessage } from "@mariozechner/pi-agent-core"; import type { TextContent } from "@mariozechner/pi-ai"; import { SessionManager } from "@mariozechner/pi-coding-agent"; +import { formatErrorMessage } from "../../infra/errors.js"; import { emitSessionTranscriptUpdate } from "../../sessions/transcript-events.js"; import { acquireSessionWriteLock } from "../session-write-lock.js"; import { log } from "./logger.js"; @@ -583,7 +584,7 @@ export function truncateOversizedToolResultsInSessionManager(params: { try { return truncateOversizedToolResultsInExistingSessionManager(params); } catch (err) { - const errMsg = err instanceof Error ? err.message : String(err); + const errMsg = formatErrorMessage(err); log.warn(`[tool-result-truncation] Failed to truncate: ${errMsg}`); return { truncated: false, truncatedCount: 0, reason: errMsg }; } @@ -609,7 +610,7 @@ export async function truncateOversizedToolResultsInSession(params: { sessionKey: params.sessionKey, }); } catch (err) { - const errMsg = err instanceof Error ? err.message : String(err); + const errMsg = formatErrorMessage(err); log.warn(`[tool-result-truncation] Failed to truncate: ${errMsg}`); return { truncated: false, truncatedCount: 0, reason: errMsg }; } finally { diff --git a/src/agents/pi-embedded-runner/transcript-rewrite.ts b/src/agents/pi-embedded-runner/transcript-rewrite.ts index 48d93d445b6..2f173980579 100644 --- a/src/agents/pi-embedded-runner/transcript-rewrite.ts +++ b/src/agents/pi-embedded-runner/transcript-rewrite.ts @@ -5,6 +5,7 @@ import type { TranscriptRewriteRequest, TranscriptRewriteResult, } from "../../context-engine/types.js"; +import { formatErrorMessage } from "../../infra/errors.js"; import { emitSessionTranscriptUpdate } from "../../sessions/transcript-events.js"; import { getRawSessionAppendMessage } from "../session-tool-result-guard.js"; import { acquireSessionWriteLock } from "../session-write-lock.js"; @@ -218,7 +219,7 @@ export async function rewriteTranscriptEntriesInSessionFile(params: { } return result; } catch (err) { - const reason = err instanceof Error ? err.message : String(err); + const reason = formatErrorMessage(err); log.warn(`[transcript-rewrite] failed: ${reason}`); return { changed: false, diff --git a/src/agents/pi-hooks/compaction-safeguard.ts b/src/agents/pi-hooks/compaction-safeguard.ts index 597fe90604a..d1e24d644d6 100644 --- a/src/agents/pi-hooks/compaction-safeguard.ts +++ b/src/agents/pi-hooks/compaction-safeguard.ts @@ -4,6 +4,7 @@ import type { AgentMessage } from "@mariozechner/pi-agent-core"; import type { ExtensionAPI, ExtensionContext, FileOperations } from "@mariozechner/pi-coding-agent"; import { extractSections } from "../../auto-reply/reply/post-compaction-context.js"; import { openBoundaryFile } from "../../infra/boundary-file-read.js"; +import { formatErrorMessage } from "../../infra/errors.js"; import { createSubsystemLogger } from "../../logging/subsystem.js"; import { hasMeaningfulConversationContent, @@ -639,7 +640,7 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void { } requestAuth = await modelRegistry.getApiKeyAndHeaders(model); } catch (err) { - const error = err instanceof Error ? err.message : String(err); + const error = formatErrorMessage(err); log.warn( `Compaction safeguard: request credentials unavailable; cancelling compaction. ${error}`, ); @@ -745,9 +746,9 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void { }); } catch (droppedError) { log.warn( - `Compaction safeguard: failed to summarize dropped messages, continuing without: ${ - droppedError instanceof Error ? droppedError.message : String(droppedError) - }`, + `Compaction safeguard: failed to summarize dropped messages, continuing without: ${formatErrorMessage( + droppedError, + )}`, ); } } @@ -848,9 +849,7 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void { if (lastSuccessfulSummary && attempt > 0) { log.warn( `Compaction safeguard: quality retry failed on attempt ${attempt + 1}; ` + - `keeping last successful summary: ${ - attemptError instanceof Error ? attemptError.message : String(attemptError) - }`, + `keeping last successful summary: ${formatErrorMessage(attemptError)}`, ); summary = lastSuccessfulSummary; break; @@ -927,7 +926,7 @@ export default function compactionSafeguardExtension(api: ExtensionAPI): void { }, }; } catch (error) { - const message = error instanceof Error ? error.message : String(error); + const message = formatErrorMessage(error); log.warn( `Compaction summarization failed; cancelling compaction to preserve history: ${message}`, );