diff --git a/src/agents/subagent-registry.ts b/src/agents/subagent-registry.ts index 5d8971f294c..cecd7e5895f 100644 --- a/src/agents/subagent-registry.ts +++ b/src/agents/subagent-registry.ts @@ -5,6 +5,7 @@ import type { ContextEngine, SubagentEndReason } from "../context-engine/types.j import { callGateway } from "../gateway/call.js"; import { onAgentEvent } from "../infra/agent-events.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; +import { importRuntimeModule } from "../shared/runtime-import.js"; import { type DeliveryContext, normalizeDeliveryContext } from "../utils/delivery-context.js"; import type { ensureRuntimePluginsLoaded as ensureRuntimePluginsLoadedFn } from "./runtime-plugins.js"; import type { SubagentRunOutcome } from "./subagent-announce-output.js"; @@ -93,9 +94,30 @@ const defaultSubagentRegistryDeps: SubagentRegistryDeps = { }; let subagentRegistryDeps: SubagentRegistryDeps = defaultSubagentRegistryDeps; -let subagentRegistryRuntimePromise: Promise< - typeof import("./subagent-registry.runtime.js") -> | null = null; +type ContextEngineInitModule = Pick< + { + ensureContextEnginesInitialized: () => void; + }, + "ensureContextEnginesInitialized" +>; +type ContextEngineRegistryModule = Pick< + { + resolveContextEngine: (cfg: OpenClawConfig) => Promise; + }, + "resolveContextEngine" +>; +type RuntimePluginsModule = Pick< + { + ensureRuntimePluginsLoaded: typeof ensureRuntimePluginsLoadedFn; + }, + "ensureRuntimePluginsLoaded" +>; + +const SUBAGENT_REGISTRY_RUNTIME_SPEC = ["./subagent-registry.runtime", ".js"] as const; + +let contextEngineInitPromise: Promise | null = null; +let contextEngineRegistryPromise: Promise | null = null; +let runtimePluginsPromise: Promise | null = null; let sweeper: NodeJS.Timeout | null = null; let sweepInProgress = false; @@ -117,9 +139,28 @@ const SESSION_RUN_TTL_MS = 5 * 60_000; // 5 minutes /** Absolute TTL for orphaned pendingLifecycleError entries. */ const PENDING_ERROR_TTL_MS = 5 * 60_000; // 5 minutes -function loadSubagentRegistryRuntime() { - subagentRegistryRuntimePromise ??= import("./subagent-registry.runtime.js"); - return subagentRegistryRuntimePromise; +function loadContextEngineInitModule(): Promise { + contextEngineInitPromise ??= importRuntimeModule( + import.meta.url, + SUBAGENT_REGISTRY_RUNTIME_SPEC, + ); + return contextEngineInitPromise; +} + +function loadContextEngineRegistryModule(): Promise { + contextEngineRegistryPromise ??= importRuntimeModule( + import.meta.url, + SUBAGENT_REGISTRY_RUNTIME_SPEC, + ); + return contextEngineRegistryPromise; +} + +function loadRuntimePluginsModule(): Promise { + runtimePluginsPromise ??= importRuntimeModule( + import.meta.url, + SUBAGENT_REGISTRY_RUNTIME_SPEC, + ); + return runtimePluginsPromise; } async function ensureSubagentRegistryPluginRuntimeLoaded(params: { @@ -132,16 +173,17 @@ async function ensureSubagentRegistryPluginRuntimeLoaded(params: { ensureRuntimePluginsLoaded(params); return; } - const runtime = await loadSubagentRegistryRuntime(); - runtime.ensureRuntimePluginsLoaded(params); + (await loadRuntimePluginsModule()).ensureRuntimePluginsLoaded(params); } async function resolveSubagentRegistryContextEngine(cfg: OpenClawConfig) { - const runtime = await loadSubagentRegistryRuntime(); + const initModule = await loadContextEngineInitModule(); + const registryModule = await loadContextEngineRegistryModule(); const ensureContextEnginesInitialized = - subagentRegistryDeps.ensureContextEnginesInitialized ?? runtime.ensureContextEnginesInitialized; + subagentRegistryDeps.ensureContextEnginesInitialized ?? + initModule.ensureContextEnginesInitialized; const resolveContextEngine = - subagentRegistryDeps.resolveContextEngine ?? runtime.resolveContextEngine; + subagentRegistryDeps.resolveContextEngine ?? registryModule.resolveContextEngine; ensureContextEnginesInitialized(); return await resolveContextEngine(cfg); } @@ -696,7 +738,9 @@ export function resetSubagentRegistryForTests(opts?: { persist?: boolean }) { resumedRuns.clear(); endedHookInFlightRunIds.clear(); clearAllPendingLifecycleErrors(); - subagentRegistryRuntimePromise = null; + contextEngineInitPromise = null; + contextEngineRegistryPromise = null; + runtimePluginsPromise = null; resetAnnounceQueuesForTests(); stopSweeper(); sweepInProgress = false; diff --git a/src/agents/tools/session-status-tool.ts b/src/agents/tools/session-status-tool.ts index 15a52c819b5..46baa360147 100644 --- a/src/agents/tools/session-status-tool.ts +++ b/src/agents/tools/session-status-tool.ts @@ -21,6 +21,7 @@ import { resolveAgentIdFromSessionKey, } from "../../routing/session-key.js"; import { applyModelOverrideToSessionEntry } from "../../sessions/model-overrides.js"; +import { importRuntimeModule } from "../../shared/runtime-import.js"; import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js"; import { buildTaskStatusSnapshotForRelatedSessionKeyForOwner } from "../../tasks/task-owner-access.js"; import { formatTaskStatusDetail, formatTaskStatusTitle } from "../../tasks/task-status.js"; @@ -54,12 +55,47 @@ const SessionStatusToolSchema = Type.Object({ model: Type.Optional(Type.String()), }); -let commandsStatusRuntimePromise: Promise< - typeof import("../../auto-reply/reply/commands-status.runtime.js") -> | null = null; +type CommandsStatusRuntimeModule = { + buildStatusText: (params: { + cfg: OpenClawConfig; + sessionEntry?: SessionEntry; + sessionKey: string; + parentSessionKey?: string; + sessionScope?: "per-sender" | "per-thread" | "shared"; + storePath?: string; + statusChannel: string; + provider: string; + model: string; + contextTokens?: number; + resolvedThinkLevel?: ThinkLevel; + resolvedFastMode?: boolean; + resolvedVerboseLevel: VerboseLevel; + resolvedReasoningLevel: ReasoningLevel; + resolvedElevatedLevel?: ElevatedLevel; + resolveDefaultThinkingLevel: () => Promise; + isGroup: boolean; + defaultGroupActivation: () => "always" | "mention"; + taskLineOverride?: string; + skipDefaultTaskLookup?: boolean; + primaryModelLabelOverride?: string; + modelAuthOverride?: string; + activeModelAuthOverride?: string; + includeTranscriptUsage?: boolean; + }) => Promise; +}; -function loadCommandsStatusRuntime() { - commandsStatusRuntimePromise ??= import("../../auto-reply/reply/commands-status.runtime.js"); +const COMMANDS_STATUS_RUNTIME_SPEC = [ + "../../auto-reply/reply/commands-status.runtime", + ".js", +] as const; + +let commandsStatusRuntimePromise: Promise | null = null; + +function loadCommandsStatusRuntime(): Promise { + commandsStatusRuntimePromise ??= importRuntimeModule( + import.meta.url, + COMMANDS_STATUS_RUNTIME_SPEC, + ); return commandsStatusRuntimePromise; } diff --git a/src/auto-reply/reply/commands-status-deps.runtime.ts b/src/auto-reply/reply/commands-status-deps.runtime.ts new file mode 100644 index 00000000000..5da38d412ce --- /dev/null +++ b/src/auto-reply/reply/commands-status-deps.runtime.ts @@ -0,0 +1,3 @@ +export { listControlledSubagentRuns } from "../../agents/subagent-control.js"; +export { countPendingDescendantRuns } from "../../agents/subagent-registry.js"; +export { buildSubagentsStatusLine } from "./commands-status-subagents.js"; diff --git a/src/auto-reply/reply/commands-status.ts b/src/auto-reply/reply/commands-status.ts index 7ac0edac40a..5664e858ef1 100644 --- a/src/auto-reply/reply/commands-status.ts +++ b/src/auto-reply/reply/commands-status.ts @@ -7,8 +7,6 @@ import { } from "../../agents/agent-scope.js"; import { resolveFastModeState } from "../../agents/fast-mode.js"; import { resolveModelAuthLabel } from "../../agents/model-auth-label.js"; -import { listControlledSubagentRuns } from "../../agents/subagent-control.js"; -import { countPendingDescendantRuns } from "../../agents/subagent-registry.js"; import { resolveInternalSessionKey, resolveMainSessionAlias, @@ -23,6 +21,7 @@ import { resolveUsageProviderId, } from "../../infra/provider-usage.js"; import type { MediaUnderstandingDecision } from "../../media-understanding/types.js"; +import { importRuntimeModule } from "../../shared/runtime-import.js"; import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js"; import { listTasksForAgentIdForStatus, @@ -35,10 +34,8 @@ import { } from "../../tasks/task-status.js"; import { normalizeGroupActivation } from "../group-activation.js"; import { resolveSelectedAndActiveModel } from "../model-runtime.js"; -import { buildStatusMessage } from "../status.js"; import type { ElevatedLevel, ReasoningLevel, ThinkLevel, VerboseLevel } from "../thinking.js"; import type { ReplyPayload } from "../types.js"; -import { buildSubagentsStatusLine } from "./commands-status-subagents.js"; import type { CommandContext } from "./commands-types.js"; import { getFollowupQueueDepth, resolveQueueSettings } from "./queue.js"; @@ -51,6 +48,43 @@ const USAGE_OAUTH_ONLY_PROVIDERS = new Set([ "openai-codex", ]); +type StatusRuntimeModule = { + buildStatusMessage: (args: Record) => string; +}; +type CommandsStatusSubagentsModule = { + buildSubagentsStatusLine: (params: { + runs: Array<{ childSessionKey: string; endedAt?: number | null }>; + verboseEnabled: boolean; + pendingDescendantsForRun: (entry: { childSessionKey: string }) => number; + }) => string | undefined; + countPendingDescendantRuns: (rootSessionKey: string) => number; + listControlledSubagentRuns: ( + controllerSessionKey: string, + ) => Array<{ childSessionKey: string; endedAt?: number | null }>; +}; + +const STATUS_RUNTIME_SPEC = ["../status.runtime", ".js"] as const; +const COMMANDS_STATUS_DEPS_RUNTIME_SPEC = ["./commands-status-deps.runtime", ".js"] as const; + +let statusRuntimePromise: Promise | null = null; +let commandsStatusDepsRuntimePromise: Promise | null = null; + +function loadStatusRuntime(): Promise { + statusRuntimePromise ??= importRuntimeModule( + import.meta.url, + STATUS_RUNTIME_SPEC, + ); + return statusRuntimePromise; +} + +function loadCommandsStatusDepsRuntime(): Promise { + commandsStatusDepsRuntimePromise ??= importRuntimeModule( + import.meta.url, + COMMANDS_STATUS_DEPS_RUNTIME_SPEC, + ); + return commandsStatusDepsRuntimePromise; +} + function shouldLoadUsageSummary(params: { provider?: string; selectedModelAuth?: string; @@ -274,6 +308,8 @@ export async function buildStatusText(params: { if (!taskLine && !params.skipDefaultTaskLookup) { taskLine = formatAgentTaskCountsLine(statusAgentId); } + const { buildSubagentsStatusLine, countPendingDescendantRuns, listControlledSubagentRuns } = + await loadCommandsStatusDepsRuntime(); const runs = listControlledSubagentRuns(requesterKey); const verboseEnabled = resolvedVerboseLevel && resolvedVerboseLevel !== "off"; subagentsLine = buildSubagentsStatusLine({ @@ -297,6 +333,7 @@ export async function buildStatusText(params: { sessionEntry, }).enabled; const agentFallbacksOverride = resolveAgentModelFallbacksOverride(cfg, statusAgentId); + const { buildStatusMessage } = await loadStatusRuntime(); const statusText = buildStatusMessage({ config: cfg, agent: { diff --git a/src/auto-reply/status.runtime.ts b/src/auto-reply/status.runtime.ts new file mode 100644 index 00000000000..7350c0b7912 --- /dev/null +++ b/src/auto-reply/status.runtime.ts @@ -0,0 +1 @@ +export { buildStatusMessage } from "./status.js"; diff --git a/src/context-engine/delegate.ts b/src/context-engine/delegate.ts index bd4b6cd965c..0a2df0c4120 100644 --- a/src/context-engine/delegate.ts +++ b/src/context-engine/delegate.ts @@ -1,8 +1,36 @@ import { normalizeStructuredPromptSection } from "../agents/prompt-cache-stability.js"; import type { MemoryCitationsMode } from "../config/types.memory.js"; import { buildMemoryPromptSection } from "../plugins/memory-state.js"; +import { importRuntimeModule } from "../shared/runtime-import.js"; import type { ContextEngine, CompactResult, ContextEngineRuntimeContext } from "./types.js"; +type CompactRuntimeModule = { + compactEmbeddedPiSessionDirect: (params: Record) => Promise<{ + ok: boolean; + compacted: boolean; + reason?: string; + result?: { + summary?: string; + firstKeptEntryId?: string; + tokensBefore?: number; + tokensAfter?: number; + details?: unknown; + }; + }>; +}; + +const COMPACT_RUNTIME_SPEC = ["../agents/pi-embedded-runner/compact.runtime", ".js"] as const; + +let compactRuntimePromise: Promise | null = null; + +function loadCompactRuntime(): Promise { + compactRuntimePromise ??= importRuntimeModule( + import.meta.url, + COMPACT_RUNTIME_SPEC, + ); + return compactRuntimePromise; +} + /** * Delegate a context-engine compaction request to OpenClaw's built-in runtime compaction path. * @@ -19,9 +47,9 @@ import type { ContextEngine, CompactResult, ContextEngineRuntimeContext } from " export async function delegateCompactionToRuntime( params: Parameters[0], ): Promise { - // Import through a dedicated runtime boundary so the lazy edge remains effective. - const { compactEmbeddedPiSessionDirect } = - await import("../agents/pi-embedded-runner/compact.runtime.js"); + // Load through the dedicated runtime boundary without introducing another + // source-level static edge into the embedded runner graph. + const { compactEmbeddedPiSessionDirect } = await loadCompactRuntime(); type RuntimeCompactionParams = Parameters[0]; // runtimeContext carries the full CompactEmbeddedPiSessionParams fields set diff --git a/src/shared/runtime-import.ts b/src/shared/runtime-import.ts new file mode 100644 index 00000000000..4d38cb7cf0a --- /dev/null +++ b/src/shared/runtime-import.ts @@ -0,0 +1,6 @@ +export async function importRuntimeModule( + baseUrl: string, + parts: readonly string[], +): Promise { + return (await import(new URL(parts.join(""), baseUrl).href)) as T; +}