UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

153 lines (152 loc) • 8.86 kB
import { c as normalizeOptionalLowercaseString, l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-1VuiLaaR.mjs"; import { i as resolveBundledPluginInstallCommandHint } from "./bundled-sources-D86vbSpz.mjs"; import { a as toAcpRuntimeError } from "./errors-1GV1HisX.mjs"; import { t as formatAcpRuntimeErrorText } from "./error-text-CMbNX9RD.mjs"; import "./errors-CEQyfSPE.mjs"; import { n as getAcpSessionManager } from "./manager-sn_kcSXp.mjs"; import { i as requireAcpRuntimeBackend, t as getAcpRuntimeBackend } from "./registry-CwtcY73s.mjs"; import { c as readAcpSessionEntryAsync, u as listAcpSessionEntries } from "./session-meta-CSN8P5u2.mjs"; import { o as listSessionBindingsBySessionsAsync } from "./session-binding-service-wvWqRl2A.mjs"; import { n as commandReply } from "./command-gates-DXl2hvMx.mjs"; import { r as resolveAcpCommandBindingContext, t as resolveAcpTargetSessionKey } from "./targets-Cn3mUj24.mjs"; import { n as ACP_DOCTOR_USAGE, p as formatAcpCapabilitiesText, r as ACP_INSTALL_USAGE, s as ACP_SESSIONS_USAGE } from "./shared-CsI73TMh.mjs"; import { existsSync } from "node:fs"; import path from "node:path"; //#region src/auto-reply/reply/commands-acp/install-hints.ts /** Resolves the install command hint shown when the configured ACP backend is missing. */ function resolveAcpInstallCommandHint(cfg) { const configured = normalizeOptionalString(cfg.acp?.runtime?.installCommand); if (configured) return configured; const workspaceDir = process.cwd(); const backendId = normalizeOptionalLowercaseString(cfg.acp?.backend) ?? "acpx"; if (backendId === "acpx") { const workspaceLocalPath = path.join(workspaceDir, "extensions", "acpx"); if (existsSync(workspaceLocalPath)) return `openclaw plugins install ${workspaceLocalPath}`; const bundledInstallHint = resolveBundledPluginInstallCommandHint({ pluginId: backendId, workspaceDir }); if (bundledInstallHint) { const localPath = bundledInstallHint.replace(/^openclaw plugins install /u, ""); const resolvedLocalPath = path.resolve(localPath); const relativeToWorkspace = path.relative(workspaceDir, resolvedLocalPath); if ((relativeToWorkspace.length === 0 || !relativeToWorkspace.startsWith("..") && !path.isAbsolute(relativeToWorkspace)) && existsSync(resolvedLocalPath)) return bundledInstallHint; } return "openclaw plugins install acpx"; } return `Install and enable the plugin that provides ACP backend "${backendId}".`; } //#endregion //#region src/auto-reply/reply/commands-acp/diagnostics.ts async function handleAcpDoctorAction(params, restTokens) { if (restTokens.length > 0) return commandReply(`⚠️ ${ACP_DOCTOR_USAGE}`); const backendId = normalizeOptionalString(params.cfg.acp?.backend) ?? "acpx"; const installHint = resolveAcpInstallCommandHint(params.cfg); const registeredBackend = getAcpRuntimeBackend(backendId); const managerSnapshot = getAcpSessionManager().getObservabilitySnapshot(); const lines = [ "ACP doctor:", "-----", `configuredBackend: ${backendId}`, `activeRuntimeSessions: ${managerSnapshot.runtimeCache.activeSessions}`, `runtimeIdleTtlMs: ${managerSnapshot.runtimeCache.idleTtlMs}`, `evictedIdleRuntimes: ${managerSnapshot.runtimeCache.evictedTotal}`, `activeTurns: ${managerSnapshot.turns.active}`, `queueDepth: ${managerSnapshot.turns.queueDepth}`, `turnLatencyMs: avg=${managerSnapshot.turns.averageLatencyMs}, max=${managerSnapshot.turns.maxLatencyMs}`, `turnCounts: completed=${managerSnapshot.turns.completed}, failed=${managerSnapshot.turns.failed}` ]; const errorStatsText = Object.entries(managerSnapshot.errorsByCode).map(([code, count]) => `${code}=${count}`).join(", ") || "(none)"; lines.push(`errorCodes: ${errorStatsText}`); lines.push(`registeredBackend: ${registeredBackend ? registeredBackend.id : "(none)"}`); const allow = params.cfg.plugins?.allow; const normalizedBackendId = normalizeLowercaseStringOrEmpty(backendId); const backendBlockedByAllowlist = Array.isArray(allow) && allow.length > 0 && Boolean(normalizedBackendId) && !allow.some((pluginId) => normalizeLowercaseStringOrEmpty(pluginId) === normalizedBackendId); if (backendBlockedByAllowlist) lines.push(`pluginActivation: blocked (${backendId} is missing from plugins.allow)`); if (registeredBackend?.runtime.doctor) try { const report = await registeredBackend.runtime.doctor(); lines.push(`runtimeDoctor: ${report.ok ? "ok" : "error"} (${report.message})`); if (report.code) lines.push(`runtimeDoctorCode: ${report.code}`); if (report.installCommand) lines.push(`runtimeDoctorInstall: ${report.installCommand}`); for (const detail of report.details ?? []) lines.push(`runtimeDoctorDetail: ${detail}`); } catch (error) { lines.push(`runtimeDoctor: error (${toAcpRuntimeError({ error, fallbackCode: "ACP_TURN_FAILED", fallbackMessage: "Runtime doctor failed." }).message})`); } try { const backend = requireAcpRuntimeBackend(backendId); const capabilities = backend.runtime.getCapabilities ? await backend.runtime.getCapabilities({}) : { controls: [], configOptionKeys: [] }; lines.push("healthy: yes"); lines.push(`capabilities: ${formatAcpCapabilitiesText(capabilities.controls ?? [])}`); if ((capabilities.configOptionKeys?.length ?? 0) > 0) lines.push(`configKeys: ${capabilities.configOptionKeys?.join(", ")}`); } catch (error) { const acpError = toAcpRuntimeError({ error, fallbackCode: "ACP_TURN_FAILED", fallbackMessage: "ACP backend doctor failed." }); lines.push("healthy: no"); lines.push(formatAcpRuntimeErrorText(acpError)); if (backendBlockedByAllowlist) lines.push(`next: add "${backendId}" to plugins.allow or unset plugins.allow.`); lines.push(`next: ${installHint}`); lines.push(`next: openclaw config set plugins.entries.${backendId}.enabled true`); if (normalizedBackendId === "acpx") lines.push("next: verify acpx is installed (`acpx --help`)."); } return commandReply(lines.join("\n")); } function handleAcpInstallAction(params, restTokens) { if (restTokens.length > 0) return commandReply(`⚠️ ${ACP_INSTALL_USAGE}`); const backendId = normalizeOptionalString(params.cfg.acp?.backend) ?? "acpx"; const installHint = resolveAcpInstallCommandHint(params.cfg); const lines = [ "ACP install:", "-----", `configuredBackend: ${backendId}`, `run: ${installHint}`, `then: openclaw config set plugins.entries.${backendId}.enabled true`, "then: /acp doctor" ]; return commandReply(lines.join("\n")); } async function handleAcpSessionsAction(params, restTokens) { if (restTokens.length > 0) return commandReply(ACP_SESSIONS_USAGE); const target = await resolveAcpTargetSessionKey({ commandParams: params }); if (!target.ok) return commandReply(`⚠️ ${target.error}`); const currentSessionKey = target.sessionKey; const bindingContext = resolveAcpCommandBindingContext(params); const normalizedChannel = bindingContext.channel; const normalizedAccountId = bindingContext.accountId || void 0; const currentEntry = params.command.senderIsOwner ? null : await readAcpSessionEntryAsync({ cfg: params.cfg, sessionKey: currentSessionKey, agentId: target.agentId, assertCurrent: params.command.assertOwnerCurrent }); params.command.assertOwnerCurrent?.(); const visibleEntries = params.command.senderIsOwner ? await listAcpSessionEntries({ cfg: params.cfg }) : currentEntry?.entry && currentEntry.acp ? [currentEntry] : []; params.command.assertOwnerCurrent?.(); const selectedEntries = visibleEntries.toSorted((a, b) => (b.entry?.updatedAt ?? 0) - (a.entry?.updatedAt ?? 0)).slice(0, 20); const bindingsBySession = await listSessionBindingsBySessionsAsync(selectedEntries.filter(({ entry, acp }) => entry && acp).map(({ storeSessionKey }) => storeSessionKey)); params.command.assertOwnerCurrent?.(); const rows = selectedEntries.map(({ storeSessionKey, agentId, entry, acp }) => { if (!entry || !acp) return ""; const bindingThreadId = (bindingsBySession.get(storeSessionKey) ?? []).find((binding) => (!normalizedChannel || binding.conversation.channel === normalizedChannel) && (!normalizedAccountId || binding.conversation.accountId === normalizedAccountId))?.conversation.conversationId; const marker = currentSessionKey === storeSessionKey && target.agentId === agentId ? "*" : " "; const label = normalizeOptionalString(entry.label) || acp.agent; const threadText = bindingThreadId ? `, thread:${bindingThreadId}` : ""; return `${marker} ${label} (${acp.mode}, ${acp.state}, backend:${acp.backend}${agentId ? `, owner:${agentId}` : ""}${threadText}) -> ${storeSessionKey}`; }).filter(Boolean); return commandReply([ "ACP sessions:", "-----", ...rows.length ? rows : ["(none)"] ].join("\n")); } //#endregion export { handleAcpDoctorAction, handleAcpInstallAction, handleAcpSessionsAction };