openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
153 lines (152 loc) • 8.86 kB
JavaScript
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 };