diff --git a/src/agents/model-auth-label.ts b/src/agents/model-auth-label.ts index 26458c71e0a4..a7cd40e36e15 100644 --- a/src/agents/model-auth-label.ts +++ b/src/agents/model-auth-label.ts @@ -20,6 +20,10 @@ import { } from "./model-auth.js"; import { normalizeProviderId } from "./model-selection.js"; +// Builds concise auth labels for UI/status surfaces without exposing credential +// values. Resolution follows profile override, provider profiles, env, CLI, then +// custom provider config. +/** Resolve the display label that describes how a provider is authenticated. */ export function resolveModelAuthLabel(params: { provider?: string; cfg?: OpenClawConfig; @@ -106,6 +110,8 @@ export function resolveModelAuthLabel(params: { return `api-key${label ? ` (${label})` : ""}`; } if (providerEntryProfileRef.kind === "profile-incompatible") { + // Preserve the fact that config pointed at a profile while avoiding a + // misleading auth mode for an incompatible provider/profile pairing. return "unknown"; } diff --git a/src/agents/model-auth-runtime-shared.ts b/src/agents/model-auth-runtime-shared.ts index 613e5144dd02..a9f80c48f6db 100644 --- a/src/agents/model-auth-runtime-shared.ts +++ b/src/agents/model-auth-runtime-shared.ts @@ -1,5 +1,7 @@ import { normalizeSecretInput } from "../utils/normalize-secret-input.js"; +// Shared provider-auth runtime types and errors. Provider calls use these helpers +// to fail with actionable auth provenance while keeping secret normalization local. const AWS_BEARER_ENV = "AWS_BEARER_TOKEN_BEDROCK"; const AWS_ACCESS_KEY_ENV = "AWS_ACCESS_KEY_ID"; const AWS_SECRET_KEY_ENV = "AWS_SECRET_ACCESS_KEY"; @@ -14,6 +16,7 @@ export type ResolvedProviderAuth = { export type ProviderAuthErrorCode = "missing-api-key" | "missing-provider-auth"; +/** Base provider auth error with a stable code for retry/fallback logic. */ export class ProviderAuthError extends Error { readonly code: ProviderAuthErrorCode; readonly provider: string; @@ -26,6 +29,7 @@ export class ProviderAuthError extends Error { } } +/** Auth error raised when a resolved provider auth source lacks usable material. */ export class MissingProviderAuthError extends ProviderAuthError { readonly mode: ResolvedProviderAuth["mode"]; readonly source: string; @@ -38,6 +42,7 @@ export class MissingProviderAuthError extends ProviderAuthError { } } +/** Narrow unknown errors to provider auth errors, optionally by code. */ export function isProviderAuthError( err: unknown, code?: ProviderAuthErrorCode, @@ -45,10 +50,12 @@ export function isProviderAuthError( return err instanceof ProviderAuthError && (!code || err.code === code); } +/** Narrow unknown errors to missing-provider-auth failures. */ export function isMissingProviderAuthError(err: unknown): err is MissingProviderAuthError { return err instanceof MissingProviderAuthError; } +/** Return the AWS credential env var that proves SDK auth is configured. */ export function resolveAwsSdkEnvVarName(env: NodeJS.ProcessEnv = process.env): string | undefined { if (env[AWS_BEARER_ENV]?.trim()) { return AWS_BEARER_ENV; @@ -62,10 +69,12 @@ export function resolveAwsSdkEnvVarName(env: NodeJS.ProcessEnv = process.env): s return undefined; } +/** Format the user-facing missing-auth error from auth provenance. */ export function formatMissingAuthError(auth: ResolvedProviderAuth, provider: string): string { return `No API key resolved for provider "${provider}" (auth mode: ${auth.mode}, checked: ${auth.source}).`; } +/** Require a normalized API key or throw a provider-auth error. */ export function requireApiKey(auth: ResolvedProviderAuth, provider: string): string { const key = normalizeSecretInput(auth.apiKey); if (key) { diff --git a/src/agents/model-fallback-observation.ts b/src/agents/model-fallback-observation.ts index f5173fd854a3..77ea0a1438b7 100644 --- a/src/agents/model-fallback-observation.ts +++ b/src/agents/model-fallback-observation.ts @@ -4,8 +4,11 @@ import { buildTextObservationFields } from "./embedded-agent-error-observation.j import type { FailoverReason } from "./embedded-agent-helpers.js"; import type { FallbackAttempt, ModelCandidate } from "./model-fallback.types.js"; +// Structured logging for model fallback decisions. The log payload carries +// sanitized error observations plus step fields that make fallback chains auditable. const decisionLog = createSubsystemLogger("model-fallback").child("decision"); +/** Return whether fallback decision logging is enabled for warn-level events. */ export function isModelFallbackDecisionLogEnabled(): boolean { return decisionLog.isEnabled("warn"); } @@ -85,6 +88,8 @@ function buildFallbackStepFields(params: { }): ModelFallbackStepFields | undefined { const lastPreviousAttempt = params.previousAttempts?.at(-1); if (params.decision === "candidate_succeeded") { + // Success records the previous failed candidate as the source and the current + // candidate as the successful fallback destination. if (!lastPreviousAttempt) { return undefined; } @@ -120,6 +125,7 @@ function buildFallbackStepFields(params: { }; } +/** Log one model fallback decision and return structured fallback-step fields. */ export function logModelFallbackDecision( params: ModelFallbackDecisionParams, ): ModelFallbackStepFields | undefined { diff --git a/src/agents/tool-allowlist-guard.ts b/src/agents/tool-allowlist-guard.ts index d6ea2ae801a8..0af251a1ec27 100644 --- a/src/agents/tool-allowlist-guard.ts +++ b/src/agents/tool-allowlist-guard.ts @@ -1,12 +1,15 @@ import { normalizeStringEntries } from "@openclaw/normalization-core/string-normalization"; import { normalizeToolList, normalizeToolName } from "./tool-policy.js"; +// Guardrails for explicit tool allowlists. They collect user/operator sources and +// explain when allowlist resolution leaves no callable tools. type ExplicitToolAllowlistSource = { label: string; entries: string[]; enforceWhenToolsDisabled?: boolean; }; +/** Normalize explicit allowlist sources, dropping empty source entries. */ export function collectExplicitToolAllowlistSources( sources: Array<{ label: string; allow?: string[]; enforceWhenToolsDisabled?: boolean }>, ): ExplicitToolAllowlistSource[] { @@ -25,6 +28,7 @@ export function collectExplicitToolAllowlistSources( }); } +/** Build an actionable error when explicit allowlists remove every callable tool. */ export function buildEmptyExplicitToolAllowlistError(params: { sources: ExplicitToolAllowlistSource[]; callableToolNames: string[]; diff --git a/src/agents/trace-base.ts b/src/agents/trace-base.ts index d19b24743118..47671cfe5dbe 100644 --- a/src/agents/trace-base.ts +++ b/src/agents/trace-base.ts @@ -1,3 +1,4 @@ +// Common trace fields copied into provider/model diagnostic events. type AgentTraceBase = { runId?: string; sessionId?: string; @@ -8,6 +9,7 @@ type AgentTraceBase = { workspaceDir?: string; }; +/** Build a trace base object while preserving undefined optional fields. */ export function buildAgentTraceBase(params: AgentTraceBase): AgentTraceBase { return { runId: params.runId, diff --git a/src/agents/transcript-redact.ts b/src/agents/transcript-redact.ts index 119ee6a40bb5..f74f774ea190 100644 --- a/src/agents/transcript-redact.ts +++ b/src/agents/transcript-redact.ts @@ -7,6 +7,9 @@ import { } from "../logging/redact.js"; import type { AgentMessage } from "./runtime/index.js"; +// Transcript redaction helpers for persisted agent messages. Text and structured +// fields share logging redaction config while preserving object identity when no +// redaction changes are needed. function resolveTranscriptRedactPatterns(patterns?: string[]) { return patterns && patterns.length > 0 ? [...patterns, ...getDefaultRedactPatterns()] : undefined; } @@ -79,9 +82,13 @@ function redactTranscriptStructuredValue( return value; } if (seen.has(value)) { + // Avoid recursive transcript payloads from escaping redaction or crashing + // persistence; circular refs serialize as a stable marker. return "[Circular]"; } if (!isPlainTranscriptObject(value)) { + // Non-plain instances can carry runtime state; leave them untouched instead + // of cloning unexpected prototypes into transcripts. return value; } @@ -100,6 +107,7 @@ function redactTranscriptStructuredValue( return next ?? value; } +/** Return a redacted transcript message according to logging config. */ export function redactTranscriptMessage(message: AgentMessage, cfg?: OpenClawConfig): AgentMessage { if (cfg?.logging?.redactSensitive === "off") { return message;