diff --git a/src/gateway/connection-details.ts b/src/gateway/connection-details.ts index 9ee4095f1fac..a758575c1678 100644 --- a/src/gateway/connection-details.ts +++ b/src/gateway/connection-details.ts @@ -1,9 +1,11 @@ +// Gateway connection detail builder for CLI/user-facing target diagnostics. import { redactSensitiveUrlLikeString } from "@openclaw/net-policy/redact-sensitive-url"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { resolveConfigPath, resolveGatewayPort } from "../config/paths.js"; import type { OpenClawConfig } from "../config/types.js"; import { isSecureWebSocketUrl } from "./net.js"; +/** Resolved gateway target plus redacted display text for diagnostics. */ export type GatewayConnectionDetails = { url: string; urlSource: string; @@ -18,6 +20,7 @@ type GatewayConnectionDetailResolvers = { resolveGatewayPort?: (cfg?: OpenClawConfig, env?: NodeJS.ProcessEnv) => number; }; +/** Build gateway target details and reject unsafe remote plaintext websocket URLs. */ export function buildGatewayConnectionDetailsWithResolvers( options: { config?: OpenClawConfig; diff --git a/src/gateway/mcp-http.loopback-runtime.ts b/src/gateway/mcp-http.loopback-runtime.ts index ccd6359549e7..cea42c1ee379 100644 --- a/src/gateway/mcp-http.loopback-runtime.ts +++ b/src/gateway/mcp-http.loopback-runtime.ts @@ -1,3 +1,4 @@ +// Process-local MCP loopback runtime state for owner/non-owner HTTP access. type McpLoopbackRuntime = { port: number; ownerToken: string; @@ -6,14 +7,17 @@ type McpLoopbackRuntime = { let activeRuntime: McpLoopbackRuntime | undefined; +/** Return a copy of the active loopback runtime, if one has been installed. */ export function getActiveMcpLoopbackRuntime(): McpLoopbackRuntime | undefined { return activeRuntime ? { ...activeRuntime } : undefined; } +/** Install the active loopback runtime used by in-process MCP callers. */ export function setActiveMcpLoopbackRuntime(runtime: McpLoopbackRuntime): void { activeRuntime = { ...runtime }; } +/** Choose the bearer token matching owner/non-owner caller identity. */ export function resolveMcpLoopbackBearerToken( runtime: McpLoopbackRuntime, senderIsOwner: boolean, @@ -21,12 +25,14 @@ export function resolveMcpLoopbackBearerToken( return senderIsOwner ? runtime.ownerToken : runtime.nonOwnerToken; } +/** Clear loopback runtime only when the owning token matches the active runtime. */ export function clearActiveMcpLoopbackRuntimeByOwnerToken(ownerToken: string): void { if (activeRuntime?.ownerToken === ownerToken) { activeRuntime = undefined; } } +/** Build the MCP server config injected into agents for loopback tool access. */ export function createMcpLoopbackServerConfig(port: number) { return { mcpServers: { diff --git a/src/gateway/models-http.ts b/src/gateway/models-http.ts index 898edb959a82..7e14a9b308ea 100644 --- a/src/gateway/models-http.ts +++ b/src/gateway/models-http.ts @@ -1,3 +1,4 @@ +// OpenAI-compatible `/v1/models` HTTP route backed by configured OpenClaw agents. import type { IncomingMessage, ServerResponse } from "node:http"; import { listAgentIds, resolveDefaultAgentId } from "../agents/agent-scope.js"; import { getRuntimeConfig } from "../config/io.js"; @@ -74,6 +75,7 @@ function resolveRequestPath(req: IncomingMessage): string { return new URL(req.url ?? "/", "http://localhost").pathname; } +/** Handle OpenAI-compatible model list/detail requests, returning false for unrelated paths. */ export async function handleOpenAiModelsHttpRequest( req: IncomingMessage, res: ServerResponse, diff --git a/src/gateway/plugin-node-capability.ts b/src/gateway/plugin-node-capability.ts index 138d64b9ec85..f6704a3be820 100644 --- a/src/gateway/plugin-node-capability.ts +++ b/src/gateway/plugin-node-capability.ts @@ -1,3 +1,4 @@ +// Capability-token helpers for plugin-hosted node surfaces. import { randomBytes } from "node:crypto"; import { asDateTimestampMs, @@ -7,22 +8,27 @@ import { } from "@openclaw/normalization-core/number-coercion"; import { safeEqualSecret } from "../security/secret-equal.js"; +/** Path marker used to scope plugin-hosted node URLs with one-time capabilities. */ export const PLUGIN_NODE_CAPABILITY_PATH_PREFIX = "/__openclaw__/cap"; const PLUGIN_NODE_CAPABILITY_QUERY_PARAM = "oc_cap"; +/** Default lifetime for plugin-node capability tokens. */ export const DEFAULT_PLUGIN_NODE_CAPABILITY_TTL_MS = 10 * 60_000; +/** Declared plugin surface that may receive scoped node capabilities. */ export type PluginNodeCapabilitySurface = { surface: string; ttlMs?: number; scopeKey?: string; }; +/** Client-side storage for surface URLs and minted plugin-node capabilities. */ export type PluginNodeCapabilityClient = { pluginSurfaceUrls?: Record; pluginNodeCapabilitySurfaces?: Record; pluginNodeCapabilities?: Record; }; +/** Index surfaces by normalized surface id, keeping the strictest TTL per surface. */ export function indexPluginNodeCapabilitySurfaces( surfaces: readonly PluginNodeCapabilitySurface[], ): Record { @@ -44,6 +50,7 @@ export function indexPluginNodeCapabilitySurfaces( return indexed; } +/** Parsed URL details after extracting path/query capability tokens. */ export type NormalizedPluginNodeCapabilityUrl = { pathname: string; capability?: string; @@ -71,10 +78,12 @@ function resolvePluginNodeCapabilityStorageKey(surface: PluginNodeCapabilitySurf return scopeKey ? `${normalizedSurface}\0${scopeKey}` : normalizedSurface; } +/** Resolve a positive TTL for a plugin-node capability surface. */ export function resolvePluginNodeCapabilityTtlMs(surface: PluginNodeCapabilitySurface) { return asPositiveSafeInteger(surface.ttlMs) ?? DEFAULT_PLUGIN_NODE_CAPABILITY_TTL_MS; } +/** Resolve the expiration timestamp for a capability minted against a surface. */ export function resolvePluginNodeCapabilityExpiresAtMs( surface: PluginNodeCapabilitySurface, nowMs: number = Date.now(), @@ -82,10 +91,12 @@ export function resolvePluginNodeCapabilityExpiresAtMs( return resolveExpiresAtMsFromDurationMs(resolvePluginNodeCapabilityTtlMs(surface), { nowMs }); } +/** Mint an opaque capability token for plugin-node surface access. */ export function mintPluginNodeCapabilityToken(): string { return randomBytes(18).toString("base64url"); } +/** Append a capability path segment to a plugin host URL. */ export function buildPluginNodeCapabilityScopedHostUrl( baseUrl: string, capability: string, @@ -107,6 +118,7 @@ export function buildPluginNodeCapabilityScopedHostUrl( } } +/** Replace the capability segment in an already scoped host URL. */ export function replacePluginNodeCapabilityInScopedHostUrl( scopedUrl: string, capability: string, @@ -140,6 +152,7 @@ export function replacePluginNodeCapabilityInScopedHostUrl( } } +/** Parse and rewrite scoped capability URLs into canonical paths plus query tokens. */ export function normalizePluginNodeCapabilityScopedUrl( rawUrl: string, ): NormalizedPluginNodeCapabilityUrl { @@ -199,6 +212,7 @@ export function normalizePluginNodeCapabilityScopedUrl( }; } +/** Store a minted capability on a client under the surface/scope storage key. */ export function setClientPluginNodeCapability(params: { client: PluginNodeCapabilityClient; surface: PluginNodeCapabilitySurface; diff --git a/src/gateway/server-runtime-subscriptions.ts b/src/gateway/server-runtime-subscriptions.ts index 4f55f50d55ef..730051f20edc 100644 --- a/src/gateway/server-runtime-subscriptions.ts +++ b/src/gateway/server-runtime-subscriptions.ts @@ -1,3 +1,4 @@ +// Gateway event subscription wiring for agent, heartbeat, transcript, and lifecycle broadcasts. import { clearAgentRunContext, onAgentEvent } from "../infra/agent-events.js"; import { onHeartbeatEvent } from "../infra/heartbeat-events.js"; import { onSessionLifecycleEvent } from "../sessions/session-lifecycle-events.js"; @@ -10,6 +11,7 @@ import type { ToolEventRecipientRegistry, } from "./server-chat-state.js"; +/** Register gateway runtime event subscriptions and return unsubscribe handles. */ export function startGatewayEventSubscriptions(params: { broadcast: (event: string, payload: unknown, opts?: { dropIfSlow?: boolean }) => void; broadcastToConnIds: ( @@ -30,6 +32,7 @@ export function startGatewayEventSubscriptions(params: { ReturnType > | null = null; const getAgentEventHandler = () => { + // Lazy-load heavy chat modules only after the first agent event reaches the gateway. agentEventHandlerPromise ??= Promise.all([ import("./server-chat.js"), import("./server-session-key.js"), diff --git a/src/gateway/server/http-listen.ts b/src/gateway/server/http-listen.ts index 42ddfaaa6cf4..68bd1f5a2a33 100644 --- a/src/gateway/server/http-listen.ts +++ b/src/gateway/server/http-listen.ts @@ -1,3 +1,4 @@ +// Gateway HTTP server listen helper with retry and lock-aware errors. import type { Server as HttpServer } from "node:http"; import { GatewayLockError } from "../../infra/gateway-lock.js"; import { sleep } from "../../utils.js"; @@ -15,6 +16,7 @@ async function closeServerQuietly(httpServer: HttpServer): Promise { }); } +/** Listen on the configured gateway host/port, retrying transient EADDRINUSE windows. */ export async function listenGatewayHttpServer(params: { httpServer: HttpServer; bindHost: string; diff --git a/src/gateway/server/readiness.ts b/src/gateway/server/readiness.ts index e0127c22385c..642bce988b34 100644 --- a/src/gateway/server/readiness.ts +++ b/src/gateway/server/readiness.ts @@ -1,3 +1,4 @@ +// Gateway readiness checker for channel health and startup sidecar state. import type { ChannelAccountSnapshot } from "../../channels/plugins/types.public.js"; import { DEFAULT_CHANNEL_CONNECT_GRACE_MS, @@ -9,6 +10,7 @@ import { import type { ChannelManager } from "../server-channels.js"; import type { GatewayEventLoopHealth } from "./event-loop-health.js"; +/** Snapshot returned by the gateway readiness probe. */ export type ReadinessResult = { ready: boolean; failing: string[]; @@ -16,6 +18,7 @@ export type ReadinessResult = { eventLoop?: GatewayEventLoopHealth; }; +/** Function form used by HTTP readiness endpoints and tests. */ export type ReadinessChecker = () => ReadinessResult; const DEFAULT_READINESS_CACHE_TTL_MS = 1_000; @@ -33,6 +36,7 @@ function shouldIgnoreReadinessFailure( return health.reason === "not-running" && accountSnapshot.restartPending === true; } +/** Create a cached readiness checker over channel runtime health. */ export function createReadinessChecker(deps: { channelManager: ChannelManager; startedAt: number; diff --git a/src/gateway/session-store-key.ts b/src/gateway/session-store-key.ts index 6741e3c7fca9..9f59685e3354 100644 --- a/src/gateway/session-store-key.ts +++ b/src/gateway/session-store-key.ts @@ -1,3 +1,4 @@ +// Session-store key canonicalization across default agents, main aliases, and legacy keys. import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, @@ -17,6 +18,7 @@ import { } from "../routing/session-key.js"; import { normalizeSessionKeyPreservingOpaquePeerIds } from "../sessions/session-key-utils.js"; +/** Canonicalize an opaque session key into the agent-scoped store namespace. */ export function canonicalizeSessionKeyForAgent(agentId: string, key: string): string { const lowered = normalizeLowercaseStringOrEmpty(key); if (lowered === "global" || lowered === "unknown") { @@ -68,6 +70,7 @@ function resolveParsedSessionStoreKey( return { agentId, sessionKey: `agent:${agentId}:${rest}` }; } +/** Resolve any incoming session key into the canonical key used in persisted session stores. */ export function resolveSessionStoreKey(params: { cfg: OpenClawConfig; sessionKey: string; @@ -107,6 +110,7 @@ export function resolveSessionStoreKey(params: { return canonicalizeSessionKeyForAgent(agentId, raw); } +/** Resolve the agent that owns a canonical session-store key. */ export function resolveSessionStoreAgentId(cfg: OpenClawConfig, canonicalKey: string): string { if (canonicalKey === "global" || canonicalKey === "unknown") { return resolveDefaultStoreAgentId(cfg); @@ -118,6 +122,7 @@ export function resolveSessionStoreAgentId(cfg: OpenClawConfig, canonicalKey: st return resolveDefaultStoreAgentId(cfg); } +/** Resolve a session key for lookup inside a specific agent's store. */ export function resolveStoredSessionKeyForAgentStore(params: { cfg: OpenClawConfig; agentId: string; @@ -139,6 +144,7 @@ export function resolveStoredSessionKeyForAgentStore(params: { }); } +/** Resolve the owner agent for a stored session key, returning null for global/unknown keys. */ export function resolveStoredSessionOwnerAgentId(params: { cfg: OpenClawConfig; agentId: string; @@ -151,6 +157,7 @@ export function resolveStoredSessionOwnerAgentId(params: { return resolveSessionStoreAgentId(params.cfg, canonicalKey); } +/** Canonicalize spawned-by parent references while preserving main-session aliases. */ export function canonicalizeSpawnedByForAgent( cfg: OpenClawConfig, agentId: string, diff --git a/src/gateway/sessions-patch.ts b/src/gateway/sessions-patch.ts index 202450f953dd..8936c11d0f36 100644 --- a/src/gateway/sessions-patch.ts +++ b/src/gateway/sessions-patch.ts @@ -1,3 +1,4 @@ +// Session patch applier for gateway session metadata and model/runtime overrides. import { randomUUID } from "node:crypto"; import { normalizeOptionalLowercaseString, @@ -129,6 +130,7 @@ function normalizeSubagentControlScope(raw: string): "children" | "none" | undef return undefined; } +/** Apply a validated gateway session patch to an in-memory session store entry. */ export async function applySessionsPatchToStore(params: { cfg: OpenClawConfig; store: Record; @@ -161,6 +163,7 @@ export async function applySessionsPatchToStore(params: { }; const existing = store[storeKey]; + // Existing entries without session ids are placeholder aliases; assigning an id makes them real. const next: SessionEntry = existing?.sessionId ? { ...existing, diff --git a/src/gateway/tools-invoke-http.ts b/src/gateway/tools-invoke-http.ts index ff9db4b4713b..20b5b5b5f3d9 100644 --- a/src/gateway/tools-invoke-http.ts +++ b/src/gateway/tools-invoke-http.ts @@ -1,3 +1,4 @@ +// HTTP endpoint adapter for invoking gateway tools from OpenAI-compatible clients. import type { IncomingMessage, ServerResponse } from "node:http"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; import { normalizeMessageChannel } from "../utils/message-channel.js"; @@ -14,6 +15,7 @@ import { invokeGatewayTool, type ToolsInvokeInput } from "./tools-invoke-shared. const DEFAULT_BODY_BYTES = 2 * 1024 * 1024; +/** Handle `/tools/invoke` requests and return false when another HTTP route should handle them. */ export async function handleToolsInvokeHttpRequest( req: IncomingMessage, res: ServerResponse,