UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

251 lines (250 loc) 12.4 kB
import { t as formatCliCommand } from "./command-format-C7YfyMTd.js"; import { E as tryResolveDefaultAgentId, m as resolveAgentWorkspaceDir, o as listAgentIds } from "./agent-scope-config-DcbEhP0R.js"; import { c as resolveOpenClawReleaseCohortVersion } from "./npm-registry-spec-CM_p1_uq.js"; import "./agent-scope-DbtJyKUL.js"; import { i as resolvePluginVersionDriftUpdateCommand } from "./plugin-version-drift-Clfu4emY.js"; import { d as loadTaskRegistryStateFromSqliteReadOnly } from "./task-registry.store.sqlite-BK18rNh0.js"; import { o as loadTaskFlowRegistryStateFromSqliteReadOnly } from "./task-flow-registry.store.sqlite-Dt5miOil.js"; import { t as note } from "./note-DqHk3fA1.js"; import { i as buildPluginCompatibilityWarnings } from "./status-DornsoQ9.js"; import { t as buildPluginRegistrySnapshotReport } from "./status-snapshot-kuewDRMh.js"; //#region src/commands/doctor-workspace-status.ts /** Doctor status summary for workspace skills, plugins, and task-flow recovery hints. */ const WORKSPACE_STATUS_CHECK_ID = "core/doctor/workspace-status"; function collectTaskFlowRecoveryFindings() { const flows = [...loadTaskFlowRegistryStateFromSqliteReadOnly().flows.values()].toSorted((left, right) => right.createdAt - left.createdAt); const tasksByFlowId = /* @__PURE__ */ new Map(); for (const task of loadTaskRegistryStateFromSqliteReadOnly().tasks.values()) { const flowId = task.parentFlowId?.trim(); if (flowId) { const linkedTasks = tasksByFlowId.get(flowId); if (linkedTasks) linkedTasks.push(task); else tasksByFlowId.set(flowId, [task]); } } return flows.flatMap((flow) => { const linkedTasks = tasksByFlowId.get(flow.flowId) ?? []; const findings = []; if (flow.syncMode === "managed" && flow.status === "running" && linkedTasks.length === 0 && flow.waitJson === void 0) findings.push({ flowId: flow.flowId, message: `${flow.flowId}: running managed TaskFlow has no linked tasks or wait state; inspect or cancel it manually.` }); if (flow.endedAt == null && flow.status === "blocked" && flow.blockedTaskId && !linkedTasks.some((task) => task.taskId === flow.blockedTaskId)) findings.push({ flowId: flow.flowId, message: `${flow.flowId}: blocked TaskFlow points at missing task ${flow.blockedTaskId}; inspect before retrying.` }); return findings; }); } function noteFlowRecoveryHints() { const suspicious = collectTaskFlowRecoveryFindings(); if (suspicious.length === 0) return; note([ ...suspicious.slice(0, 5).map((finding) => finding.message), suspicious.length > 5 ? `...and ${suspicious.length - 5} more.` : null, `Inspect: ${formatCliCommand("openclaw tasks flow show <flow-id>")}`, `Cancel: ${formatCliCommand("openclaw tasks flow cancel <flow-id>")}` ].filter((line) => Boolean(line)).join("\n"), "TaskFlow recovery"); } function pluginVersionDriftToHealthFindings(drift, runningGatewayVersion) { if (!drift) return []; if (drift.drifts.length === 0) { if (!isGatewayRestartPending(drift, runningGatewayVersion)) return []; return [{ checkId: WORKSPACE_STATUS_CHECK_ID, severity: "warning", message: `Active official plugins match post-restart OpenClaw ${drift.gatewayVersion}, but the running Gateway is ${runningGatewayVersion}.`, path: "plugins", requirement: "plugin-version-gateway-restart", fixHint: formatCliCommand("openclaw gateway restart") }]; } return drift.drifts.map((entry) => { const updateCommand = resolvePluginVersionDriftUpdateCommand(entry); const targetResolution = entry.targetResolution; const targetError = targetResolution?.status === "unresolved" ? targetResolution.error : "npm registry target was not resolved"; return { checkId: WORKSPACE_STATUS_CHECK_ID, severity: "warning", message: `Plugin ${entry.pluginId} is ${entry.installedVersion}, but a Gateway restart will load OpenClaw ${drift.gatewayVersion}.${runningGatewayVersion ? ` The running Gateway is ${runningGatewayVersion}.` : ""}${updateCommand ? "" : ` Repair target resolution failed: ${targetError}.`}`, path: `plugins.entries.${entry.pluginId}`, target: entry.pluginId, requirement: "plugin-version-drift", fixHint: updateCommand ? `${formatCliCommand(updateCommand)} && ${formatCliCommand("openclaw gateway restart")}` : `No install command generated; retry openclaw doctor after checking registry availability (${targetError}).` }; }); } function isGatewayRestartPending(drift, runningGatewayVersion) { return Boolean(runningGatewayVersion && resolveOpenClawReleaseCohortVersion(runningGatewayVersion) !== resolveOpenClawReleaseCohortVersion(drift.gatewayVersion)); } function pluginVersionReadinessToHealthFindings(readiness) { if (!readiness) return []; if (readiness.status === "resolved") return pluginVersionDriftToHealthFindings(readiness.report, readiness.runningGatewayVersion); return [{ checkId: WORKSPACE_STATUS_CHECK_ID, severity: "warning", message: `Could not check plugin restart readiness: ${readiness.reason}`, path: "plugins", requirement: "plugin-version-restart-readiness", fixHint: "Repair the Gateway service installation, then rerun openclaw doctor before restarting." }]; } function pluginCompatibilityWarningToHealthFinding(message) { return { checkId: WORKSPACE_STATUS_CHECK_ID, severity: "warning", message, path: "plugins", requirement: "plugin-compatibility", fixHint: "Update or replace the plugin so it no longer depends on legacy compatibility paths." }; } function pluginDiagnosticToHealthFinding(diagnostic, message = diagnostic.message) { return { checkId: WORKSPACE_STATUS_CHECK_ID, severity: diagnostic.level === "error" ? "error" : "warning", message, ...diagnostic.pluginId ? { path: `plugins.entries.${diagnostic.pluginId}` } : {}, ...diagnostic.pluginId ? { target: diagnostic.pluginId } : {}, ...diagnostic.source ? { source: diagnostic.source } : {}, ...diagnostic.code ? { requirement: diagnostic.code } : { requirement: "plugin-diagnostic" } }; } function taskFlowRecoveryToHealthFinding(finding) { return { checkId: WORKSPACE_STATUS_CHECK_ID, severity: "warning", message: finding.message, path: "tasks.flows", target: finding.flowId, requirement: "taskflow-recovery", fixHint: [formatCliCommand(`openclaw tasks flow show ${finding.flowId}`), formatCliCommand(`openclaw tasks flow cancel ${finding.flowId}`)].join(" or ") }; } function collectWorkspaceStatusHealthFindings(cfg, options = {}) { const agentIds = listAgentIds(cfg); const scopes = agentIds.map((agentId) => ({ agentId, workspaceDir: resolveAgentWorkspaceDir(cfg, agentId) })); const workspaceFindings = []; for (const { agentId, workspaceDir } of scopes) { const collectForWorkspace = () => { const findings = []; const prefix = agentIds.length > 1 ? `Agent "${agentId}": ` : ""; const pluginRegistry = buildPluginRegistrySnapshotReport({ config: cfg, workspaceDir }); const compatibilityWarnings = buildPluginCompatibilityWarnings({ config: cfg, workspaceDir, report: pluginRegistry }); for (const message of compatibilityWarnings) findings.push(pluginCompatibilityWarningToHealthFinding(`${prefix}${message}`)); for (const diagnostic of pluginRegistry.diagnostics) findings.push(pluginDiagnosticToHealthFinding(diagnostic, `${prefix}${diagnostic.message}`)); return findings; }; workspaceFindings.push(...options.runWithPluginMetadataSnapshot ? options.runWithPluginMetadataSnapshot({ config: cfg, workspaceDir }, collectForWorkspace) : collectForWorkspace()); } return [ ...pluginVersionReadinessToHealthFindings(options.pluginVersionReadiness), ...workspaceFindings, ...collectTaskFlowRecoveryFindings().map(taskFlowRecoveryToHealthFinding) ]; } function notePluginVersionReadiness(readiness) { if (!readiness) return; if (readiness.status === "unresolved") { const running = readiness.runningGatewayVersion ? `\nRunning Gateway: OpenClaw ${readiness.runningGatewayVersion}` : ""; note(`${readiness.reason}${running}\nRepair the Gateway service installation, then rerun openclaw doctor before restarting.`, "Plugin restart readiness"); return; } const drift = readiness.report; if (drift.drifts.length === 0) { if (!isGatewayRestartPending(drift, readiness.runningGatewayVersion)) return; note([ `Running Gateway: OpenClaw ${readiness.runningGatewayVersion}`, `Active official plugins match post-restart OpenClaw ${drift.gatewayVersion}.`, `Fix: ${formatCliCommand("openclaw gateway restart")}.` ].join("\n"), "Plugin restart readiness"); return; } const singleDrift = drift.drifts.length === 1 ? drift.drifts[0] : void 0; const repairs = drift.drifts.map((entry) => ({ entry, command: resolvePluginVersionDriftUpdateCommand(entry) })); const updateCommands = repairs.map(({ command }) => command).filter((command) => Boolean(command)).map((command) => formatCliCommand(command)); const unresolvedRepairs = repairs.filter(({ command }) => !command); const lines = [ ...readiness.runningGatewayVersion ? [`Running Gateway: OpenClaw ${readiness.runningGatewayVersion}`] : [], `${drift.drifts.length} active official plugin${drift.drifts.length === 1 ? "" : "s"} not on post-restart OpenClaw ${drift.gatewayVersion}`, ...drift.drifts.map((entry) => { const sourceLabel = entry.source === "clawhub" ? "clawhub" : "npm"; return `- ${entry.pluginId}: ${entry.installedVersion} (${sourceLabel}) -> expected ${drift.gatewayVersion}`; }), ...unresolvedRepairs.map(({ entry }) => { const targetResolution = entry.targetResolution; const detail = targetResolution?.status === "unresolved" ? targetResolution.error : "npm registry target was not resolved"; return `Repair target resolution failed for ${entry.pluginId}: ${detail}. No install command generated.`; }), singleDrift && updateCommands.length === 1 ? `Fix: ${updateCommands[0]} && ${formatCliCommand("openclaw gateway restart")}.` : updateCommands.length > 0 ? [ "Fix each drifted plugin:", ...updateCommands.map((command) => `- ${command}`), ...unresolvedRepairs.length === 0 ? [`Then run ${formatCliCommand("openclaw gateway restart")}.`] : [] ].join("\n") : null ]; note(lines.filter((line) => Boolean(line)).join("\n"), "Plugin restart readiness"); } /** Emits plugin and TaskFlow recovery problem notes for doctor. */ function noteWorkspaceStatus(cfg, options = {}) { const defaultAgentId = tryResolveDefaultAgentId(cfg); const agentIds = listAgentIds(cfg); const scopes = agentIds.map((agentId) => ({ agentId, workspaceDir: resolveAgentWorkspaceDir(cfg, agentId) })); for (const { agentId, workspaceDir } of scopes) { const noteForWorkspace = () => { const prefix = agentIds.length > 1 ? `Agent "${agentId}":\n` : ""; const pluginRegistry = buildPluginRegistrySnapshotReport({ config: cfg, workspaceDir }); const errored = pluginRegistry.plugins.filter((plugin) => plugin.status === "error").toSorted((a, b) => a.id.localeCompare(b.id)); if (errored.length > 0) { const lines = [`${prefix}Errors: ${errored.length}`, `- ${errored.slice(0, 10).map((plugin) => plugin.id).join("\n- ")}${errored.length > 10 ? "\n- ..." : ""}`]; note(lines.join("\n"), "Plugins"); } const compatibilityWarnings = buildPluginCompatibilityWarnings({ config: cfg, workspaceDir, report: pluginRegistry }); if (compatibilityWarnings.length > 0) note(`${prefix}${compatibilityWarnings.map((line) => `- ${line}`).join("\n")}`, "Plugin compatibility"); if (pluginRegistry.diagnostics.length > 0) { const lines = pluginRegistry.diagnostics.map((diag) => { const level = diag.level.toUpperCase(); const plugin = diag.pluginId ? ` ${diag.pluginId}` : ""; const source = diag.source ? ` (${diag.source})` : ""; return `- ${level}${plugin}: ${diag.message}${source}`; }); note(`${prefix}${lines.join("\n")}`, "Plugin diagnostics"); } }; if (options.runWithPluginMetadataSnapshot) options.runWithPluginMetadataSnapshot({ config: cfg, workspaceDir }, noteForWorkspace); else noteForWorkspace(); } notePluginVersionReadiness(options.pluginVersionReadiness); noteFlowRecoveryHints(); return { workspaceDir: scopes.find((scope) => scope.agentId === defaultAgentId)?.workspaceDir ?? scopes[0]?.workspaceDir }; } //#endregion export { collectWorkspaceStatusHealthFindings, noteWorkspaceStatus };