Files
openclaw/src/agents/cli-runner/prepare.ts
2026-04-11 02:17:39 +01:00

247 lines
8.7 KiB
TypeScript

import {
createMcpLoopbackServerConfig,
getActiveMcpLoopbackRuntime,
} from "../../gateway/mcp-http.loopback-runtime.js";
import { resolveSessionAgentIds } from "../agent-scope.js";
import {
buildBootstrapInjectionStats,
buildBootstrapPromptWarning,
buildBootstrapTruncationReportMeta,
analyzeBootstrapBudget,
} from "../bootstrap-budget.js";
import {
makeBootstrapWarn as makeBootstrapWarnImpl,
resolveBootstrapContextForRun as resolveBootstrapContextForRunImpl,
} from "../bootstrap-files.js";
import { resolveCliAuthEpoch } from "../cli-auth-epoch.js";
import { resolveCliBackendConfig } from "../cli-backends.js";
import { hashCliSessionText, resolveCliSessionReuse } from "../cli-session.js";
import { resolveHeartbeatPromptForSystemPrompt } from "../heartbeat-system-prompt.js";
import {
resolveBootstrapMaxChars,
resolveBootstrapPromptTruncationWarningMode,
resolveBootstrapTotalMaxChars,
} from "../pi-embedded-helpers.js";
import { applyPluginTextReplacements } from "../plugin-text-transforms.js";
import { resolveSkillsPromptForRun } from "../skills.js";
import { resolveSystemPromptOverride } from "../system-prompt-override.js";
import { buildSystemPromptReport } from "../system-prompt-report.js";
import { redactRunIdentifier, resolveRunWorkspaceDir } from "../workspace-run.js";
import { prepareCliBundleMcpConfig } from "./bundle-mcp.js";
import { buildSystemPrompt, normalizeCliModel } from "./helpers.js";
import { cliBackendLog } from "./log.js";
import type { PreparedCliRunContext, RunCliAgentParams } from "./types.js";
const prepareDeps = {
makeBootstrapWarn: makeBootstrapWarnImpl,
resolveBootstrapContextForRun: resolveBootstrapContextForRunImpl,
getActiveMcpLoopbackRuntime,
createMcpLoopbackServerConfig,
resolveOpenClawDocsPath: async (
params: Parameters<typeof import("../docs-path.js").resolveOpenClawDocsPath>[0],
) => (await import("../docs-path.js")).resolveOpenClawDocsPath(params),
};
export function setCliRunnerPrepareTestDeps(overrides: Partial<typeof prepareDeps>): void {
Object.assign(prepareDeps, overrides);
}
export async function prepareCliRunContext(
params: RunCliAgentParams,
): Promise<PreparedCliRunContext> {
const started = Date.now();
const workspaceResolution = resolveRunWorkspaceDir({
workspaceDir: params.workspaceDir,
sessionKey: params.sessionKey,
agentId: params.agentId,
config: params.config,
});
const resolvedWorkspace = workspaceResolution.workspaceDir;
const redactedSessionId = redactRunIdentifier(params.sessionId);
const redactedSessionKey = redactRunIdentifier(params.sessionKey);
const redactedWorkspace = redactRunIdentifier(resolvedWorkspace);
if (workspaceResolution.usedFallback) {
cliBackendLog.warn(
`[workspace-fallback] caller=runCliAgent reason=${workspaceResolution.fallbackReason} run=${params.runId} session=${redactedSessionId} sessionKey=${redactedSessionKey} agent=${workspaceResolution.agentId} workspace=${redactedWorkspace}`,
);
}
const workspaceDir = resolvedWorkspace;
const backendResolved = resolveCliBackendConfig(params.provider, params.config);
if (!backendResolved) {
throw new Error(`Unknown CLI backend: ${params.provider}`);
}
const authEpoch = await resolveCliAuthEpoch({
provider: params.provider,
authProfileId: params.authProfileId,
});
const extraSystemPrompt = params.extraSystemPrompt?.trim() ?? "";
const extraSystemPromptHash = hashCliSessionText(extraSystemPrompt);
const modelId = (params.model ?? "default").trim() || "default";
const normalizedModel = normalizeCliModel(modelId, backendResolved.config);
const modelDisplay = `${params.provider}/${modelId}`;
const sessionLabel = params.sessionKey ?? params.sessionId;
const { bootstrapFiles, contextFiles } = await prepareDeps.resolveBootstrapContextForRun({
workspaceDir,
config: params.config,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
warn: prepareDeps.makeBootstrapWarn({
sessionLabel,
warn: (message) => cliBackendLog.warn(message),
}),
});
const bootstrapMaxChars = resolveBootstrapMaxChars(params.config);
const bootstrapTotalMaxChars = resolveBootstrapTotalMaxChars(params.config);
const bootstrapAnalysis = analyzeBootstrapBudget({
files: buildBootstrapInjectionStats({
bootstrapFiles,
injectedFiles: contextFiles,
}),
bootstrapMaxChars,
bootstrapTotalMaxChars,
});
const bootstrapPromptWarningMode = resolveBootstrapPromptTruncationWarningMode(params.config);
const bootstrapPromptWarning = buildBootstrapPromptWarning({
analysis: bootstrapAnalysis,
mode: bootstrapPromptWarningMode,
seenSignatures: params.bootstrapPromptWarningSignaturesSeen,
previousSignature: params.bootstrapPromptWarningSignature,
});
const { defaultAgentId, sessionAgentId } = resolveSessionAgentIds({
sessionKey: params.sessionKey,
config: params.config,
agentId: params.agentId,
});
const mcpLoopbackRuntime = backendResolved.bundleMcp
? prepareDeps.getActiveMcpLoopbackRuntime()
: undefined;
const preparedBackend = await prepareCliBundleMcpConfig({
enabled: backendResolved.bundleMcp,
mode: backendResolved.bundleMcpMode,
backend: backendResolved.config,
workspaceDir,
config: params.config,
additionalConfig: mcpLoopbackRuntime
? prepareDeps.createMcpLoopbackServerConfig(mcpLoopbackRuntime.port)
: undefined,
env: mcpLoopbackRuntime
? {
OPENCLAW_MCP_TOKEN: mcpLoopbackRuntime.token,
OPENCLAW_MCP_AGENT_ID: sessionAgentId ?? "",
OPENCLAW_MCP_ACCOUNT_ID: params.agentAccountId ?? "",
OPENCLAW_MCP_SESSION_KEY: params.sessionKey ?? "",
OPENCLAW_MCP_MESSAGE_CHANNEL: params.messageProvider ?? "",
OPENCLAW_MCP_SENDER_IS_OWNER: params.senderIsOwner === true ? "true" : "false",
}
: undefined,
warn: (message) => cliBackendLog.warn(message),
});
const reusableCliSession = params.cliSessionBinding
? resolveCliSessionReuse({
binding: params.cliSessionBinding,
authProfileId: params.authProfileId,
authEpoch,
extraSystemPromptHash,
mcpConfigHash: preparedBackend.mcpConfigHash,
})
: params.cliSessionId
? { sessionId: params.cliSessionId }
: {};
if (reusableCliSession.invalidatedReason) {
cliBackendLog.info(
`cli session reset: provider=${params.provider} reason=${reusableCliSession.invalidatedReason}`,
);
}
const heartbeatPrompt = resolveHeartbeatPromptForSystemPrompt({
config: params.config,
agentId: sessionAgentId,
defaultAgentId,
});
const docsPath = await prepareDeps.resolveOpenClawDocsPath({
workspaceDir,
argv1: process.argv[1],
cwd: process.cwd(),
moduleUrl: import.meta.url,
});
const skillsPrompt = resolveSkillsPromptForRun({
skillsSnapshot: params.skillsSnapshot,
workspaceDir,
config: params.config,
agentId: sessionAgentId,
});
const builtSystemPrompt =
resolveSystemPromptOverride({
config: params.config,
agentId: sessionAgentId,
}) ??
buildSystemPrompt({
workspaceDir,
config: params.config,
defaultThinkLevel: params.thinkLevel,
extraSystemPrompt,
ownerNumbers: params.ownerNumbers,
heartbeatPrompt,
docsPath: docsPath ?? undefined,
skillsPrompt,
tools: [],
contextFiles,
modelDisplay,
agentId: sessionAgentId,
});
const transformedSystemPrompt =
backendResolved.transformSystemPrompt?.({
config: params.config,
workspaceDir,
provider: params.provider,
modelId,
modelDisplay,
agentId: sessionAgentId,
systemPrompt: builtSystemPrompt,
}) ?? builtSystemPrompt;
const systemPrompt = applyPluginTextReplacements(
transformedSystemPrompt,
backendResolved.textTransforms?.input,
);
const systemPromptReport = buildSystemPromptReport({
source: "run",
generatedAt: Date.now(),
sessionId: params.sessionId,
sessionKey: params.sessionKey,
provider: params.provider,
model: modelId,
workspaceDir,
bootstrapMaxChars,
bootstrapTotalMaxChars,
bootstrapTruncation: buildBootstrapTruncationReportMeta({
analysis: bootstrapAnalysis,
warningMode: bootstrapPromptWarningMode,
warning: bootstrapPromptWarning,
}),
sandbox: { mode: "off", sandboxed: false },
systemPrompt,
bootstrapFiles,
injectedFiles: contextFiles,
skillsPrompt,
tools: [],
});
return {
params,
started,
workspaceDir,
backendResolved,
preparedBackend,
reusableCliSession,
modelId,
normalizedModel,
systemPrompt,
systemPromptReport,
bootstrapPromptWarningLines: bootstrapPromptWarning.lines,
heartbeatPrompt,
authEpoch,
extraSystemPromptHash,
};
}