diff --git a/src/gateway/server-close.ts b/src/gateway/server-close.ts index d4ae674fb55c..b55270eaf7d2 100644 --- a/src/gateway/server-close.ts +++ b/src/gateway/server-close.ts @@ -1,3 +1,5 @@ +// Gateway shutdown and restart close orchestration. +// Coordinates hooks, drains, sockets, sidecars, plugins, and runtime cleanup. import type { Server as HttpServer } from "node:http"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import type { WebSocketServer } from "ws"; @@ -18,8 +20,6 @@ import { import type { ChatRunEntry, ChatRunState } from "./server-chat-state.js"; import type { GatewayPostReadySidecarHandle } from "./server-startup-post-attach.js"; -// Gateway shutdown/restart close orchestration with bounded drain and cleanup steps. - const shutdownLog = createSubsystemLogger("gateway/shutdown"); const GATEWAY_SHUTDOWN_HOOK_TIMEOUT_MS = 5_000; const GATEWAY_PRE_RESTART_HOOK_TIMEOUT_MS = 10_000; diff --git a/src/gateway/server-control-ui-root.ts b/src/gateway/server-control-ui-root.ts index a724406fa499..78392545b324 100644 --- a/src/gateway/server-control-ui-root.ts +++ b/src/gateway/server-control-ui-root.ts @@ -1,3 +1,5 @@ +// Gateway Control UI root resolver. +// Finds configured, bundled, or source-built UI assets during startup. import path from "node:path"; import { ensureControlUiAssetsBuilt, @@ -8,9 +10,6 @@ import { import type { RuntimeEnv } from "../runtime.js"; import type { ControlUiRootState } from "./control-ui.js"; -// Control UI root resolution prefers explicit config, then bundled/proven -// assets. Missing bundled assets trigger an async build attempt without blocking -// gateway startup. function startControlUiAssetsBuild(params: { gatewayRuntime: RuntimeEnv; log: { warn: (message: string) => void }; diff --git a/src/gateway/server-cron-lazy.ts b/src/gateway/server-cron-lazy.ts index ff16d64ea9f8..5264327ee416 100644 --- a/src/gateway/server-cron-lazy.ts +++ b/src/gateway/server-cron-lazy.ts @@ -1,11 +1,11 @@ +// Gateway cron lazy loader. +// Defers scheduler startup until cron is touched by runtime or API handlers. import type { CliDeps } from "../cli/deps.types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { CronServiceContract } from "../cron/service-contract.js"; import { resolveCronJobsStorePath } from "../cron/store.js"; import type { GatewayCronState } from "./server-cron.js"; -// Gateway cron is loaded lazily so startup/tests that never touch cron do not -// materialize scheduler loops or bundled plugin runtime. type LazyGatewayCronParams = { cfg: OpenClawConfig; deps: CliDeps; @@ -29,6 +29,8 @@ export function createLazyGatewayCronState(params: LazyGatewayCronParams): Gatew if (loaded) { return loaded; } + // Share the same import promise across concurrent API calls so only one + // scheduler instance is built for a Gateway process. loading ??= import("./server-cron.js").then(({ buildGatewayCronService }) => { loaded = { state: buildGatewayCronService(params), diff --git a/src/gateway/server-maintenance.ts b/src/gateway/server-maintenance.ts index f851d0393ac0..3da978e71b50 100644 --- a/src/gateway/server-maintenance.ts +++ b/src/gateway/server-maintenance.ts @@ -1,3 +1,5 @@ +// Gateway maintenance timers. +// Starts periodic health, dedupe, abort, and media cleanup loops. import { isFutureDateTimestampMs } from "@openclaw/normalization-core/number-coercion"; import type { HealthSummary } from "../commands/health.js"; import { sweepStaleRunContexts } from "../infra/agent-events.js"; @@ -128,6 +130,8 @@ export function startGatewayMaintenanceTimers(params: { return isFutureDateTimestampMs(expiresAtMs, { nowMs: now }); }; const isActiveRunDedupeKey = (key: string, dedupeEntry: DedupeEntry) => { + // Keep idempotency records for active runs so retries cannot create + // duplicate chat/agent work while a command is still draining. if (!key.startsWith("agent:") && !key.startsWith("chat:")) { return false; } diff --git a/src/gateway/server-session-events.ts b/src/gateway/server-session-events.ts index 4dc221e16ff0..7c3ad86e5e7c 100644 --- a/src/gateway/server-session-events.ts +++ b/src/gateway/server-session-events.ts @@ -1,3 +1,5 @@ +// Gateway session event broadcaster. +// Projects transcript and lifecycle updates to websocket subscribers. import { asPositiveSafeInteger } from "@openclaw/normalization-core/number-coercion"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { resolveDefaultAgentId } from "../agents/agent-scope.js"; @@ -20,10 +22,6 @@ import { type GatewaySessionRow, } from "./session-utils.js"; -// Session event broadcasting bridges transcript/lifecycle stores to live -// Gateway websocket subscribers. Message updates go to session-specific -// subscribers plus broad session listeners; non-display messages still trigger -// sessions.changed so lists refresh. type SessionEventSubscribers = Pick; type SessionMessageSubscribers = Pick; diff --git a/src/gateway/server-shared-auth-generation.ts b/src/gateway/server-shared-auth-generation.ts index 064ebc8be722..f828c0f9981a 100644 --- a/src/gateway/server-shared-auth-generation.ts +++ b/src/gateway/server-shared-auth-generation.ts @@ -1,8 +1,8 @@ +// Gateway shared-auth generation enforcement. +// Disconnects clients when config writes invalidate shared credentials. import type { OpenClawConfig } from "../config/types.openclaw.js"; import { resolveGatewayReloadSettings } from "./config-reload-settings.js"; -// Tracks shared Gateway auth generations across config writes and reload modes. - /** Gateway client subset relevant to shared auth generation enforcement. */ export type SharedGatewayAuthClient = { usesSharedGatewayAuth?: boolean; diff --git a/src/gateway/session-transcript-files.fs.ts b/src/gateway/session-transcript-files.fs.ts index 327fb3e95340..45b4469dd7cf 100644 --- a/src/gateway/session-transcript-files.fs.ts +++ b/src/gateway/session-transcript-files.fs.ts @@ -1,3 +1,5 @@ +// Filesystem session transcript helpers. +// Resolves, archives, and cleans up transcript files owned by Gateway sessions. import fs from "node:fs"; import os from "node:os"; import path from "node:path"; @@ -118,6 +120,8 @@ export function resolveSessionTranscriptCandidates( } } + // Keep the legacy global sessions directory as a final candidate so tagged + // upgrades can still find transcripts created before per-agent paths. const home = resolveRequiredHomeDir(process.env, os.homedir); const legacyDir = path.join(home, ".openclaw", "sessions"); pushCandidate(() => resolveSessionTranscriptPathInDir(sessionId, legacyDir)); diff --git a/src/gateway/session-transcript-key.ts b/src/gateway/session-transcript-key.ts index d3f41019092b..c7b15c6c5653 100644 --- a/src/gateway/session-transcript-key.ts +++ b/src/gateway/session-transcript-key.ts @@ -1,3 +1,5 @@ +// Session transcript key resolver. +// Maps transcript file paths back to Gateway session keys for live broadcasts. import { getRuntimeConfig } from "../config/io.js"; import type { SessionEntry } from "../config/sessions/types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; @@ -38,10 +40,12 @@ function sessionKeyMatchesTranscriptPath(params: { ).some((candidate) => resolveTranscriptPathForComparison(candidate) === params.targetPath); } +/** Clears the transcript path lookup cache for isolated tests. */ export function clearSessionTranscriptKeyCacheForTests(): void { TRANSCRIPT_SESSION_KEY_CACHE.clear(); } +/** Resolve the most likely Gateway session key for a transcript file path. */ export function resolveSessionKeyForTranscriptFile(sessionFile: string): string | undefined { const targetPath = resolveTranscriptPathForComparison(sessionFile); if (!targetPath) { @@ -81,6 +85,8 @@ export function resolveSessionKeyForTranscriptFile(sessionFile: string): string } if (matchingEntries.length > 0) { + // Multiple keys can point at copied/forked transcript paths. Prefer the + // freshest unambiguous session ID group; ties stay unresolved. const matchesBySessionId = new Map>(); for (const entry of matchingEntries) { const sessionId = entry[1].sessionId;