UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

742 lines (741 loc) 25.8 kB
import { i as formatErrorMessage } from "./errors-BXgSefBE.js"; import "./agent-scope-MrLta7Pq.js"; import { u as normalizeAgentId } from "./session-key-B_NoIfpX.js"; import { c as resolveDefaultAgentId, n as listAgentIds, o as resolveAgentWorkspaceDir, s as resolveDefaultAgentDir, t as listAgentEntries } from "./agent-scope-config-CgCYpZfK.js"; import "./defaults-mDjiWzE5.js"; import { x as findModelInCatalog } from "./model-selection-shared-b32cUYts.js"; import { c as resolveDefaultModelForAgent } from "./model-selection-CA_65iXi.js"; import { n as loadModelCatalog } from "./model-catalog-0-HKr2yW.js"; import { h as normalizeToolName, i as collectExplicitAllowlist } from "./tool-policy-CpBMaMTY.js"; import { n as resolveEffectiveToolPolicy } from "./agent-tools.policy-dtD_g0G1.js"; import { h as supportsModelTools } from "./openai-transport-stream-BVQx4NgZ.js"; import { i as getPluginToolMeta, o as setPluginToolMeta } from "./tools-oBw0X8xm.js"; import { n as createBundleMcpToolRuntime } from "./agent-bundle-mcp-materialize-NJW_G1cx.js"; import "./agent-bundle-mcp-tools-tGjdbeEf.js"; import { t as createOpenClawCodingTools } from "./agent-tools-uBjiioJ8.js"; import { t as buildWorkspaceSkillStatus } from "./status-Dlj2eT9o.js"; import { r as inspectRuntimeToolInputSchemas } from "./tool-schema-projection-DRzFBr6V.js"; import { n as normalizeAgentRuntimeTools } from "./tools-C5Dgo3sN.js"; import { t as applyFinalEffectiveToolPolicy } from "./effective-tool-policy-B_2ZMFOZ.js"; import { a as shouldCreateBundleMcpRuntimeForAttempt } from "./attempt-tool-construction-plan-C32M6pQQ.js"; import { t as collectUnavailableAgentSkills } from "./doctor-skills-core-N-TJqicN.js"; //#region src/flows/doctor-core-checks.runtime.ts const PROVIDER_CATALOG_ORDERS = [ "simple", "profile", "paired", "late" ]; const PROVIDER_CATALOG_ORDER_SET = new Set(PROVIDER_CATALOG_ORDERS); function detectUnavailableSkills(cfg) { const agentId = resolveDefaultAgentId(cfg); return collectUnavailableAgentSkills(buildWorkspaceSkillStatus(resolveAgentWorkspaceDir(cfg, agentId), { config: cfg, agentId })); } function providerCatalogPath(pluginId) { return pluginId ? `plugins.entries.${pluginId}` : void 0; } function providerCatalogProjectionFinding(params) { const path = providerCatalogPath(params.pluginId); return { checkId: "core/doctor/provider-catalog-projection", severity: "error", message: params.message, ...path ? { path } : {}, target: params.providerId, requirement: formatErrorMessage(params.error), fixHint: "Fix the plugin provider catalog hook or disable the plugin, then rerun doctor before relying on model discovery." }; } function isReadableRecord(value) { return value !== null && typeof value === "object"; } function isTrimmedNonEmptyString(value) { return typeof value === "string" && value.trim() === value && value.length > 0; } function hasProviderCatalogKey(params) { try { return { ok: true, present: params.key in params.value }; } catch (error) { return { ok: false, finding: providerCatalogProjectionFinding({ providerId: params.providerId, pluginId: params.pluginId, message: `Provider catalog ${params.providerId} result keys cannot be checked during doctor validation.`, error }) }; } } function readProviderCatalogValue(params) { if (!isReadableRecord(params.value)) return { ok: true, value: void 0 }; try { return { ok: true, value: params.value[params.key] }; } catch (error) { return { ok: false, finding: providerCatalogProjectionFinding({ providerId: params.providerId, pluginId: params.pluginId, message: `Provider catalog ${params.providerId} entry cannot be read during doctor validation.`, error }) }; } } function collectProviderCatalogModelFindings(params) { const findings = []; let models; try { if (!Array.isArray(params.models)) return [providerCatalogProjectionFinding({ providerId: params.providerId, pluginId: params.pluginId, message: `Provider catalog ${params.providerId} models value is invalid during doctor validation.`, error: /* @__PURE__ */ new Error("models must be an array") })]; models = params.models; } catch (error) { return [providerCatalogProjectionFinding({ providerId: params.providerId, pluginId: params.pluginId, message: `Provider catalog ${params.providerId} models value cannot be checked during doctor validation.`, error })]; } let modelEntries; try { modelEntries = []; let index = 0; for (const model of models) { modelEntries.push([index, model]); index += 1; } } catch (error) { return [providerCatalogProjectionFinding({ providerId: params.providerId, pluginId: params.pluginId, message: `Provider catalog ${params.providerId} model rows cannot be enumerated during doctor validation.`, error })]; } for (const [index, model] of modelEntries) { const modelId = readProviderCatalogValue({ value: model, key: "id", providerId: params.providerId, pluginId: params.pluginId }); if (!modelId.ok) { findings.push(modelId.finding); continue; } if (!isTrimmedNonEmptyString(modelId.value)) findings.push(providerCatalogProjectionFinding({ providerId: params.providerId, pluginId: params.pluginId, message: `Provider catalog ${params.providerId} model row ${index} has an invalid model id.`, error: /* @__PURE__ */ new Error("model id must be a non-empty trimmed string") })); const modelName = readProviderCatalogValue({ value: model, key: "name", providerId: params.providerId, pluginId: params.pluginId }); if (!modelName.ok) { findings.push(modelName.finding); continue; } if (modelName.value !== void 0 && typeof modelName.value !== "string") findings.push(providerCatalogProjectionFinding({ providerId: params.providerId, pluginId: params.pluginId, message: `Provider catalog ${params.providerId} model row ${index} has an invalid model name.`, error: /* @__PURE__ */ new Error("model name must be a string when present") })); } return findings; } function collectProviderCatalogResultFindings(params) { if (params.result == null) return []; if (!isReadableRecord(params.result)) return [providerCatalogProjectionFinding({ providerId: params.providerId, pluginId: params.pluginId, message: `Provider catalog ${params.providerId} result is invalid during doctor validation.`, error: /* @__PURE__ */ new Error("result must be an object") })]; const hasProvider = hasProviderCatalogKey({ value: params.result, key: "provider", providerId: params.providerId, pluginId: params.pluginId }); if (!hasProvider.ok) return [hasProvider.finding]; const provider = readProviderCatalogValue({ value: params.result, key: "provider", providerId: params.providerId, pluginId: params.pluginId }); if (!provider.ok) return [provider.finding]; if (hasProvider.present && !isReadableRecord(provider.value)) return [providerCatalogProjectionFinding({ providerId: params.providerId, pluginId: params.pluginId, message: `Provider catalog ${params.providerId} provider value is invalid during doctor validation.`, error: /* @__PURE__ */ new Error("provider must be an object") })]; if (isReadableRecord(provider.value)) { const models = readProviderCatalogValue({ value: provider.value, key: "models", providerId: params.providerId, pluginId: params.pluginId }); return models.ok ? collectProviderCatalogModelFindings({ ...params, models: models.value }) : [models.finding]; } const providers = readProviderCatalogValue({ value: params.result, key: "providers", providerId: params.providerId, pluginId: params.pluginId }); if (!providers.ok) return [providers.finding]; if (!isReadableRecord(providers.value)) return [providerCatalogProjectionFinding({ providerId: params.providerId, pluginId: params.pluginId, message: `Provider catalog ${params.providerId} result is invalid during doctor validation.`, error: /* @__PURE__ */ new Error("result must include provider or providers object") })]; let providerIds; try { providerIds = Object.keys(providers.value); } catch (error) { return [providerCatalogProjectionFinding({ providerId: params.providerId, pluginId: params.pluginId, message: `Provider catalog ${params.providerId} provider entries cannot be enumerated during doctor validation.`, error })]; } const findings = []; for (const providerId of providerIds) { if (!isTrimmedNonEmptyString(providerId)) { findings.push(providerCatalogProjectionFinding({ providerId: params.providerId, pluginId: params.pluginId, message: `Provider catalog ${params.providerId} provider key is invalid during doctor validation.`, error: /* @__PURE__ */ new Error("provider key must be a non-empty trimmed string") })); continue; } const providerConfig = readProviderCatalogValue({ value: providers.value, key: providerId, providerId, pluginId: params.pluginId }); if (!providerConfig.ok) { findings.push(providerConfig.finding); continue; } if (!isReadableRecord(providerConfig.value)) { findings.push(providerCatalogProjectionFinding({ providerId, pluginId: params.pluginId, message: `Provider catalog ${providerId} provider entry is invalid during doctor validation.`, error: /* @__PURE__ */ new Error("provider entry must be an object") })); continue; } const models = readProviderCatalogValue({ value: providerConfig.value, key: "models", providerId, pluginId: params.pluginId }); findings.push(...models.ok ? collectProviderCatalogModelFindings({ providerId, pluginId: params.pluginId, models: models.value }) : [models.finding]); } return findings; } function readProviderCatalogOrder(provider) { let order; try { order = provider.staticCatalog?.order ?? "late"; } catch (error) { return { ok: false, finding: providerCatalogProjectionFinding({ providerId: provider.id, pluginId: provider.pluginId, message: `Provider catalog ${provider.id} order cannot be read during doctor validation.`, error }) }; } if (PROVIDER_CATALOG_ORDER_SET.has(order)) return { ok: true, order }; return { ok: false, finding: providerCatalogProjectionFinding({ providerId: provider.id, pluginId: provider.pluginId, message: `Provider catalog ${provider.id} order is invalid during doctor validation.`, error: /* @__PURE__ */ new Error("order must be simple, profile, paired, or late") }) }; } function groupProviderCatalogsForDoctor(providers) { const findings = []; const byOrder = { simple: [], profile: [], paired: [], late: [] }; for (const provider of providers) { const order = readProviderCatalogOrder(provider); if (!order.ok) { findings.push(order.finding); byOrder.late.push(provider); continue; } byOrder[order.order].push(provider); } for (const order of PROVIDER_CATALOG_ORDERS) byOrder[order].sort((a, b) => a.label.localeCompare(b.label)); return { findings, byOrder }; } async function collectProviderCatalogProjectionFindings(cfg) { const { runProviderStaticCatalog } = await import("./provider-discovery-CneKP9FW.js"); const { resolvePluginProviders } = await import("./providers.runtime.js"); const env = process.env; const agentDir = resolveDefaultAgentDir(cfg); const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)); let providers; try { providers = resolvePluginProviders({ config: cfg, workspaceDir, env, includeUntrustedWorkspacePlugins: false }); } catch (error) { return [{ checkId: "core/doctor/provider-catalog-projection", severity: "error", message: "Provider catalog hooks could not be loaded for doctor validation.", requirement: formatErrorMessage(error), fixHint: "Fix plugin provider discovery loading, then rerun doctor." }]; } const findings = []; const grouped = groupProviderCatalogsForDoctor(providers); findings.push(...grouped.findings); for (const order of PROVIDER_CATALOG_ORDERS) for (const provider of grouped.byOrder[order]) { let staticCatalog; let staticCatalogRun; try { staticCatalog = provider.staticCatalog; staticCatalogRun = isReadableRecord(staticCatalog) ? staticCatalog.run : void 0; } catch (error) { findings.push(providerCatalogProjectionFinding({ providerId: provider.id, pluginId: provider.pluginId, message: `Provider catalog ${provider.id} static catalog hook cannot be read during doctor validation.`, error })); continue; } if (staticCatalog === void 0) continue; if (typeof staticCatalogRun !== "function") { findings.push(providerCatalogProjectionFinding({ providerId: provider.id, pluginId: provider.pluginId, message: `Provider catalog ${provider.id} static catalog hook is invalid during doctor validation.`, error: /* @__PURE__ */ new Error("static catalog run must be a function") })); continue; } let result; try { result = await runProviderStaticCatalog({ provider, config: cfg, agentDir, workspaceDir, env }); } catch (error) { findings.push(providerCatalogProjectionFinding({ providerId: provider.id, pluginId: provider.pluginId, message: `Provider catalog ${provider.id} failed during doctor validation.`, error })); continue; } findings.push(...collectProviderCatalogResultFindings({ providerId: provider.id, pluginId: provider.pluginId, result })); } return findings; } function buildDoctorRuntimeModel(params) { const provider = params.provider || "openai"; const id = params.modelId || "gpt-5.5"; const api = params.entry?.api ?? (provider === "openai" ? "openai-responses" : void 0); const baseUrl = params.entry?.baseUrl ?? (api === "openai-chatgpt-responses" ? "https://chatgpt.com/backend-api" : provider === "openai" ? "https://api.openai.com/v1" : void 0); return { ...params.entry, provider, id, name: params.entry?.name ?? id, ...api ? { api } : {}, ...baseUrl ? { baseUrl } : {} }; } function toolSchemaDiagnosticToFinding(params) { let tool; try { tool = params.tools[params.diagnostic.toolIndex]; } catch { tool = void 0; } const pluginId = tool ? getPluginToolMeta(tool)?.pluginId : void 0; const owner = pluginId ? ` from plugin ${pluginId}` : ""; const agent = `Agent ${params.agentId} `; const path = pluginId === "bundle-mcp" ? "mcp.servers" : pluginId ? `plugins.entries.${pluginId}` : `tools.${params.diagnostic.toolName}`; const fixHint = pluginId === "bundle-mcp" ? "Disable or update the offending MCP server/tool so its parameters are a JSON object schema, then rerun doctor." : "Disable or update the offending plugin/tool so its parameters are a JSON object schema, then rerun doctor."; return { checkId: "core/doctor/runtime-tool-schemas", severity: "error", message: `${agent}tool ${params.diagnostic.toolName}${owner} has an unsupported input schema for runtime projection.`, path, target: params.diagnostic.toolName, requirement: params.diagnostic.violations.join(", "), fixHint }; } function collectToolSchemaFindings(params) { return inspectRuntimeToolInputSchemas(params.tools).map((diagnostic) => toolSchemaDiagnosticToFinding({ agentId: params.agentId, tools: params.tools, diagnostic })); } function collectBundleMcpRuntimeToolSchemaFindings(params) { const activeBundleTools = applyFinalEffectiveToolPolicy({ bundledTools: params.bundleRuntime.tools, config: params.cfg, agentId: params.agentId, modelProvider: params.modelRef.provider, modelId: params.modelRef.model, warn: () => {}, toolPolicyAuditLogLevel: "debug" }); const preNormalizationFindings = []; let normalizedTools; try { normalizedTools = normalizeAgentRuntimeTools({ tools: activeBundleTools, provider: params.modelRef.provider, config: params.cfg, workspaceDir: params.workspaceDir, env: process.env, modelId: params.modelRef.model, modelApi: params.model.api, model: params.model, onPreNormalizationSchemaDiagnostics: (diagnostics, sourceTools) => { preNormalizationFindings.push(...diagnostics.map((diagnostic) => toolSchemaDiagnosticToFinding({ agentId: params.agentId, tools: sourceTools, diagnostic }))); } }); } catch (error) { return [...preNormalizationFindings, bundleMcpRuntimeNormalizationFailureFinding(error)]; } return [...preNormalizationFindings, ...collectToolSchemaFindings({ agentId: params.agentId, tools: normalizedTools })]; } function agentRuntimeToolLoadFailureFinding(params) { return { checkId: "core/doctor/runtime-tool-schemas", severity: "error", message: `Agent ${params.agentId} runtime tool schema validation could not load the runtime tool set.`, path: `agents.${params.agentId}.tools`, requirement: formatErrorMessage(params.error), fixHint: "Fix provider/plugin tool loading errors, then rerun doctor before relying on assistant tool startup." }; } function agentRuntimeToolNormalizationFailureFinding(params) { return { checkId: "core/doctor/runtime-tool-schemas", severity: "error", message: `Agent ${params.agentId} runtime tool schema validation could not normalize the runtime tool set.`, path: `agents.${params.agentId}.tools`, requirement: formatErrorMessage(params.error), fixHint: "Fix provider/plugin schema normalization errors, then rerun doctor before relying on assistant tool startup." }; } function collectAgentRuntimeToolSchemaFindings(params) { let tools; try { tools = createOpenClawCodingTools({ agentId: params.agentId, workspaceDir: params.workspaceDir, config: params.cfg, modelProvider: params.modelRef.provider, modelId: params.modelRef.model, modelApi: params.model.api, modelCompat: params.model.compat, modelContextWindowTokens: params.model.contextWindow, allowGatewaySubagentBinding: true, emitBeforeToolCallDiagnostics: false, toolPolicyAuditLogLevel: "debug" }); } catch (error) { return [agentRuntimeToolLoadFailureFinding({ agentId: params.agentId, error })]; } const preNormalizationFindings = []; let normalizedTools; try { normalizedTools = normalizeAgentRuntimeTools({ tools, provider: params.modelRef.provider, config: params.cfg, workspaceDir: params.workspaceDir, env: process.env, modelId: params.modelRef.model, modelApi: params.model.api, model: params.model, onPreNormalizationSchemaDiagnostics: (diagnostics, sourceTools) => { preNormalizationFindings.push(...diagnostics.map((diagnostic) => toolSchemaDiagnosticToFinding({ agentId: params.agentId, tools: sourceTools, diagnostic }))); } }); } catch (error) { return [...preNormalizationFindings, agentRuntimeToolNormalizationFailureFinding({ agentId: params.agentId, error })]; } return [...preNormalizationFindings, ...collectToolSchemaFindings({ agentId: params.agentId, tools: normalizedTools })]; } 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 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 = normalizeToolName(params.allowlistEntry); const serverPrefix = normalizeToolName(`${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, agentId: params.agentId, modelProvider: params.modelRef.provider, modelId: params.modelRef.model, warn: () => {}, toolPolicyAuditLogLevel: "debug" }).length > 0; } function filterPolicyActiveBundleMcpDiagnostics(params) { return params.diagnostics.filter((diagnostic) => shouldReportBundleMcpRuntimeDiagnostic({ cfg: params.cfg, agentId: params.agentId, modelRef: params.modelRef, diagnostic })); } function isAcpRuntimeAgent(cfg, agentId) { return listAgentEntries(cfg).find((candidate) => normalizeAgentId(candidate.id) === agentId)?.runtime?.type === "acp"; } async function collectRuntimeToolSchemaFindings(cfg) { const catalog = await loadModelCatalog({ config: cfg }); const findings = []; const bundleRuntimeByWorkspace = /* @__PURE__ */ new Map(); const bundleRuntimeLoadErrorsByWorkspace = /* @__PURE__ */ new Map(); const reportedBundleRuntimeLoadErrors = /* @__PURE__ */ new Set(); try { for (const agentId of listAgentIds(cfg)) { if (isAcpRuntimeAgent(cfg, agentId)) continue; const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId); const modelRef = resolveDefaultModelForAgent({ cfg, agentId, allowPluginNormalization: true }); const model = buildDoctorRuntimeModel({ entry: findModelInCatalog(catalog, modelRef.provider, modelRef.model), provider: modelRef.provider, modelId: modelRef.model }); if (!supportsModelTools(model)) continue; findings.push(...collectAgentRuntimeToolSchemaFindings({ cfg, agentId, workspaceDir, modelRef, model })); if (!shouldCreateBundleMcpRuntimeForAttempt({ toolsEnabled: true })) continue; if (!bundleRuntimeByWorkspace.has(workspaceDir) && !bundleRuntimeLoadErrorsByWorkspace.has(workspaceDir)) try { bundleRuntimeByWorkspace.set(workspaceDir, await createBundleMcpToolRuntime({ workspaceDir, cfg })); } catch (error) { bundleRuntimeLoadErrorsByWorkspace.set(workspaceDir, bundleMcpRuntimeLoadFailureFinding(error)); } const bundleRuntimeLoadError = bundleRuntimeLoadErrorsByWorkspace.get(workspaceDir); if (bundleRuntimeLoadError) { if (!reportedBundleRuntimeLoadErrors.has(workspaceDir)) { findings.push(bundleRuntimeLoadError); reportedBundleRuntimeLoadErrors.add(workspaceDir); } continue; } const bundleRuntime = bundleRuntimeByWorkspace.get(workspaceDir); if (bundleRuntime) { if (bundleRuntime.diagnostics && bundleRuntime.diagnostics.length > 0) { const policyActiveDiagnostics = filterPolicyActiveBundleMcpDiagnostics({ diagnostics: bundleRuntime.diagnostics, cfg, agentId, modelRef }); findings.push(...policyActiveDiagnostics.map(bundleMcpRuntimeDiagnosticFinding)); } findings.push(...collectBundleMcpRuntimeToolSchemaFindings({ bundleRuntime, cfg, agentId, workspaceDir, modelRef, model })); } } } finally { await Promise.all([...bundleRuntimeByWorkspace.values()].map((runtime) => runtime.dispose())); } return findings; } //#endregion export { collectProviderCatalogProjectionFindings, collectRuntimeToolSchemaFindings, detectUnavailableSkills };