docs: document gateway shutdown session helpers

This commit is contained in:
Peter Steinberger
2026-06-04 17:34:36 -04:00
parent 861bf541c2
commit aa9cc80060
8 changed files with 26 additions and 13 deletions
+2 -2
View File
@@ -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;
+2 -3
View File
@@ -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 };
+4 -2
View File
@@ -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),
+4
View File
@@ -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;
}
+2 -4
View File
@@ -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<SessionEventSubscriberRegistry, "getAll">;
type SessionMessageSubscribers = Pick<SessionMessageSubscriberRegistry, "get">;
+2 -2
View File
@@ -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;
@@ -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));
+6
View File
@@ -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<string, Array<[string, SessionEntry]>>();
for (const entry of matchingEntries) {
const sessionId = entry[1].sessionId;