From c8ac4c8aeadabfaeaf62878a2e32827f02fc1e38 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 4 Jun 2026 17:30:08 -0400 Subject: [PATCH] docs: document gateway reload helpers --- src/gateway/config-reload-plan.ts | 4 ++++ src/gateway/config-reload.ts | 4 ++++ src/gateway/credential-planner.ts | 4 ++++ src/gateway/exec-approval-ios-push.ts | 4 ++++ src/gateway/exec-approval-manager.ts | 4 ++++ src/gateway/model-pricing-cache-state.ts | 4 ++++ src/gateway/model-pricing-cache.ts | 9 ++++++++- src/gateway/server-reload-handlers.ts | 4 ++++ 8 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/gateway/config-reload-plan.ts b/src/gateway/config-reload-plan.ts index b93ff9a78367..c1ddfccf65c9 100644 --- a/src/gateway/config-reload-plan.ts +++ b/src/gateway/config-reload-plan.ts @@ -1,3 +1,5 @@ +// Gateway config reload planner. +// Maps changed config paths to hot-reload actions, no-ops, or full restarts. import { type ChannelId, listChannelPlugins } from "../channels/plugins/index.js"; import { getActivePluginChannelRegistryVersion, @@ -146,6 +148,8 @@ function listReloadRules(): ReloadRule[] { const registry = getActivePluginRegistry(); const activeRegistryVersion = getActivePluginRegistryVersion(); const channelRegistryVersion = getActivePluginChannelRegistryVersion(); + // Plugin/channel reload rules are process-stable until the active registry + // version changes; cache them to keep every config diff cheap. if ( registry !== cachedRegistry || activeRegistryVersion !== cachedActiveRegistryVersion || diff --git a/src/gateway/config-reload.ts b/src/gateway/config-reload.ts index 9da71bc72eb0..26d235166eeb 100644 --- a/src/gateway/config-reload.ts +++ b/src/gateway/config-reload.ts @@ -1,3 +1,5 @@ +// Gateway config hot-reload watcher. +// Diffs config/plugin install snapshots and dispatches hot reload or restart plans. import chokidar from "chokidar"; import type { ConfigWriteNotification } from "../config/io.js"; import { formatConfigIssueLines } from "../config/issue-format.js"; @@ -126,6 +128,8 @@ export function startGatewayConfigReloader(opts: { if (stopped) { return; } + // Coalesce filesystem/write-listener bursts into one reload pass. Config + // writes often touch temp and final paths in quick succession. if (debounceTimer) { clearTimeout(debounceTimer); } diff --git a/src/gateway/credential-planner.ts b/src/gateway/credential-planner.ts index bf73b83cd5be..078e166570c1 100644 --- a/src/gateway/credential-planner.ts +++ b/src/gateway/credential-planner.ts @@ -1,3 +1,5 @@ +// Gateway credential planning helpers. +// Classifies local/remote auth inputs before SecretRef resolution. import { normalizeOptionalString } from "../../packages/normalization-core/src/string-coerce.js"; import { containsEnvVarReference } from "../config/env-substitution.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; @@ -145,6 +147,8 @@ export function createGatewayCredentialPlan(params: { const remoteUrlConfigured = Boolean(trimToUndefined(remote?.url)); const tailscaleRemoteExposure = gateway?.tailscale?.mode === "serve" || gateway?.tailscale?.mode === "funnel"; + // Remote credential surfaces are considered active when the gateway is used + // remotely or when local auth may be borrowed for a published Tailscale URL. const remoteConfiguredSurface = remoteMode || remoteUrlConfigured || tailscaleRemoteExposure; // Remote credentials may borrow local auth credentials only when the remote // surface exists but no explicit remote/env candidate can satisfy the mode. diff --git a/src/gateway/exec-approval-ios-push.ts b/src/gateway/exec-approval-ios-push.ts index b8cc9ea92273..0abce032cd84 100644 --- a/src/gateway/exec-approval-ios-push.ts +++ b/src/gateway/exec-approval-ios-push.ts @@ -1,3 +1,5 @@ +// Gateway iOS exec-approval push delivery. +// Sends APNs request/resolution wakes to paired operator devices. import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce"; import { getRuntimeConfig } from "../config/io.js"; import { @@ -149,6 +151,8 @@ async function resolveDeliveryPlan(params: { isTargetVisible?: (target: ApprovalPushTarget) => boolean; log: GatewayLikeLogger; }): Promise { + // Request delivery requires current approval scope; resolution delivery may + // target prior node ids so existing notification badges can be cleared. const targets = params.explicitNodeIds?.length ? await loadRegisteredTargets({ deviceIds: params.explicitNodeIds }) : await resolvePairedTargets({ diff --git a/src/gateway/exec-approval-manager.ts b/src/gateway/exec-approval-manager.ts index a4500ba8489e..2e849193797f 100644 --- a/src/gateway/exec-approval-manager.ts +++ b/src/gateway/exec-approval-manager.ts @@ -1,3 +1,5 @@ +// Gateway exec approval manager. +// Tracks pending operator decisions and short-lived resolved approval records. import { randomUUID } from "node:crypto"; import { resolveExpiresAtMsFromDurationMs } from "@openclaw/normalization-core/number-coercion"; import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; @@ -18,6 +20,8 @@ function unrefTimer(timer: ReturnType): void { } function scheduleResolvedEntryCleanup(cleanup: () => void): void { + // Resolved approvals stay visible briefly so node.invoke sanitizers can + // consume a just-approved id after the UI decision races the command retry. const timer = setTimeout(cleanup, RESOLVED_ENTRY_GRACE_MS); unrefTimer(timer); } diff --git a/src/gateway/model-pricing-cache-state.ts b/src/gateway/model-pricing-cache-state.ts index 0a684fbe5aa9..2a42153c0a77 100644 --- a/src/gateway/model-pricing-cache-state.ts +++ b/src/gateway/model-pricing-cache-state.ts @@ -1,3 +1,5 @@ +// Gateway model-pricing cache state. +// Stores normalized pricing rows and source-health failures for runtime reads. import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce"; import { normalizeModelRef } from "../agents/model-selection.js"; @@ -42,6 +44,8 @@ const sourceFailures = new Map< >(); function modelPricingCacheKey(provider: string, model: string): string { + // Keys accept both provider/model and provider-prefixed model ids so external + // catalogs can be queried without double-prefixing. const providerId = normalizeProviderId(provider); const modelId = model.trim(); if (!providerId || !modelId) { diff --git a/src/gateway/model-pricing-cache.ts b/src/gateway/model-pricing-cache.ts index 27acbc2b8d58..107a29e62f78 100644 --- a/src/gateway/model-pricing-cache.ts +++ b/src/gateway/model-pricing-cache.ts @@ -1,4 +1,10 @@ +// Gateway model-pricing refresh and normalization. +// Fetches, normalizes, and schedules cached pricing for model usage estimates. import type { ModelCatalogCost } from "@openclaw/model-catalog-core/model-catalog-types"; +import { + normalizeOptionalString, + resolvePrimaryStringValue, +} from "../../packages/normalization-core/src/string-coerce.js"; import { DEFAULT_PROVIDER } from "../agents/defaults.js"; import { buildModelAliasIndex, @@ -26,7 +32,6 @@ import { } from "../plugins/plugin-metadata-snapshot.js"; import type { PluginMetadataRegistryView } from "../plugins/plugin-metadata-snapshot.types.js"; import type { PluginRegistrySnapshot } from "../plugins/plugin-registry.js"; -import { normalizeOptionalString, resolvePrimaryStringValue } from "../../packages/normalization-core/src/string-coerce.js"; import { clearGatewayModelPricingCacheState, clearGatewayModelPricingFailures, @@ -180,6 +185,8 @@ function isTimeoutError(error: unknown): boolean { } function createPricingFetchSignal(signal: AbortSignal | undefined): AbortSignal { + // Pricing fetches are background refreshes; bound them so startup/reload + // cannot leave an unbounded network request alive. const timeoutSignal = AbortSignal.timeout(FETCH_TIMEOUT_MS); return signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal; } diff --git a/src/gateway/server-reload-handlers.ts b/src/gateway/server-reload-handlers.ts index c2d674a3c3d3..61666d7e71d1 100644 --- a/src/gateway/server-reload-handlers.ts +++ b/src/gateway/server-reload-handlers.ts @@ -1,3 +1,5 @@ +// Gateway hot-reload handlers. +// Applies config reload plans to hooks, cron, heartbeat, plugins, channels, and restarts. import { disposeAllSessionMcpRuntimes } from "../agents/agent-bundle-mcp-tools.js"; import { getActiveEmbeddedRunCount, @@ -102,6 +104,8 @@ async function disposeMcpRuntimesWithTimeout(params: { onWarn: (message: string) => void; label: string; }) { + // MCP runtime disposal may need async provider cleanup. Bound it so config + // reload can proceed and report the stale runtime risk. let timer: ReturnType | undefined; const disposePromise = params.dispose().catch((error: unknown) => { params.onWarn(`${params.label} failed: ${String(error)}`);