UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

842 lines (841 loc) 36.6 kB
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js"; import { o as isRecord } from "./record-coerce-DHZ4bFlT.js"; import { _ as uniqueStrings } from "./string-normalization-WNUDCpXX.js"; import { p as resolveUserPath } from "./utils-CCC-BEJH.js"; import { m as writeJson, o as tryReadJson } from "./json-files-2umMHm0W.js"; import { y as resolveSessionAgentIds } from "./agent-scope-MrLta7Pq.js"; import { a as resolveAgentDir } from "./agent-scope-config-CgCYpZfK.js"; import { i as getRuntimeConfig } from "./io-Gi7-pyU-.js"; import { t as applyMergePatch } from "./merge-patch-DFjTrPP1.js"; import { t as DEFAULT_CONTEXT_TOKENS } from "./defaults-mDjiWzE5.js"; import "./config-C9RxTsn1.js"; import { F as readClaudeCliCredentialsCached, I as readCodexCliCredentialsCached, L as readGeminiCliCredentialsCached, c as loadAuthProfileStoreForRuntime } from "./store-C8spD0DG.js"; import { n as applyPluginTextReplacements } from "./text-transforms.runtime.js"; import { t as extractMcpServerMap } from "./bundle-mcp-BeA96PNA.js"; import { t as getGlobalHookRunner } from "./hook-runner-global-BnyQREr6.js"; import { o as resolveContextEngine } from "./registry-Dca-zXEr.js"; import { r as loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader-Dp8IS24b.js"; import { r as externalCliDiscoveryForProviderAuth } from "./external-cli-discovery-Cr-vJMRB.js"; import { n as annotateInterSessionPromptText } from "./input-provenance-CYQvfQZP.js"; import { s as resolveContextTokensForModel } from "./context-CvrdNrKN.js"; import { i as resolveCliBackendConfig } from "./cli-backends-CcaQo9wE.js"; import { i as buildGenericCliContextEngineHostSupport, r as assertContextEngineHostSupport } from "./host-compat-BibWlia2.js"; import { n as ensureSystemPromptCacheBoundary } from "./system-prompt-cache-boundary-vl0D_wqS.js"; import { t as buildAgentHookContextChannelFields } from "./hook-agent-context-52t6V0oE.js"; import { u as resolveSkillsPromptForRun } from "./workspace-_gXfieOh.js"; import { S as toCliBundleMcpServerConfig, x as loadMergedBundleMcpConfig } from "./agent-bundle-mcp-runtime-Bf36JLIh.js"; import { b as resolveBootstrapTotalMaxChars, v as resolveBootstrapMaxChars, y as resolveBootstrapPromptTruncationWarningMode } from "./embedded-agent-helpers-7sfpu1OT.js"; import { a as hashCliSessionText, o as resolveCliSessionReuse } from "./cli-session-DY8dQzGa.js"; import { t as ensureContextEnginesInitialized } from "./init-CJMy1vMZ.js"; import { i as buildBootstrapPromptWarning, o as buildBootstrapTruncationReportMeta, r as buildBootstrapInjectionStats, t as analyzeBootstrapBudget } from "./bootstrap-budget-bmmzkOyn.js"; import { a as resolveBootstrapContextForRun, i as makeBootstrapWarn } from "./bootstrap-files-DiRvjZAg.js"; import { t as resolveHeartbeatPromptForSystemPrompt } from "./heartbeat-system-prompt-CKczFcLs.js"; import { n as appendModelIdentitySystemPrompt, r as buildModelIdentityPromptLine } from "./system-prompt-config-CVau6v04.js"; import { t as buildSystemPromptReport } from "./system-prompt-report-DXh85F1c.js"; import { a as prependSystemPromptAddition, c as resolvePromptBuildHookResult, s as resolveAttemptMediaTaskSystemPromptAddition } from "./attempt.prompt-helpers-CgpMNs9B.js"; import { s as resolveContextWindowInfo } from "./context-window-guard-Xr_q41jl.js"; import { n as composeSystemPromptWithHookContext } from "./attempt.thread-helpers-UNL5VFeq.js"; import { t as buildCurrentInboundPrompt } from "./runtime-context-prompt-BJmPR0_F.js"; import { n as resolveRunWorkspaceDir, t as redactRunIdentifier } from "./workspace-run-CBuaghKz.js"; import { r as cliBackendLog } from "./log-RtgQ_MP8.js"; import { c as buildCliSessionHistoryPrompt, d as loadCliSessionHistoryMessages, f as loadCliSessionReseedMessages, l as hasCliSessionTranscript, n as claudeCliSessionTranscriptHasContent, p as resolveAutoCliSessionReseedHistoryChars, r as claudeCliSessionTranscriptHasOrphanedToolUse } from "./attempt-execution.helpers-CPoC_qYr.js"; import { a as decodeHeaderEnvPlaceholder, i as applyCommonServerConfig, o as normalizeStringRecord } from "./codex-mcp-config-DGNrQzzV.js"; import { n as injectCodexMcpConfigArgs } from "./bundle-mcp-codex-QokEsifk.js"; import { a as normalizeCliModel, n as buildCliAgentSystemPrompt } from "./helpers-BLGx1Zfi.js"; import { t as prepareClaudeCliSkillsPlugin } from "./claude-skills-plugin-DpYXpxCq.js"; import { a as createMcpLoopbackServerConfig, i as resolveMcpLoopbackScopedTools, n as ensureMcpLoopbackServer, o as getActiveMcpLoopbackRuntime, s as resolveMcpLoopbackBearerToken } from "./mcp-http-Js9d10tX.js"; import path from "node:path"; import fs from "node:fs/promises"; import os from "node:os"; import crypto from "node:crypto"; //#region src/plugin-sdk/anthropic-cli.ts function loadFacadeModule() { return loadBundledPluginPublicSurfaceModuleSync({ dirName: "anthropic", artifactBasename: "api.js" }); } loadFacadeModule()["CLAUDE_CLI_BACKEND_ID"]; /** Returns whether a provider id belongs to the Claude CLI backend family. */ const isClaudeCliProvider = ((...args) => loadFacadeModule()["isClaudeCliProvider"](...args)); const cliAuthEpochDeps = { readClaudeCliCredentialsCached, readCodexCliCredentialsCached, readGeminiCliCredentialsCached, loadAuthProfileStoreForRuntime }; function hashCliAuthEpochPart(value) { return crypto.createHash("sha256").update(value).digest("hex"); } function encodeUnknown(value) { return JSON.stringify(value ?? null); } function encodeOAuthIdentity(credential) { return JSON.stringify([ "oauth", credential.provider, credential.clientId ?? null, credential.email ?? null, credential.enterpriseUrl ?? null, credential.projectId ?? null, credential.accountId ?? null ]); } function encodeClaudeCredential(credential) { return encodeOAuthIdentity({ type: "oauth", provider: credential.provider }); } function encodeCodexCredential(credential) { return encodeOAuthIdentity(credential); } function encodeGeminiCredential(credential) { return encodeOAuthIdentity(credential); } function encodeAuthProfileCredential(credential) { switch (credential.type) { case "api_key": return JSON.stringify([ "api_key", credential.provider, credential.key ?? null, encodeUnknown(credential.keyRef), credential.email ?? null, credential.displayName ?? null, encodeUnknown(credential.metadata) ]); case "token": if (credential.tokenRef !== void 0) return JSON.stringify([ "token-identity", credential.provider, encodeUnknown(credential.tokenRef), credential.email ?? null, credential.displayName ?? null ]); return JSON.stringify([ "token", credential.provider, credential.token ?? null, encodeUnknown(credential.tokenRef), credential.email ?? null, credential.displayName ?? null ]); case "oauth": return encodeOAuthIdentity(credential); } throw new Error("Unsupported auth profile credential type"); } function hasOAuthAccountIdentity(credential) { return credential.type === "oauth" && (normalizeOptionalString(credential.accountId) !== void 0 || normalizeOptionalString(credential.email) !== void 0); } function encodeAuthProfileEpochPart(authProfileId, credential) { const credentialHash = hashCliAuthEpochPart(encodeAuthProfileCredential(credential)); if (hasOAuthAccountIdentity(credential)) return `profile:oauth-identity:${credentialHash}`; return `profile:${authProfileId}:${credentialHash}`; } function getLocalCliCredentialFingerprint(provider) { switch (provider) { case "claude-cli": { const credential = cliAuthEpochDeps.readClaudeCliCredentialsCached({ ttlMs: 5e3, allowKeychainPrompt: false }); return credential ? hashCliAuthEpochPart(encodeClaudeCredential(credential)) : void 0; } case "codex-cli": { const credential = cliAuthEpochDeps.readCodexCliCredentialsCached({ ttlMs: 5e3, allowKeychainPrompt: false }); return credential ? hashCliAuthEpochPart(encodeCodexCredential(credential)) : void 0; } case "google-gemini-cli": { const credential = cliAuthEpochDeps.readGeminiCliCredentialsCached({ ttlMs: 5e3 }); return credential ? hashCliAuthEpochPart(encodeGeminiCredential(credential)) : void 0; } default: return; } } function getAuthProfileCredential(store, authProfileId) { if (!authProfileId) return; return store.profiles[authProfileId]; } /** Resolves the stable auth epoch hash for a CLI runtime/provider session. */ async function resolveCliAuthEpoch(params) { const provider = params.provider.trim(); const authProfileId = normalizeOptionalString(params.authProfileId); const parts = []; if (params.skipLocalCredential !== true) { const localFingerprint = getLocalCliCredentialFingerprint(provider); if (localFingerprint) parts.push(`local:${provider}:${localFingerprint}`); } if (authProfileId) { const credential = getAuthProfileCredential(cliAuthEpochDeps.loadAuthProfileStoreForRuntime(void 0, { readOnly: true, allowKeychainPrompt: false }), authProfileId); if (credential) parts.push(encodeAuthProfileEpochPart(authProfileId, credential)); } if (parts.length === 0) return; return hashCliAuthEpochPart(parts.join("\n")); } //#endregion //#region src/agents/cli-runner/bundle-mcp-claude.ts /** * Claude CLI argument helpers for OpenClaw-managed bundle MCP config. */ /** Find an existing Claude `--mcp-config` argument value. */ function findClaudeMcpConfigPath(args) { if (!args?.length) return; for (let i = 0; i < args.length; i += 1) { const arg = args[i] ?? ""; if (arg === "--mcp-config") return normalizeOptionalString(args[i + 1]); if (arg.startsWith("--mcp-config=")) return normalizeOptionalString(arg.slice(13)); } } /** Return Claude args with OpenClaw's strict MCP config path injected. */ function injectClaudeMcpConfigArgs(args, mcpConfigPath) { const next = []; for (let i = 0; i < (args?.length ?? 0); i += 1) { const arg = args?.[i] ?? ""; if (arg === "--strict-mcp-config") continue; if (arg === "--mcp-config") { i += 1; continue; } if (arg.startsWith("--mcp-config=")) continue; next.push(arg); } next.push("--strict-mcp-config", "--mcp-config", mcpConfigPath); return next; } //#endregion //#region src/agents/cli-runner/bundle-mcp-gemini.ts /** * Gemini CLI bundle MCP adapter that writes temporary system settings files. */ async function readJsonObject(filePath) { const raw = await tryReadJson(filePath); return raw && typeof raw === "object" && !Array.isArray(raw) ? { ...raw } : {}; } function resolveEnvPlaceholder(value, inheritedEnv) { const decoded = decodeHeaderEnvPlaceholder(value); if (!decoded) return value; const resolved = inheritedEnv?.[decoded.envVar] ?? process.env[decoded.envVar] ?? ""; return decoded.bearer ? `Bearer ${resolved}` : resolved; } function normalizeGeminiServerConfig(server, inheritedEnv) { const next = {}; applyCommonServerConfig(next, server); if (typeof server.type === "string") next.type = server.type; const headers = normalizeStringRecord(server.headers); if (headers) next.headers = Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, resolveEnvPlaceholder(value, inheritedEnv)])); if (typeof server.trust === "boolean") next.trust = server.trust; return next; } /** Writes merged Gemini system settings and returns env plus cleanup hook. */ async function writeGeminiSystemSettings(mergedConfig, inheritedEnv) { const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-gemini-mcp-")); const settingsPath = path.join(tempDir, "settings.json"); const existingSettingsPath = inheritedEnv?.GEMINI_CLI_SYSTEM_SETTINGS_PATH ?? process.env.GEMINI_CLI_SYSTEM_SETTINGS_PATH; const base = typeof existingSettingsPath === "string" && existingSettingsPath.trim() ? await readJsonObject(existingSettingsPath) : {}; const normalizedConfig = { mcpServers: Object.fromEntries(Object.entries(mergedConfig.mcpServers).map(([name, server]) => [name, normalizeGeminiServerConfig(server, inheritedEnv)])) }; const settings = applyMergePatch(base, { mcp: { allowed: Object.keys(normalizedConfig.mcpServers) }, mcpServers: normalizedConfig.mcpServers }); if (!isRecord(settings.mcp) || !isRecord(settings.mcpServers)) throw new Error("Gemini MCP settings merge produced an invalid object"); await writeJson(settingsPath, settings, { trailingNewline: true }); return { env: { ...inheritedEnv, GEMINI_CLI_SYSTEM_SETTINGS_PATH: settingsPath }, cleanup: async () => { await fs.rm(tempDir, { recursive: true, force: true }); } }; } //#endregion //#region src/agents/cli-runner/bundle-mcp.ts /** * Prepares bundled MCP configuration for CLI runner backends. */ function resolveBundleMcpMode(mode) { return mode ?? "claude-config-file"; } async function readExternalMcpConfig(configPath) { return { mcpServers: extractMcpServerMap(await tryReadJson(configPath)) }; } function sortJsonValue(value) { if (Array.isArray(value)) return value.map((entry) => sortJsonValue(entry)); if (!isRecord(value)) return value; return Object.fromEntries(Object.keys(value).toSorted().map((key) => [key, sortJsonValue(value[key])])); } function normalizeOpenClawLoopbackUrl(value) { const match = /^(http:\/\/(?:127\.0\.0\.1|localhost|\[::1\])):\d+(\/mcp)$/.exec(value.trim()) ?? void 0; if (!match) return value; return `${match[1]}:<openclaw-loopback>${match[2]}`; } function canonicalizeBundleMcpConfigForResume(config) { return { mcpServers: sortJsonValue(Object.fromEntries(Object.entries(config.mcpServers).map(([name, server]) => { if (name !== "openclaw" || typeof server.url !== "string") return [name, sortJsonValue(server)]; return [name, sortJsonValue({ ...server, url: normalizeOpenClawLoopbackUrl(server.url) })]; }))) }; } async function prepareModeSpecificBundleMcpConfig(params) { const serializedConfig = `${JSON.stringify(params.mergedConfig, null, 2)}\n`; const mcpConfigHash = crypto.createHash("sha256").update(serializedConfig).digest("hex"); const serializedResumeConfig = `${JSON.stringify(canonicalizeBundleMcpConfigForResume(params.mergedConfig), null, 2)}\n`; const mcpResumeHash = crypto.createHash("sha256").update(serializedResumeConfig).digest("hex"); if (params.mode === "codex-config-overrides") return { backend: { ...params.backend, args: injectCodexMcpConfigArgs(params.backend.args, params.mergedConfig), resumeArgs: injectCodexMcpConfigArgs(params.backend.resumeArgs ?? params.backend.args ?? [], params.mergedConfig) }, mcpConfigHash, mcpResumeHash, env: params.env }; if (params.mode === "gemini-system-settings") { const settings = await writeGeminiSystemSettings(params.mergedConfig, params.env); return { backend: params.backend, mcpConfigHash, mcpResumeHash, env: settings.env, cleanup: settings.cleanup }; } const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-cli-mcp-")); const mcpConfigPath = path.join(tempDir, "mcp.json"); await fs.writeFile(mcpConfigPath, serializedConfig, "utf-8"); return { backend: { ...params.backend, args: injectClaudeMcpConfigArgs(params.backend.args, mcpConfigPath), resumeArgs: injectClaudeMcpConfigArgs(params.backend.resumeArgs ?? params.backend.args ?? [], mcpConfigPath) }, mcpConfigHash, mcpResumeHash, env: params.env, cleanup: async () => { await fs.rm(tempDir, { recursive: true, force: true }); } }; } /** Prepare backend args/env/cleanup for bundle MCP injection into a CLI run. */ async function prepareCliBundleMcpConfig(params) { if (!params.enabled) return { backend: params.backend, env: params.env }; const mode = resolveBundleMcpMode(params.mode); const existingMcpConfigPath = mode === "claude-config-file" ? findClaudeMcpConfigPath(params.backend.resumeArgs) ?? findClaudeMcpConfigPath(params.backend.args) : void 0; let mergedConfig = { mcpServers: {} }; if (existingMcpConfigPath) { const resolvedExistingPath = path.isAbsolute(existingMcpConfigPath) ? existingMcpConfigPath : path.resolve(params.workspaceDir, existingMcpConfigPath); mergedConfig = applyMergePatch(mergedConfig, await readExternalMcpConfig(resolvedExistingPath)); } const bundleConfig = loadMergedBundleMcpConfig({ workspaceDir: params.workspaceDir, cfg: params.config, mapConfiguredServer: toCliBundleMcpServerConfig }); for (const diagnostic of bundleConfig.diagnostics) params.warn?.(`bundle MCP skipped for ${diagnostic.pluginId}: ${diagnostic.message}`); mergedConfig = applyMergePatch(mergedConfig, bundleConfig.config); if (params.additionalConfig) mergedConfig = applyMergePatch(mergedConfig, params.additionalConfig); return await prepareModeSpecificBundleMcpConfig({ mode, backend: params.backend, mergedConfig, env: params.env }); } //#endregion //#region src/agents/cli-runner/prepare.ts /** * Prepares CLI backend run context: backend config, prompts, bootstrap context, * MCP, auth epoch, and reusable session metadata. */ const prepareDeps = { makeBootstrapWarn, resolveBootstrapContextForRun, getActiveMcpLoopbackRuntime, ensureMcpLoopbackServer, createMcpLoopbackServerConfig, resolveMcpLoopbackBearerToken, resolveMcpLoopbackScopedTools, resolveOpenClawReferencePaths: async (params) => (await import("./docs-path-CswT2n8K.js")).resolveOpenClawReferencePaths(params), prepareClaudeCliSkillsPlugin, claudeCliSessionTranscriptHasContent, claudeCliSessionTranscriptHasOrphanedToolUse }; const CLAUDE_CLI_CONTEXT_MODEL_ALIASES = { opus: "claude-opus-4-8", "opus-4.8": "claude-opus-4-8", "opus-4-8": "claude-opus-4-8", "opus-4.7": "claude-opus-4-7", "opus-4-7": "claude-opus-4-7", "opus-4.6": "claude-opus-4-6", "opus-4-6": "claude-opus-4-6", sonnet: "claude-sonnet-4-6", "sonnet-4.6": "claude-sonnet-4-6", "sonnet-4-6": "claude-sonnet-4-6" }; function resolveClaudeCliContextModelId(modelId) { const trimmed = modelId.trim(); return CLAUDE_CLI_CONTEXT_MODEL_ALIASES[trimmed.toLowerCase()] ?? trimmed; } /** Returns whether profile-owned prepared execution should skip local CLI epoch hashing. */ function shouldSkipLocalCliCredentialEpoch(params) { return Boolean(params.authEpochMode === "profile-only" && params.authProfileId && params.authCredential && params.preparedExecution); } /** Builds the complete context required to execute a CLI-backed agent run. */ async function prepareCliRunContext(params) { 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 cwd = params.cwd ? resolveUserPath(params.cwd) : workspaceDir; const cwdHash = hashCliSessionText(cwd); const backendResolved = resolveCliBackendConfig(params.provider, params.config, { agentId: params.agentId }); if (!backendResolved) throw new Error(`Unknown CLI backend: ${params.provider}`); if (params.toolsAllow !== void 0) throw new Error(`CLI backend ${backendResolved.id} cannot enforce runtime toolsAllow; use an embedded runtime for restricted tool policy`); if (params.disableTools === true && backendResolved.nativeToolMode === "always-on") throw new Error(`CLI backend ${backendResolved.id} cannot run with tools disabled because it exposes native tools`); const { defaultAgentId, sessionAgentId } = resolveSessionAgentIds({ sessionKey: params.sessionKey, config: params.config, agentId: params.agentId }); const agentDir = resolveAgentDir(params.config ?? {}, sessionAgentId); const effectiveAuthProfileId = (params.authProfileId?.trim() || void 0) ?? backendResolved.defaultAuthProfileId?.trim() ?? void 0; let authCredential; if (effectiveAuthProfileId) authCredential = loadAuthProfileStoreForRuntime(agentDir, { readOnly: true, externalCli: externalCliDiscoveryForProviderAuth({ provider: params.provider, profileId: effectiveAuthProfileId }) }).profiles[effectiveAuthProfileId]; const extraSystemPrompt = params.extraSystemPrompt?.trim() ?? ""; const extraSystemPromptHash = params.extraSystemPromptStatic !== void 0 ? hashCliSessionText(params.extraSystemPromptStatic.trim() || void 0) : hashCliSessionText(extraSystemPrompt); const modelId = (params.model ?? "default").trim() || "default"; const normalizedModel = normalizeCliModel(modelId, backendResolved.config); const modelDisplay = `${params.provider}/${modelId}`; const isClaudeCli = isClaudeCliProvider(params.provider); const modelContextTokens = isClaudeCli ? resolveContextTokensForModel({ cfg: params.config, provider: params.provider, model: resolveClaudeCliContextModelId(modelId), fallbackContextTokens: 2e5, allowAsyncLoad: false }) : void 0; const contextWindowInfo = resolveContextWindowInfo({ cfg: params.config, provider: params.provider, modelId, modelContextTokens, defaultTokens: DEFAULT_CONTEXT_TOKENS }); const autoReseedHistoryChars = isClaudeCli ? resolveAutoCliSessionReseedHistoryChars(contextWindowInfo.tokens) : void 0; const sessionLabel = params.sessionKey ?? params.sessionId; const { bootstrapFiles, contextFiles } = await prepareDeps.resolveBootstrapContextForRun({ workspaceDir, config: params.config, sessionKey: params.sessionKey, sessionId: params.sessionId, agentId: sessionAgentId, contextMode: params.bootstrapContextMode, runKind: params.bootstrapContextRunKind, warn: prepareDeps.makeBootstrapWarn({ sessionLabel, workspaceDir, warn: (message) => cliBackendLog.warn(message) }) }); const bootstrapMaxChars = resolveBootstrapMaxChars(params.config, sessionAgentId); const bootstrapTotalMaxChars = resolveBootstrapTotalMaxChars(params.config, sessionAgentId); 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 bundleMcpEnabled = backendResolved.bundleMcp && params.disableTools !== true; let mcpLoopbackRuntime = bundleMcpEnabled ? prepareDeps.getActiveMcpLoopbackRuntime() : void 0; if (bundleMcpEnabled && !mcpLoopbackRuntime) { try { await prepareDeps.ensureMcpLoopbackServer(); } catch (error) { cliBackendLog.warn(`mcp loopback server failed to start: ${String(error)}`); } mcpLoopbackRuntime = prepareDeps.getActiveMcpLoopbackRuntime(); } const preparedBackend = await prepareCliBundleMcpConfig({ enabled: bundleMcpEnabled, mode: backendResolved.bundleMcpMode, backend: backendResolved.config, workspaceDir, config: params.config, additionalConfig: mcpLoopbackRuntime ? prepareDeps.createMcpLoopbackServerConfig(mcpLoopbackRuntime.port) : void 0, env: mcpLoopbackRuntime ? { OPENCLAW_MCP_TOKEN: prepareDeps.resolveMcpLoopbackBearerToken(mcpLoopbackRuntime, params.senderIsOwner === true), OPENCLAW_MCP_AGENT_ID: sessionAgentId ?? "", OPENCLAW_MCP_ACCOUNT_ID: params.agentAccountId ?? "", OPENCLAW_MCP_SESSION_KEY: params.sessionKey ?? "", OPENCLAW_MCP_MESSAGE_CHANNEL: params.messageChannel ?? params.messageProvider ?? "", OPENCLAW_MCP_CURRENT_CHANNEL_ID: params.currentChannelId ?? "", OPENCLAW_MCP_CURRENT_THREAD_TS: params.currentThreadTs ?? "", OPENCLAW_MCP_CURRENT_MESSAGE_ID: params.currentMessageId != null ? String(params.currentMessageId) : "", OPENCLAW_MCP_CURRENT_INBOUND_AUDIO: params.currentInboundAudio === true ? "true" : "", OPENCLAW_MCP_INBOUND_EVENT_KIND: params.currentInboundEventKind ?? "", OPENCLAW_MCP_SOURCE_REPLY_DELIVERY_MODE: params.sourceReplyDeliveryMode ?? "" } : void 0, warn: (message) => cliBackendLog.warn(message) }); const preparedExecution = await backendResolved.prepareExecution?.({ config: params.config, workspaceDir, agentDir, provider: params.provider, modelId, authProfileId: effectiveAuthProfileId }); const skipLocalCredentialEpoch = shouldSkipLocalCliCredentialEpoch({ authEpochMode: backendResolved.authEpochMode, authProfileId: effectiveAuthProfileId, authCredential, preparedExecution }); const authEpoch = await resolveCliAuthEpoch({ provider: params.provider, authProfileId: effectiveAuthProfileId, skipLocalCredential: skipLocalCredentialEpoch }); const preparedBackendEnv = preparedExecution?.env && Object.keys(preparedExecution.env).length > 0 ? { ...preparedBackend.env, ...preparedExecution.env } : preparedBackend.env; const preparedBackendCleanup = preparedBackend.cleanup || preparedExecution?.cleanup ? async () => { try { await preparedExecution?.cleanup?.(); } finally { await preparedBackend.cleanup?.(); } } : void 0; const claudeSkillsPlugin = await prepareDeps.prepareClaudeCliSkillsPlugin({ backendId: backendResolved.id, skillsSnapshot: params.skillsSnapshot }); const preparedCleanup = preparedBackendCleanup || claudeSkillsPlugin.args.length > 0 ? async () => { try { await claudeSkillsPlugin.cleanup(); } finally { await preparedBackendCleanup?.(); } } : void 0; const preparedBackendClearEnv = [...preparedBackend.backend.clearEnv ?? [], ...preparedExecution?.clearEnv ?? []]; const preparedBackendFinal = { ...preparedBackend, backend: { ...preparedBackend.backend, ...preparedBackendClearEnv.length > 0 ? { clearEnv: uniqueStrings(preparedBackendClearEnv) } : {} }, ...preparedBackendEnv ? { env: preparedBackendEnv } : {}, ...preparedCleanup ? { cleanup: preparedCleanup } : {} }; const promptTools = bundleMcpEnabled && mcpLoopbackRuntime ? prepareDeps.resolveMcpLoopbackScopedTools({ cfg: params.config ?? getRuntimeConfig(), sessionKey: params.sessionKey ?? "", messageProvider: params.messageChannel ?? params.messageProvider, currentChannelId: params.currentChannelId, currentThreadTs: params.currentThreadTs, currentMessageId: params.currentMessageId, currentInboundAudio: params.currentInboundAudio, accountId: params.agentAccountId, inboundEventKind: params.currentInboundEventKind, sourceReplyDeliveryMode: params.sourceReplyDeliveryMode, senderIsOwner: params.senderIsOwner }).tools : []; const promptToolNamesHash = bundleMcpEnabled && mcpLoopbackRuntime ? hashCliSessionText(JSON.stringify(promptTools.map((tool) => tool.name).toSorted())) : void 0; const reusableCliSessionCandidate = params.cliSessionBinding ? resolveCliSessionReuse({ binding: params.cliSessionBinding, authProfileId: effectiveAuthProfileId, authEpoch, authEpochVersion: 5, extraSystemPromptHash, promptToolNamesHash, cwdHash, mcpConfigHash: preparedBackendFinal.mcpConfigHash, mcpResumeHash: preparedBackendFinal.mcpResumeHash }) : params.cliSessionId ? { sessionId: params.cliSessionId } : {}; const candidateClaudeCliSessionId = reusableCliSessionCandidate.sessionId?.trim() || void 0; const hasClaudeCliCandidate = candidateClaudeCliSessionId !== void 0 && isClaudeCliProvider(params.provider); const claudeCliTranscriptMissing = hasClaudeCliCandidate && !await prepareDeps.claudeCliSessionTranscriptHasContent({ sessionId: candidateClaudeCliSessionId, workspaceDir: cwd }); const claudeCliTranscriptOrphanedToolUse = hasClaudeCliCandidate && !claudeCliTranscriptMissing && await prepareDeps.claudeCliSessionTranscriptHasOrphanedToolUse({ sessionId: candidateClaudeCliSessionId, workspaceDir: cwd }); const claudeCliInvalidatedReason = claudeCliTranscriptMissing ? "missing-transcript" : claudeCliTranscriptOrphanedToolUse ? "orphaned-tool-use" : void 0; const reusableCliSession = claudeCliInvalidatedReason ? { invalidatedReason: claudeCliInvalidatedReason } : reusableCliSessionCandidate; if (reusableCliSession.invalidatedReason) cliBackendLog.info(`cli session reset: provider=${params.provider} reason=${reusableCliSession.invalidatedReason}`); let openClawHistoryMessages; const loadOpenClawHistoryMessages = async () => { openClawHistoryMessages ??= await loadCliSessionHistoryMessages({ sessionId: params.sessionId, sessionFile: params.sessionFile, sessionKey: params.sessionKey, agentId: params.agentId, config: params.config }); return openClawHistoryMessages; }; const heartbeatPrompt = resolveHeartbeatPromptForSystemPrompt({ config: params.config, agentId: sessionAgentId, defaultAgentId }); const openClawReferences = await prepareDeps.resolveOpenClawReferencePaths({ workspaceDir, argv1: process.argv[1], cwd, moduleUrl: import.meta.url }); const skillsPrompt = resolveSkillsPromptForRun({ skillsSnapshot: params.skillsSnapshot, workspaceDir, config: params.config, agentId: sessionAgentId }); const systemPromptSkillsPrompt = claudeSkillsPlugin.args.length > 0 ? "" : skillsPrompt; const builtSystemPrompt = buildCliAgentSystemPrompt({ workspaceDir, cwd, config: params.config, defaultThinkLevel: params.thinkLevel, extraSystemPrompt, sourceReplyDeliveryMode: params.sourceReplyDeliveryMode, silentReplyPromptMode: params.silentReplyPromptMode, ownerNumbers: params.ownerNumbers, heartbeatPrompt, docsPath: openClawReferences.docsPath ?? void 0, sourcePath: openClawReferences.sourcePath ?? void 0, skillsPrompt: systemPromptSkillsPrompt, tools: promptTools, contextFiles, modelDisplay, agentId: sessionAgentId }); let systemPrompt = backendResolved.transformSystemPrompt?.({ config: params.config, workspaceDir, provider: params.provider, modelId, modelDisplay, agentId: sessionAgentId, systemPrompt: builtSystemPrompt }) ?? builtSystemPrompt; let preparedPrompt = params.prompt; const hookRunner = getGlobalHookRunner(); try { const hookResult = await resolvePromptBuildHookResult({ config: params.config ?? getRuntimeConfig(), prompt: params.prompt, messages: await loadOpenClawHistoryMessages(), hookCtx: { runId: params.runId, agentId: sessionAgentId, sessionKey: params.sessionKey, sessionId: params.sessionId, workspaceDir, modelProviderId: params.provider, modelId, trigger: params.trigger, ...buildAgentHookContextChannelFields(params) }, hookRunner }); if (hookResult.prependContext) preparedPrompt = `${hookResult.prependContext}\n\n${preparedPrompt}`; if (hookResult.appendContext) preparedPrompt = `${preparedPrompt}\n\n${hookResult.appendContext}`; const hookSystemPrompt = hookResult.systemPrompt?.trim(); if (hookSystemPrompt) systemPrompt = hookSystemPrompt; systemPrompt = composeSystemPromptWithHookContext({ baseSystemPrompt: systemPrompt, prependSystemContext: hookResult.prependSystemContext, appendSystemContext: hookResult.appendSystemContext }) ?? systemPrompt; const mediaTaskSystemPromptAddition = resolveAttemptMediaTaskSystemPromptAddition({ sessionKey: params.sessionKey, trigger: params.trigger }); if (mediaTaskSystemPromptAddition) systemPrompt = prependSystemPromptAddition({ systemPrompt: ensureSystemPromptCacheBoundary(systemPrompt), systemPromptAddition: mediaTaskSystemPromptAddition }); } catch (error) { cliBackendLog.warn(`cli prompt-build hook preparation failed: ${String(error)}`); } const fullCurrentInboundPrompt = buildCurrentInboundPrompt({ context: params.currentInboundContext, prompt: preparedPrompt }); const runCurrentInboundPrompt = buildCurrentInboundPrompt({ context: params.currentInboundContext, prompt: preparedPrompt, preferResumableText: params.currentInboundEventKind === "room_event" && Boolean(reusableCliSession.sessionId) }); const historyPromptCurrentTurn = annotateInterSessionPromptText(fullCurrentInboundPrompt, params.inputProvenance); preparedPrompt = annotateInterSessionPromptText(runCurrentInboundPrompt, params.inputProvenance); const allowRawTranscriptReseed = backendResolved.config.reseedFromRawTranscriptWhenUncompacted === true; const rawTranscriptReseedReason = reusableCliSession.sessionId ? "session-expired" : reusableCliSession.invalidatedReason; const openClawHistoryPrompt = !reusableCliSession.sessionId || allowRawTranscriptReseed ? buildCliSessionHistoryPrompt({ messages: await loadCliSessionReseedMessages({ sessionId: params.sessionId, sessionFile: params.sessionFile, sessionKey: params.sessionKey, agentId: params.agentId, config: params.config, allowRawTranscriptReseed, rawTranscriptReseedReason }), prompt: historyPromptCurrentTurn, maxHistoryChars: autoReseedHistoryChars }) : void 0; const systemPromptWithReplacements = applyPluginTextReplacements(systemPrompt, backendResolved.textTransforms?.input); systemPrompt = appendModelIdentitySystemPrompt({ systemPrompt: buildModelIdentityPromptLine(modelDisplay) && systemPromptWithReplacements.trim().length > 0 ? ensureSystemPromptCacheBoundary(systemPromptWithReplacements) : systemPromptWithReplacements, model: modelDisplay }); 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: systemPromptSkillsPrompt, tools: promptTools, currentTurn: { ...params.currentInboundEventKind ? { kind: params.currentInboundEventKind } : {}, promptChars: preparedPrompt.length, runtimeContextChars: 0 } }); const contextEngineConfig = params.config ?? getRuntimeConfig(); try { ensureContextEnginesInitialized(); const { sessionAgentId: contextEngineSessionAgentId } = resolveSessionAgentIds({ sessionKey: params.sessionKey, config: contextEngineConfig, agentId: params.agentId }); const resolvedContextEngine = await resolveContextEngine(contextEngineConfig, { agentDir: resolveAgentDir(contextEngineConfig, contextEngineSessionAgentId), workspaceDir }); const contextEngine = resolvedContextEngine.info.id !== "legacy" ? resolvedContextEngine : void 0; if (contextEngine) assertContextEngineHostSupport({ contextEngine, operation: "agent-run", host: buildGenericCliContextEngineHostSupport({ backendId: backendResolved.id, capabilities: backendResolved.contextEngineHostCapabilities }) }); const hadSessionFile = await hasCliSessionTranscript({ sessionId: params.sessionId, sessionFile: params.sessionFile, sessionKey: params.sessionKey, agentId: params.agentId, config: contextEngineConfig }); const contextEngineTurnPrompt = params.transcriptPrompt ?? params.prompt; return { params: { ...params, config: contextEngineConfig, prompt: preparedPrompt }, effectiveAuthProfileId, started, workspaceDir, cwd, backendResolved, preparedBackend: preparedBackendFinal, reusableCliSession, hadSessionFile, contextEngineConfig, contextEngine, contextEngineTurnPrompt, modelId, normalizedModel, contextWindowInfo, systemPrompt, systemPromptReport, claudeSkillsPluginArgs: claudeSkillsPlugin.args, bootstrapPromptWarningLines: bootstrapPromptWarning.lines, ...openClawHistoryPrompt ? { openClawHistoryPrompt } : {}, heartbeatPrompt, authEpoch, authEpochVersion: 5, extraSystemPromptHash, promptToolNamesHash, cwdHash }; } catch (err) { try { await preparedBackendFinal.cleanup?.(); } catch (cleanupErr) { cliBackendLog.warn(`cli backend cleanup after prepare failure failed: ${String(cleanupErr)}`); } throw err; } } //#endregion export { prepareCliRunContext };