UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

970 lines (969 loc) 37.1 kB
import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js"; import { O as tryResolveSoleAgentId, i as listAgentEntries, m as resolveAgentWorkspaceDir, o as listAgentIds, u as resolveAgentDir } from "./agent-scope-config-DcbEhP0R.js"; import "./session-key-BnWWjqNc.js"; import { o as redactSensitiveUrlLikeString } from "./redact-sensitive-url-BN1NZvXG.js"; import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js"; import { n as sanitizeTerminalText } from "./safe-text-BGBqp1a4.js"; import { c as normalizeToolPolicyName } from "./tool-policy-shared-DIyS0iQC.js"; import { t as assignSafeServerNames } from "./agent-bundle-mcp-names-CP3ugHLh.js"; import { i as collectExplicitAllowlist } from "./tool-policy-Wi0C45cX.js"; import { x as findModelInCatalog } from "./model-selection-shared-BlLyx1r2.js"; import "./defaults-CdX9UGcX.js"; import "./agent-scope-DbtJyKUL.js"; import { t as resolveDefaultModelForAgent } from "./model-selection-config-BrdmmqKD.js"; import { n as isGatewaySecretRefUnavailableError } from "./credentials-CXbS4PS0.js"; import { a as readGatewayServiceState, o as resolveGatewayService } from "./service-Cc6NX6Jm.js"; import { n as getSystemdCgroupHygieneSummary } from "./service-runtime-C1Las5eF.js"; import { a as buildGatewayProbeConnectionDetails, o as callGateway, p as isGatewayCredentialsRequiredError } from "./call-BWR5pPgw.js"; import { t as loadSessionMcpConfig } from "./agent-bundle-mcp-runtime-config-MN40lQtN.js"; import { i as partitionMcpServersByConnectionScope } from "./mcp-connection-resolver-FyG5JEPz.js"; import { n as resolveMcpAuthProfileId } from "./mcp-auth-profile--_BtN_eA.js"; import "./model-catalog-DOgUhUHe.js"; import { a as loadPreparedModelCatalog } from "./prepared-model-catalog-Bt4dYhnY.js"; import "./model-selection-di2kjKCB.js"; import { a as setPluginToolMeta, r as getPluginToolMeta } from "./tool-metadata-B5aqo73s.js"; import { n as resolveEffectiveToolPolicy } from "./agent-tools.policy-NF_9Y4S4.js"; import { t as resolveConversationCapabilityProfile } from "./conversation-capability-profile-B6PkMXFD.js"; import { r as inspectRuntimeToolInputSchemas } from "./tool-schema-projection-a_T2Sr4Q.js"; import { n as normalizeAgentRuntimeTools } from "./tools-CAIhAR2v.js"; import { t as applyFinalEffectiveToolPolicy } from "./effective-tool-policy-Bco8DCTH.js"; import { a as shouldCreateBundleMcpRuntimeForAttempt } from "./attempt-tool-construction-plan-CRC43c_k.js"; import { n as supportsModelTools } from "./model-tool-support-DIQSEumC.js"; import { t as buildWorkspaceSkillStatus } from "./status-BDd5nSbi.js"; import { n as formatLocalAudioSelection, r as inspectLocalAudioSelection } from "./local-audio-YEyS3BND.js"; import { t as collectUnavailableAgentSkills } from "./doctor-skills-core-DZQVCoNO.js"; import { t as hasActiveGatewayExecCredential } from "./doctor-gateway-exec-credential-jPkNF-y8.js"; import { t as scrubDoctorErrorMessage } from "./doctor-error-message-PVjhuNzO.js"; import { c as gatewayConnectErrorWasRateLimited, r as GATEWAY_HEALTH_RATE_LIMITED_MESSAGE } from "./gateway-health-auth-diagnostic-6ZBqs-mo.js"; import { s as shouldManageGatewayService } from "./doctor-service-repair-policy-S9bPaCiq.js"; import { t as projectDoctorSecretRuntimeDegradations } from "./doctor-secret-runtime-degradation-auwIRP9K.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 formatGatewayHealthDiagnostic(value) { const raw = value instanceof Error ? value.message : String(value); return scrubDoctorErrorMessage(sanitizeTerminalText(redactSensitiveUrlLikeString(raw))); } function detectUnavailableSkills(cfg, workspaceDir) { const report = buildWorkspaceSkillStatus(workspaceDir, { config: cfg, agentId: tryResolveSoleAgentId(cfg) }); return collectUnavailableAgentSkills(report); } async function collectLocalAudioAccelerationFindings() { const selection = await inspectLocalAudioSelection(); const available = selection.candidates.filter((candidate) => candidate.available); if (available.length === 0) return []; const summary = formatLocalAudioSelection(selection); if (summary) return [{ checkId: "core/doctor/local-audio-acceleration", severity: "info", message: `Local STT auto-selection: ${summary}.`, path: "tools.media.models" }]; return [{ checkId: "core/doctor/local-audio-acceleration", severity: "info", message: `Local STT commands were found but none are ready for auto-selection: ${available.map((candidate) => `${candidate.command}: ${candidate.reason}`).join("; ")}.`, path: "tools.media.models", fixHint: "Install the matching local model/runtime, or configure an audio-capable tools.media.models CLI entry." }]; } async function collectGatewayHealthFindings(ctx) { const mode = ctx.cfg.gateway?.mode === "remote" ? "remote" : "local"; const gatewayPath = mode === "remote" ? "gateway.remote.url" : "gateway.mode"; let probeDetails; const warning = (message, fixHint) => ({ checkId: "core/doctor/gateway-health", severity: "warning", message, path: probeDetails || mode === "remote" ? gatewayPath : "gateway", ...probeDetails ? { target: formatGatewayHealthDiagnostic(probeDetails.url) } : {}, fixHint }); try { probeDetails = await buildGatewayProbeConnectionDetails({ config: ctx.cfg, configPath: ctx.configPath }); if (ctx.allowExecSecretRefs !== true && await hasActiveGatewayExecCredential({ cfg: ctx.cfg, env: ctx.env, targetUrl: probeDetails.url })) return [warning("Authenticated Gateway health inspection was intentionally skipped because an active credential uses an exec SecretRef.", "Rerun `openclaw doctor --lint --only core/doctor/gateway-health --allow-exec` to permit configured secret execution.")]; const status = await callGateway({ method: "status", params: { includeChannelSummary: false }, timeoutMs: 3e3, sharedStateMode: "read-only", config: ctx.cfg, configPath: ctx.configPath, tlsFingerprint: probeDetails.tlsFingerprint, preauthHandshakeTimeoutMs: probeDetails.preauthHandshakeTimeoutMs }); return projectDoctorSecretRuntimeDegradations(status).map((owner) => ({ checkId: "core/doctor/gateway-health", severity: "warning", message: `Secret runtime degradation: ${owner.message}`, path: owner.path, target: owner.target, fixHint: `Retry: ${owner.retryHint}` })); } catch (error) { if (!probeDetails) return [warning(`Gateway health inspection could not be prepared: ${formatGatewayHealthDiagnostic(error)}`, "Fix Gateway connection configuration, then rerun `openclaw doctor --lint --only core/doctor/gateway-health`.")]; const diagnostic = gatewayConnectErrorWasRateLimited(error) ? { message: GATEWAY_HEALTH_RATE_LIMITED_MESSAGE, fixHint: "Wait for the temporary authentication lockout to expire, then rerun doctor." } : isGatewayCredentialsRequiredError(error) || isGatewaySecretRefUnavailableError(error) ? { message: "Gateway status could not be inspected because this CLI has no usable token/password or paired device token for read-scope RPCs.", fixHint: "Configure the Gateway token/password or pair this device, then rerun the selected health check." } : { message: `Gateway status could not be inspected: ${formatGatewayHealthDiagnostic(error)}`, fixHint: mode === "remote" ? "Verify the remote Gateway URL, network path, TLS settings, and credentials." : "Inspect the service with `openclaw gateway status --deep`, or run `openclaw doctor` for guided checks." }; return [warning(diagnostic.message, diagnostic.fixHint)]; } } function gatewayRuntimeStatus(runtime) { return runtime?.status ?? runtime?.state ?? runtime?.subState; } async function collectGatewayDaemonFindings(ctx) { if (ctx.cfg.gateway?.mode === "remote" || !await shouldManageGatewayService()) return []; const service = resolveGatewayService(); const state = await readGatewayServiceState(service, { env: process.env }); const findings = []; if (state.loadState.status === "unknown") { findings.push({ checkId: "core/doctor/gateway-daemon", severity: "warning", message: `Gateway service status could not be determined: ${state.loadState.detail}`, path: state.command?.sourcePath, target: service.label, fixHint: "Run `openclaw gateway status --deep`, restore service-manager access, and retry." }); return findings; } if (!state.installed) { findings.push({ checkId: "core/doctor/gateway-daemon", severity: "warning", message: "Gateway service is not installed.", path: "gateway.mode", target: service.label, fixHint: "Run `openclaw gateway install` to install the service." }); return findings; } if (state.loadState.status === "not-loaded") findings.push({ checkId: "core/doctor/gateway-daemon", severity: "warning", message: "Gateway service is installed but not loaded.", path: state.command?.sourcePath, target: service.label, fixHint: "Start the installed service with `openclaw gateway start`." }); const status = gatewayRuntimeStatus(state.runtime); if (state.loadState.status === "loaded" && !state.running) findings.push({ checkId: "core/doctor/gateway-daemon", severity: "warning", message: status ? `Gateway service runtime is ${status}, not running.` : "Gateway service is loaded but runtime status could not confirm it is running.", path: state.command?.sourcePath, target: service.label, fixHint: "Run `openclaw gateway status --deep` to inspect the service before choosing a recovery action." }); if (state.runtime?.missingGuiSession) findings.push({ checkId: "core/doctor/gateway-daemon", severity: "warning", message: "Gateway service cannot attach to the user GUI session.", path: state.command?.sourcePath, target: service.label, fixHint: state.runtime.detail ?? "Log into a GUI session, then rerun doctor." }); if (state.runtime?.missingSupervision || state.runtime?.missingUnit) findings.push({ checkId: "core/doctor/gateway-daemon", severity: "warning", message: "Gateway service supervision metadata is missing.", path: state.command?.sourcePath, target: service.label, fixHint: state.runtime.detail ?? "Reinstall or reload the Gateway service." }); const hygiene = getSystemdCgroupHygieneSummary(state.runtime?.systemd); if (hygiene) findings.push({ checkId: "core/doctor/gateway-daemon", severity: "warning", message: `Gateway systemd service has risky ${hygiene}.`, path: state.command?.sourcePath, target: service.label, fixHint: "Repair the systemd unit so stale child processes are cleaned up reliably." }); return findings; } 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, workspaceDir) { const { runProviderStaticCatalog } = await import("./provider-discovery--QYRxR2h.js"); const { resolvePluginProvidersCore } = await import("./providers.runtime.js"); const env = process.env; let providers; try { providers = resolvePluginProvidersCore({ 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 }); } 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.6-sol"; 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 collectNormalizedToolSchemaFindings(params) { const preNormalizationFindings = []; let normalizedTools; try { normalizedTools = normalizeAgentRuntimeTools({ tools: params.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, params.normalizationFailureFinding(error)]; } return [...preNormalizationFindings, ...collectToolSchemaFindings({ agentId: params.agentId, tools: normalizedTools })]; } function collectBundleMcpRuntimeToolSchemaFindings(params) { 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 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." }; } async function collectAgentRuntimeToolSchemaFindings(params) { let tools; try { const { createOpenClawCodingTools } = await import("./agent-tools-Nk45faPF.js"); 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 }); } catch (error) { return [agentRuntimeToolLoadFailureFinding({ agentId: params.agentId, error })]; } return collectNormalizedToolSchemaFindings({ agentId: params.agentId, tools, cfg: params.cfg, workspaceDir: params.workspaceDir, modelRef: params.modelRef, model: params.model, normalizationFailureFinding: (error) => agentRuntimeToolNormalizationFailureFinding({ agentId: params.agentId, error }) }); } 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; } 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, options) { const findings = []; 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(); try { for (const agentId of listAgentIds(cfg)) { if (isAcpRuntimeAgent(cfg, agentId)) continue; const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId); const collectForAgent = async () => { const agentDir = resolveAgentDir(cfg, agentId); const catalog = await loadPreparedModelCatalog({ config: cfg, agentId, agentDir, readOnly: true, providerDiscoveryProviderIds: [] }); 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)) return; findings.push(...await collectAgentRuntimeToolSchemaFindings({ cfg, agentId, workspaceDir, modelRef, model })); if (!shouldCreateBundleMcpRuntimeForAttempt({ toolsEnabled: true })) return; const fullMcpConfig = loadSessionMcpConfig({ workspaceDir, cfg, logDiagnostics: false }); 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); const staticMcpConfig = loadSessionMcpConfig({ workspaceDir, cfg, logDiagnostics: false, excludeServerNames, safeServerNamesByServer }); const credentialContext = Object.values(staticMcpConfig.loaded.mcpServers).some(resolveMcpAuthProfileId) ? agentDir : "shared"; const runtimeContext = `${staticMcpConfig.fingerprint}\0${credentialContext}`; if (!bundleRuntimeByContext.has(runtimeContext) && !bundleRuntimeLoadErrorsByContext.has(runtimeContext)) try { const { createBundleMcpToolRuntime } = await import("./agent-bundle-mcp-tools-FqLm3YjH.js"); 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) { if (bundleRuntime.diagnostics && bundleRuntime.diagnostics.length > 0) { const policyActiveDiagnostics = filterPolicyActiveBundleMcpDiagnostics({ diagnostics: bundleRuntime.diagnostics, cfg, agentId, modelRef }); for (const diagnostic of policyActiveDiagnostics) { if (reportedBundleRuntimeDiagnostics.has(diagnostic.serverName)) continue; findings.push(bundleMcpRuntimeDiagnosticFinding(diagnostic)); reportedBundleRuntimeDiagnostics.add(diagnostic.serverName); } } findings.push(...collectBundleMcpRuntimeToolSchemaFindings({ bundleRuntime, cfg, agentId, workspaceDir, modelRef, model })); } }; if (options?.runWithPluginMetadataSnapshot) await options.runWithPluginMetadataSnapshot({ config: cfg, workspaceDir }, collectForAgent); else await collectForAgent(); } } finally { await Promise.all([...bundleRuntimeByContext.values()].map((runtime) => runtime.dispose())); } return findings; } //#endregion export { collectGatewayDaemonFindings, collectGatewayHealthFindings, collectLocalAudioAccelerationFindings, collectProviderCatalogProjectionFindings, collectRuntimeToolSchemaFindings, detectUnavailableSkills };