mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:50:43 +00:00
fix(runtime): hide lazy command and context seams from static graph
This commit is contained in:
@@ -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<ContextEngine>;
|
||||
},
|
||||
"resolveContextEngine"
|
||||
>;
|
||||
type RuntimePluginsModule = Pick<
|
||||
{
|
||||
ensureRuntimePluginsLoaded: typeof ensureRuntimePluginsLoadedFn;
|
||||
},
|
||||
"ensureRuntimePluginsLoaded"
|
||||
>;
|
||||
|
||||
const SUBAGENT_REGISTRY_RUNTIME_SPEC = ["./subagent-registry.runtime", ".js"] as const;
|
||||
|
||||
let contextEngineInitPromise: Promise<ContextEngineInitModule> | null = null;
|
||||
let contextEngineRegistryPromise: Promise<ContextEngineRegistryModule> | null = null;
|
||||
let runtimePluginsPromise: Promise<RuntimePluginsModule> | 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<ContextEngineInitModule> {
|
||||
contextEngineInitPromise ??= importRuntimeModule<ContextEngineInitModule>(
|
||||
import.meta.url,
|
||||
SUBAGENT_REGISTRY_RUNTIME_SPEC,
|
||||
);
|
||||
return contextEngineInitPromise;
|
||||
}
|
||||
|
||||
function loadContextEngineRegistryModule(): Promise<ContextEngineRegistryModule> {
|
||||
contextEngineRegistryPromise ??= importRuntimeModule<ContextEngineRegistryModule>(
|
||||
import.meta.url,
|
||||
SUBAGENT_REGISTRY_RUNTIME_SPEC,
|
||||
);
|
||||
return contextEngineRegistryPromise;
|
||||
}
|
||||
|
||||
function loadRuntimePluginsModule(): Promise<RuntimePluginsModule> {
|
||||
runtimePluginsPromise ??= importRuntimeModule<RuntimePluginsModule>(
|
||||
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;
|
||||
|
||||
@@ -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<ThinkLevel | undefined>;
|
||||
isGroup: boolean;
|
||||
defaultGroupActivation: () => "always" | "mention";
|
||||
taskLineOverride?: string;
|
||||
skipDefaultTaskLookup?: boolean;
|
||||
primaryModelLabelOverride?: string;
|
||||
modelAuthOverride?: string;
|
||||
activeModelAuthOverride?: string;
|
||||
includeTranscriptUsage?: boolean;
|
||||
}) => Promise<string>;
|
||||
};
|
||||
|
||||
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<CommandsStatusRuntimeModule> | null = null;
|
||||
|
||||
function loadCommandsStatusRuntime(): Promise<CommandsStatusRuntimeModule> {
|
||||
commandsStatusRuntimePromise ??= importRuntimeModule<CommandsStatusRuntimeModule>(
|
||||
import.meta.url,
|
||||
COMMANDS_STATUS_RUNTIME_SPEC,
|
||||
);
|
||||
return commandsStatusRuntimePromise;
|
||||
}
|
||||
|
||||
|
||||
3
src/auto-reply/reply/commands-status-deps.runtime.ts
Normal file
3
src/auto-reply/reply/commands-status-deps.runtime.ts
Normal file
@@ -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";
|
||||
@@ -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, unknown>) => 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<StatusRuntimeModule> | null = null;
|
||||
let commandsStatusDepsRuntimePromise: Promise<CommandsStatusSubagentsModule> | null = null;
|
||||
|
||||
function loadStatusRuntime(): Promise<StatusRuntimeModule> {
|
||||
statusRuntimePromise ??= importRuntimeModule<StatusRuntimeModule>(
|
||||
import.meta.url,
|
||||
STATUS_RUNTIME_SPEC,
|
||||
);
|
||||
return statusRuntimePromise;
|
||||
}
|
||||
|
||||
function loadCommandsStatusDepsRuntime(): Promise<CommandsStatusSubagentsModule> {
|
||||
commandsStatusDepsRuntimePromise ??= importRuntimeModule<CommandsStatusSubagentsModule>(
|
||||
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: {
|
||||
|
||||
1
src/auto-reply/status.runtime.ts
Normal file
1
src/auto-reply/status.runtime.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { buildStatusMessage } from "./status.js";
|
||||
@@ -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<string, unknown>) => 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<CompactRuntimeModule> | null = null;
|
||||
|
||||
function loadCompactRuntime(): Promise<CompactRuntimeModule> {
|
||||
compactRuntimePromise ??= importRuntimeModule<CompactRuntimeModule>(
|
||||
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<ContextEngine["compact"]>[0],
|
||||
): Promise<CompactResult> {
|
||||
// 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<typeof compactEmbeddedPiSessionDirect>[0];
|
||||
|
||||
// runtimeContext carries the full CompactEmbeddedPiSessionParams fields set
|
||||
|
||||
6
src/shared/runtime-import.ts
Normal file
6
src/shared/runtime-import.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export async function importRuntimeModule<T>(
|
||||
baseUrl: string,
|
||||
parts: readonly string[],
|
||||
): Promise<T> {
|
||||
return (await import(new URL(parts.join(""), baseUrl).href)) as T;
|
||||
}
|
||||
Reference in New Issue
Block a user