UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

372 lines (371 loc) • 15.8 kB
import { n as sanitizeTerminalText } from "./safe-text-SY_CvCi3.mjs"; import { t as formatErrorMessage } from "./errors-O33D01XW.mjs"; import { u as isUpdateDoctorLintPass } from "./update-phase-BClSJ17n.mjs"; import { u as withPluginRuntimeRegistryScope } from "./gateway-request-scope-BSmCVVpP.mjs"; import { t as assignSafeServerNames } from "./agent-bundle-mcp-names-BHnv55Pk.mjs"; import { l as normalizeToolPolicyName } from "./tool-policy-shared-DWHChhWM.mjs"; import { i as collectExplicitAllowlist } from "./tool-policy-BKvvmcQR.mjs"; import { t as loadSessionMcpConfig } from "./agent-bundle-mcp-runtime-config-BL8MME2J.mjs"; import { a as partitionMcpServersByConnectionScope } from "./mcp-connection-resolver-ECD9_ES3.mjs"; import { a as setPluginToolMeta } from "./tool-metadata-DEC0Rqn3.mjs"; import { n as resolveEffectiveToolPolicy } from "./agent-tools.policy-D6S4y999.mjs"; import { t as resolveConversationCapabilityProfile } from "./conversation-capability-profile-BL2iKi3w.mjs"; import { t as appendRuntimePluginToolGrant } from "./tool-grant-allowlist-DlmDPMdD.mjs"; import { t as applyFinalEffectiveToolPolicy } from "./effective-tool-policy-brw4Qiqw.mjs"; import { a as shouldCreateBundleMcpRuntimeForAttempt } from "./attempt-tool-construction-plan-7h3_PwTS.mjs"; //#region src/flows/doctor-tool-schema-runtime.ts async function collectBundleMcpRuntimeToolSchemaFindings(params) { const { collectNormalizedToolSchemaFindings } = await import("./doctor-tool-schema-projection-cMjUyPZM.mjs"); const activeBundleTools = applyFinalEffectiveToolPolicy({ bundledTools: params.bundleRuntime.tools, config: params.cfg, conversationCapabilityProfile: resolveConversationCapabilityProfile({ config: params.cfg, agentId: params.agentId, modelProvider: params.modelRef.provider, modelId: params.modelRef.model }), warn: () => {} }); return collectNormalizedToolSchemaFindings({ agentId: params.agentId, tools: activeBundleTools, cfg: params.cfg, workspaceDir: params.workspaceDir, modelRef: params.modelRef, model: params.model, normalizationFailureFinding: bundleMcpRuntimeNormalizationFailureFinding }); } function bundleMcpRuntimeNormalizationFailureFinding(error) { return { checkId: "core/doctor/runtime-tool-schemas", severity: "error", message: "Configured MCP tool schema validation could not normalize the runtime tool set.", path: "mcp.servers", requirement: formatErrorMessage(error), fixHint: "Fix provider/plugin schema normalization errors, then rerun doctor before relying on assistant tool startup." }; } function bundleMcpRuntimeLoadFailureFinding(error) { return { checkId: "core/doctor/runtime-tool-schemas", severity: "error", message: "Configured MCP tool schema validation could not load the runtime tool set.", path: "mcp.servers", requirement: formatErrorMessage(error), fixHint: "Fix or disable the offending MCP server, then rerun doctor before relying on assistant tool startup." }; } function bundleMcpRuntimeDiagnosticFinding(diagnostic) { return { checkId: "core/doctor/runtime-tool-schemas", severity: "error", message: `Configured MCP server "${diagnostic.serverName}" could not expose runtime tools for schema validation.`, path: `mcp.servers.${diagnostic.serverName}`, requirement: diagnostic.message, fixHint: "Fix or disable the offending MCP server, then rerun doctor before relying on assistant tool startup." }; } function bundleMcpRequesterInspectionFinding(serverName) { return { checkId: "core/doctor/runtime-tool-schemas", severity: "info", message: `Configured requester-scoped MCP server "${serverName}" was not probed without an authenticated requester.`, path: `mcp.servers.${serverName}`, requirement: "authenticated requester context", fixHint: "Verify this server from an authenticated agent turn." }; } function makeBundleMcpDiagnosticSentinel(name) { const sentinel = { name, label: "Bundle MCP diagnostic", description: "Internal doctor sentinel for bundle MCP schema diagnostics.", parameters: { type: "object", properties: {} }, execute: async () => ({ content: [], details: {} }) }; setPluginToolMeta(sentinel, { pluginId: "bundle-mcp", optional: false }); return sentinel; } function synthesizeBundleMcpAllowlistSentinelName(params) { const normalized = normalizeToolPolicyName(params.allowlistEntry); const serverPrefix = normalizeToolPolicyName(`${params.safeServerName}__`); if (normalized.startsWith(serverPrefix)) return normalized; const separatorIndex = normalized.lastIndexOf("__"); if (separatorIndex < 0) return; const toolPattern = normalized.slice(separatorIndex + 2); if (!toolPattern) return; const concreteToolName = toolPattern.replace(/\*/g, "diagnostic").replace(/\?/g, "x"); return `${params.safeServerName}__${concreteToolName}`; } function collectBundleMcpDiagnosticSentinels(params) { const sentinels = [makeBundleMcpDiagnosticSentinel(`${params.diagnostic.safeServerName}__runtime_schema`)]; const effectivePolicy = resolveEffectiveToolPolicy({ config: params.cfg, agentId: params.agentId, modelProvider: params.modelRef.provider, modelId: params.modelRef.model }); const explicitAllowlist = collectExplicitAllowlist([ effectivePolicy.globalPolicy, effectivePolicy.globalProviderPolicy, effectivePolicy.agentPolicy, effectivePolicy.agentProviderPolicy, effectivePolicy.profileAlsoAllow ? { allow: effectivePolicy.profileAlsoAllow } : void 0, effectivePolicy.providerProfileAlsoAllow ? { allow: effectivePolicy.providerProfileAlsoAllow } : void 0 ]); if (explicitAllowlist.length === 0) return sentinels; for (const entry of explicitAllowlist) { const sentinelName = synthesizeBundleMcpAllowlistSentinelName({ safeServerName: params.diagnostic.safeServerName, allowlistEntry: entry }); if (sentinelName) sentinels.push(makeBundleMcpDiagnosticSentinel(sentinelName)); } return sentinels; } function shouldReportBundleMcpRuntimeDiagnostic(params) { return applyFinalEffectiveToolPolicy({ bundledTools: collectBundleMcpDiagnosticSentinels(params), config: params.cfg, conversationCapabilityProfile: resolveConversationCapabilityProfile({ config: params.cfg, agentId: params.agentId, modelProvider: params.modelRef.provider, modelId: params.modelRef.model }), warn: () => {} }).length > 0; } async function collectRuntimeToolSchemaFindings(sourceConfig, options = {}) { const [{ captureRuntimeConfig }, { prepareDoctorToolSchemaFrames }, { collectAgentRuntimeToolSchemaFindings }] = await Promise.all([ import("./runtime-source-projection-05WDEmVh.mjs"), import("./doctor-tool-schema-frames-DpYaOMg-.mjs"), import("./doctor-tool-schema-projection-cMjUyPZM.mjs") ]); const cfg = captureRuntimeConfig(sourceConfig); const env = options.env ?? process.env; const runWithPluginMetadataSnapshot = options.runWithPluginMetadataSnapshot ?? (await import("./plugin-metadata-snapshot-scope-DXzxnOjt.mjs")).createDoctorPluginMetadataSnapshotScope({ env }).run; const { frames, findings } = await prepareDoctorToolSchemaFrames(cfg, { ...options, env, runWithPluginMetadataSnapshot }); const deferMcpProbes = isUpdateDoctorLintPass(env); const deferredServers = /* @__PURE__ */ new Set(); const bundleRuntimeByContext = /* @__PURE__ */ new Map(); const bundleRuntimeLoadErrorsByContext = /* @__PURE__ */ new Map(); const reportedBundleRuntimeDiagnostics = /* @__PURE__ */ new Set(); const reportedBundleRuntimeLoadErrors = /* @__PURE__ */ new Set(); const reportedRequesterScopedServers = /* @__PURE__ */ new Set(); let inspection; try { if (frames.length > 0) { try { const [{ acquirePluginToolInspectionRegistry }, { resolvePluginRuntimeLoadContext }] = await Promise.all([import("./tools-Bghz61ju.mjs"), import("./load-context.resolve-D832YfKQ.mjs")]); inspection = await runWithPluginMetadataSnapshot({ config: cfg }, () => acquirePluginToolInspectionRegistry({ loadContext: resolvePluginRuntimeLoadContext({ config: cfg, env }), runWithPluginMetadataSnapshot, scopes: frames.map((frame) => ({ context: { config: cfg, runtimeConfig: cfg, agentId: frame.agentId, agentDir: frame.agentDir, workspaceDir: frame.workspaceDir }, env, allowGatewaySubagentBinding: true, toolAllowlist: appendRuntimePluginToolGrant(frame.capabilityProfile.policy.explicitToolAllowlist, frame.capabilityProfile.policy.runtimePluginToolGrant), toolDenylist: frame.capabilityProfile.policy.explicitToolDenylist })) })); } catch (error) { findings.push({ checkId: "core/doctor/runtime-tool-schemas", severity: "warning", message: "Runtime tool schema inspection could not prepare plugin registrations.", requirement: formatErrorMessage(error), fixHint: "Fix plugin loading errors, then rerun doctor to inspect active tools." }); return findings; } for (const plugin of inspection.registry?.plugins ?? []) if (plugin.status === "error") findings.push({ checkId: "core/doctor/runtime-tool-schemas", severity: "warning", message: `Plugin ${plugin.id} tool schemas were not inspected because registration failed.`, path: `plugins.entries.${plugin.id}`, target: plugin.id, requirement: plugin.error ?? "plugin-registration-failed", fixHint: "Fix or disable the plugin, then rerun doctor." }); } for (const frame of frames) { const { agentId, agentDir, workspaceDir, modelRef, model } = frame; const collectForAgent = async () => { findings.push(...await withPluginRuntimeRegistryScope(inspection?.registry, () => collectAgentRuntimeToolSchemaFindings({ ...frame, cfg }))); if (!shouldCreateBundleMcpRuntimeForAttempt({ toolsEnabled: true })) return; const fullMcpConfig = loadSessionMcpConfig({ workspaceDir, cfg, logDiagnostics: false }); if (deferMcpProbes) { for (const serverName of Object.keys(fullMcpConfig.loaded.mcpServers)) { if (deferredServers.has(serverName)) continue; deferredServers.add(serverName); findings.push({ checkId: "core/doctor/runtime-tool-schemas", severity: "warning", message: `MCP server "${sanitizeTerminalText(serverName)}" was not started for update validation. Run \`openclaw doctor --lint --only core/doctor/runtime-tool-schemas\` after the update to inspect its tools.`, path: `mcp.servers.${serverName}` }); } return; } const safeServerNamesByServer = assignSafeServerNames(Object.keys(fullMcpConfig.loaded.mcpServers)); const { requesterScopedServerNames } = partitionMcpServersByConnectionScope(fullMcpConfig.loaded.mcpServers); for (const serverName of requesterScopedServerNames) { if (reportedRequesterScopedServers.has(serverName)) continue; const diagnostic = { serverName, safeServerName: safeServerNamesByServer.get(serverName) ?? serverName, launchSummary: "requester-scoped connection", message: "authenticated requester context required" }; if (shouldReportBundleMcpRuntimeDiagnostic({ cfg, agentId, modelRef, diagnostic })) { findings.push(bundleMcpRequesterInspectionFinding(serverName)); reportedRequesterScopedServers.add(serverName); } } const excludeServerNames = new Set(requesterScopedServerNames); for (const [serverName, server] of Object.entries(fullMcpConfig.loaded.mcpServers)) { if (excludeServerNames.has(serverName) || server.auth !== "oauth") continue; excludeServerNames.add(serverName); const diagnostic = { serverName, safeServerName: safeServerNamesByServer.get(serverName) ?? serverName, launchSummary: "OAuth inspection deferred", message: "OAuth refresh requires durable credential ownership" }; if (!reportedBundleRuntimeDiagnostics.has(serverName) && shouldReportBundleMcpRuntimeDiagnostic({ cfg, agentId, modelRef, diagnostic })) { findings.push({ checkId: "core/doctor/runtime-tool-schemas", severity: "info", message: `Configured MCP server "${serverName}" was not probed during read-only inspection because OAuth may rotate external credentials.`, path: `mcp.servers.${serverName}`, fixHint: "For configured servers, run `openclaw mcp probe <name>` against the serving configuration. Validate plugin-provided or agent-local MCP servers from an authenticated serving-agent turn so refreshed credentials persist with their owner." }); reportedBundleRuntimeDiagnostics.add(serverName); } } const runtimeContext = loadSessionMcpConfig({ workspaceDir, cfg, logDiagnostics: false, excludeServerNames, safeServerNamesByServer }).fingerprint; if (!bundleRuntimeByContext.has(runtimeContext) && !bundleRuntimeLoadErrorsByContext.has(runtimeContext)) try { const { createBundleMcpToolRuntime } = await import("./agent-bundle-mcp-tools-Bs-8c3N1.mjs"); bundleRuntimeByContext.set(runtimeContext, await createBundleMcpToolRuntime({ workspaceDir, agentDir, cfg, excludeServerNames, safeServerNamesByServer })); } catch (error) { bundleRuntimeLoadErrorsByContext.set(runtimeContext, bundleMcpRuntimeLoadFailureFinding(error)); } const bundleRuntimeLoadError = bundleRuntimeLoadErrorsByContext.get(runtimeContext); if (bundleRuntimeLoadError) { if (!reportedBundleRuntimeLoadErrors.has(runtimeContext)) { findings.push(bundleRuntimeLoadError); reportedBundleRuntimeLoadErrors.add(runtimeContext); } return; } const bundleRuntime = bundleRuntimeByContext.get(runtimeContext); if (bundleRuntime) { const policyActiveDiagnostics = (bundleRuntime.diagnostics ?? []).filter((diagnostic) => shouldReportBundleMcpRuntimeDiagnostic({ cfg, agentId, modelRef, diagnostic })); for (const diagnostic of policyActiveDiagnostics) { if (reportedBundleRuntimeDiagnostics.has(diagnostic.serverName)) continue; findings.push(bundleMcpRuntimeDiagnosticFinding(diagnostic)); reportedBundleRuntimeDiagnostics.add(diagnostic.serverName); } findings.push(...await collectBundleMcpRuntimeToolSchemaFindings({ bundleRuntime, cfg, agentId, workspaceDir, modelRef, model })); } }; await runWithPluginMetadataSnapshot({ config: cfg, workspaceDir }, collectForAgent); } } finally { const cleanup = await Promise.allSettled([...bundleRuntimeByContext.values()].map(async (runtime) => await runtime.dispose())); for (const outcome of cleanup) if (outcome.status === "rejected") findings.push({ checkId: "core/doctor/runtime-tool-schemas", severity: "error", message: "Configured MCP tool schema inspection could not confirm child-process cleanup.", path: "mcp.servers", requirement: formatErrorMessage(outcome.reason), fixHint: "Inspect or stop the configured MCP server processes, then rerun doctor." }); if (inspection && options.deferInspectionDisposal) { const resource = inspection; options.deferInspectionDisposal(() => resource.release()); } else try { await inspection?.release(); } catch (error) { findings.push({ checkId: "core/doctor/runtime-tool-schemas", severity: "warning", message: "Runtime tool schema inspection could not confirm plugin cleanup.", requirement: formatErrorMessage(error), fixHint: "Inspect the plugin cleanup error, then rerun doctor." }); } } return findings; } //#endregion export { collectRuntimeToolSchemaFindings };