UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

444 lines (443 loc) 23.4 kB
import { b as parseStrictPositiveInteger } from "./number-coercion-CJQ8TR--.js"; import "./parse-finite-number-Z7n6tXLk.js"; import { t as formatDocsLink } from "./links-CsLBrRff.js"; import { n as isRich, r as theme, t as colorize } from "./theme-vjDs9tao.js"; import { t as createLazyImportLoader } from "./lazy-promise-BONnzNfb.js"; import { n as inheritOptionFromParent } from "./command-options-Dhjl7AMa.js"; import { t as addGatewayServiceCommands } from "./register-service-commands-CAi8KNGt.js"; import { n as defaultRuntime } from "./runtime-B4lgFmsS.js"; import { t as formatHelpExamples } from "./help-format-CAcwboTs.js"; import { t as addGatewayRunCommand } from "./run-command-D3I2FhzG.js"; //#region src/cli/gateway-cli/register.ts const configModuleLoader = createLazyImportLoader(() => import("./read-best-effort-config.runtime.js")); const gatewayStatusModuleLoader = createLazyImportLoader(() => import("./gateway-status-CN0E6TDN.js")); const gatewayHealthModuleLoader = createLazyImportLoader(() => import("./health-B9v-C93Q.js")); const bonjourDiscoveryModuleLoader = createLazyImportLoader(() => import("./bonjour-discovery-CHH3o35w.js")); const wideAreaDnsModuleLoader = createLazyImportLoader(() => import("./widearea-dns-FoVN6BL_.js")); const healthStyleModuleLoader = createLazyImportLoader(() => import("./terminal-core/health-style.js")); const usageFormatModuleLoader = createLazyImportLoader(() => import("./usage-format-CCb6QRAg.js")); const stabilityBundleModuleLoader = createLazyImportLoader(() => import("./diagnostic-stability-bundle-Crx4XQNo.js")); const supportExportModuleLoader = createLazyImportLoader(() => import("./diagnostic-support-export-CdwHsX_g.js")); const daemonStatusGatherModuleLoader = createLazyImportLoader(() => import("./status.gather-DIr8hvwr.js")); function loadConfigModule() { return configModuleLoader.load(); } function loadGatewayStatusModule() { return gatewayStatusModuleLoader.load(); } function loadGatewayHealthModule() { return gatewayHealthModuleLoader.load(); } function loadBonjourDiscoveryModule() { return bonjourDiscoveryModuleLoader.load(); } function loadWideAreaDnsModule() { return wideAreaDnsModuleLoader.load(); } function loadHealthStyleModule() { return healthStyleModuleLoader.load(); } function loadUsageFormatModule() { return usageFormatModuleLoader.load(); } function loadStabilityBundleModule() { return stabilityBundleModuleLoader.load(); } function loadSupportExportModule() { return supportExportModuleLoader.load(); } function loadDaemonStatusGatherModule() { return daemonStatusGatherModuleLoader.load(); } function gatewayCallOpts(cmd) { return cmd.option("--url <url>", "Gateway WebSocket URL (defaults to gateway.remote.url when configured)").option("--token <token>", "Gateway token (if required)").option("--password <password>", "Gateway password (password auth)").option("--timeout <ms>", "Timeout in ms", "10000").option("--expect-final", "Wait for final response (agent)", false).option("--json", "Output JSON", false); } async function callGatewayCli(method, opts, params) { return (await import("./call-Cyb2qxBY.js")).callGatewayCli(method, opts, params); } async function runGatewayCommand(action, label, opts) { try { await action(); } catch (err) { if (opts?.json) { const { formatGatewayTransportErrorJson } = await import("./call-BEl78mhZ.js"); const payload = formatGatewayTransportErrorJson(err); if (payload) { defaultRuntime.writeJson(payload); defaultRuntime.exit(1); return; } } const message = String(err); defaultRuntime.error(label ? `${label}: ${message}` : message); defaultRuntime.exit(1); } } function parseDaysOption(raw, fallback = 30) { if (typeof raw === "number" && Number.isFinite(raw)) return Math.max(1, Math.floor(raw)); if (typeof raw === "string" && raw.trim() !== "") { const parsed = parseStrictPositiveInteger(raw); if (parsed !== void 0) return parsed; } return fallback; } function resolveGatewayRpcOptions(opts, command) { const parentToken = inheritOptionFromParent(command, "token"); const parentPassword = inheritOptionFromParent(command, "password"); return { ...opts, token: opts.token ?? parentToken, password: opts.password ?? parentPassword }; } async function renderCostUsageSummaryAsync(summary, days, rich) { const { formatTokenCount, formatUsd } = await loadUsageFormatModule(); const totalCost = formatUsd(summary.totals.totalCost) ?? "$0.00"; const totalTokens = formatTokenCount(summary.totals.totalTokens) ?? "0"; const lines = [colorize(rich, theme.heading, `Usage cost (${days} days)`), `${colorize(rich, theme.muted, "Total:")} ${totalCost} · ${totalTokens} tokens`]; if (summary.totals.missingCostEntries > 0) lines.push(`${colorize(rich, theme.muted, "Missing entries:")} ${summary.totals.missingCostEntries}`); const latest = summary.daily.at(-1); if (latest) { const latestCost = formatUsd(latest.totalCost) ?? "$0.00"; const latestTokens = formatTokenCount(latest.totalTokens) ?? "0"; lines.push(`${colorize(rich, theme.muted, "Latest day:")} ${latest.date} · ${latestCost} · ${latestTokens} tokens`); } return lines; } function formatBytes(value) { if (value === void 0) return "n/a"; const units = [ "B", "KiB", "MiB", "GiB" ]; let amount = value; let unitIndex = 0; while (amount >= 1024 && unitIndex < units.length - 1) { amount /= 1024; unitIndex += 1; } const digits = unitIndex === 0 || amount >= 100 ? 0 : 1; return `${amount.toFixed(digits)} ${units[unitIndex]}`; } function formatStabilityEvent(record) { return [ new Date(record.ts).toISOString(), `#${record.seq}`, record.type, record.level ? `level=${record.level}` : "", record.action ? `action=${record.action}` : "", record.outcome ? `outcome=${record.outcome}` : "", record.surface ? `surface=${record.surface}` : "", record.channel ? `channel=${record.channel}` : "", record.pluginId ? `plugin=${record.pluginId}` : "", record.reason ? `reason=${record.reason}` : "", record.bytes !== void 0 ? `bytes=${formatBytes(record.bytes)}` : "", record.limitBytes !== void 0 ? `limit=${formatBytes(record.limitBytes)}` : "", record.queueDepth !== void 0 ? `queueDepth=${record.queueDepth}` : "", record.queueLength !== void 0 ? `queueLength=${record.queueLength}` : "", record.droppedEvents !== void 0 ? `dropped=${record.droppedEvents}` : "", record.maxQueueLength !== void 0 ? `maxQueue=${record.maxQueueLength}` : "", record.queued !== void 0 ? `queued=${record.queued}` : "", record.memory ? `rss=${formatBytes(record.memory.rssBytes)}` : "", record.memory ? `heap=${formatBytes(record.memory.heapUsedBytes)}` : "" ].filter(Boolean).join(" "); } function renderStabilitySummary(snapshot, rich) { const lines = [colorize(rich, theme.heading, "Gateway Stability"), `${colorize(rich, theme.muted, "Events:")} ${snapshot.count}/${snapshot.capacity}${snapshot.dropped > 0 ? ` · dropped=${snapshot.dropped}` : ""}`]; const topTypes = Object.entries(snapshot.summary.byType).toSorted((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 8).map(([type, count]) => `${type}=${count}`).join(", "); if (topTypes) lines.push(`${colorize(rich, theme.muted, "Types:")} ${topTypes}`); const memory = snapshot.summary.memory; if (memory) lines.push(`${colorize(rich, theme.muted, "Memory:")} rss=${formatBytes(memory.latest?.rssBytes)} heap=${formatBytes(memory.latest?.heapUsedBytes)} maxRss=${formatBytes(memory.maxRssBytes)} pressure=${memory.pressureCount}`); const payloadLarge = snapshot.summary.payloadLarge; if (payloadLarge) { const surfaces = Object.entries(payloadLarge.bySurface).toSorted((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([surface, count]) => `${surface}=${count}`).join(", "); lines.push(`${colorize(rich, theme.muted, "Large payloads:")} total=${payloadLarge.count} rejected=${payloadLarge.rejected} truncated=${payloadLarge.truncated} chunked=${payloadLarge.chunked}${surfaces ? ` · ${surfaces}` : ""}`); } if (snapshot.events.length > 0) { lines.push(colorize(rich, theme.muted, "Recent:")); for (const event of snapshot.events) lines.push(` ${formatStabilityEvent(event)}`); } return lines; } function normalizeStabilityBundleTarget(raw) { if (raw === void 0 || raw === false) return null; if (raw === true) return "latest"; if (typeof raw !== "string") return "latest"; const value = raw.trim(); return value === "" ? "latest" : value; } function formatBundleError(result) { if (result.status === "missing") return `No stability bundles found in ${result.dir}`; if (result.status === "failed") return result.error instanceof Error ? result.error.message : String(result.error); return "Unexpected stability bundle read result"; } async function readStabilityBundleTarget(bundleTarget) { const { readDiagnosticStabilityBundleFileSync, readLatestDiagnosticStabilityBundleSync } = await loadStabilityBundleModule(); return bundleTarget === "latest" ? readLatestDiagnosticStabilityBundleSync() : readDiagnosticStabilityBundleFileSync(bundleTarget); } function renderStabilityBundleSummary(params) { const { bundle, path, rich, snapshot } = params; const processDetails = [ `pid=${bundle.process.pid}`, `node=${bundle.process.node}`, `${bundle.process.platform}/${bundle.process.arch}`, `uptime=${Math.round(bundle.process.uptimeMs / 1e3)}s` ].join(" "); const lines = [ colorize(rich, theme.heading, "Stability bundle"), `${colorize(rich, theme.muted, "Path:")} ${path}`, `${colorize(rich, theme.muted, "Generated:")} ${bundle.generatedAt}`, `${colorize(rich, theme.muted, "Reason:")} ${bundle.reason}`, `${colorize(rich, theme.muted, "Process:")} ${processDetails}`, `${colorize(rich, theme.muted, "Host:")} ${bundle.host.hostname}` ]; if (bundle.error) { const errorParts = [bundle.error.name ? `name=${bundle.error.name}` : "", bundle.error.code ? `code=${bundle.error.code}` : ""].filter(Boolean); if (errorParts.length > 0) lines.push(`${colorize(rich, theme.muted, "Error:")} ${errorParts.join(" ")}`); } const memoryPressure = bundle.evidence?.memoryPressure; if (memoryPressure) { lines.push(`${colorize(rich, theme.muted, "Memory pressure:")} ${memoryPressure.level}/${memoryPressure.reason} rss=${formatBytes(memoryPressure.memory.rssBytes)} heap=${formatBytes(memoryPressure.memory.heapUsedBytes)} threshold=${formatBytes(memoryPressure.thresholdBytes)}`); if (memoryPressure.heapStatistics) lines.push(`${colorize(rich, theme.muted, "V8 heap:")} used=${formatBytes(memoryPressure.heapStatistics.usedHeapSizeBytes)} limit=${formatBytes(memoryPressure.heapStatistics.heapSizeLimitBytes)} available=${formatBytes(memoryPressure.heapStatistics.totalAvailableSizeBytes)}`); if (memoryPressure.activeResources) { const resources = Object.entries(memoryPressure.activeResources.byType).map(([type, count]) => `${type}=${count}`).join(", "); lines.push(`${colorize(rich, theme.muted, "Active resources:")} total=${memoryPressure.activeResources.total}${resources ? ` · ${resources}` : ""}`); } if (memoryPressure.topSessionFiles?.length) { const files = memoryPressure.topSessionFiles.slice(0, 5).map((file) => `${file.relativePath}=${formatBytes(file.sizeBytes)}`).join(", "); lines.push(`${colorize(rich, theme.muted, "Largest session files:")} ${files}`); } } lines.push("", ...renderStabilitySummary(snapshot, rich)); return lines; } function renderSupportExportResult(result, rich) { return [ colorize(rich, theme.heading, "Diagnostics export"), `${colorize(rich, theme.muted, "Path:")} ${result.path}`, `${colorize(rich, theme.muted, "Size:")} ${formatBytes(result.bytes)}`, `${colorize(rich, theme.muted, "Files:")} ${result.manifest.contents.length}`, `${colorize(rich, theme.muted, "Privacy:")} payload-free stability, sanitized logs/status/health/config` ]; } function resolveSupportExportRpcOptions(rpc) { return { url: rpc?.url, token: rpc?.token, password: rpc?.password, timeout: rpc?.timeout ?? "3000", json: true }; } function parseOptionalPositiveIntegerOption(raw, label) { if (raw === void 0 || raw === null || raw === "") return; const parsed = parseStrictPositiveInteger(raw); if (parsed === void 0) throw new Error(`${label} must be a positive integer.`); return parsed; } async function writeSupportExportFromCli(opts) { const { writeDiagnosticSupportExport } = await loadSupportExportModule(); const rpc = resolveSupportExportRpcOptions(opts.rpc); const result = await writeDiagnosticSupportExport({ outputPath: opts.output, logLimit: parseOptionalPositiveIntegerOption(opts.logLines, "--log-lines"), logMaxBytes: parseOptionalPositiveIntegerOption(opts.logBytes, "--log-bytes"), stabilityBundle: opts.stabilityBundle, readStatusSnapshot: async () => { const { gatherDaemonStatus } = await loadDaemonStatusGatherModule(); return await gatherDaemonStatus({ rpc, probe: true, requireRpc: false, deep: false }); }, readHealthSnapshot: async () => await callGatewayCli("health", rpc) }); if (opts.json) { defaultRuntime.writeJson(result); return; } const rich = isRich(); for (const line of renderSupportExportResult(result, rich)) defaultRuntime.log(line); } function registerGatewayCli(program) { const gateway = addGatewayRunCommand(program.command("gateway").description("Run, inspect, and query the WebSocket Gateway").addHelpText("after", () => `\n${theme.heading("Examples:")}\n${formatHelpExamples([ ["openclaw gateway run", "Run the gateway in the foreground."], ["openclaw gateway status", "Show service status plus connectivity/capability."], ["openclaw gateway discover", "Find local and wide-area gateway beacons."], ["openclaw gateway stability", "Show recent stability diagnostics."], ["openclaw gateway call health", "Call a gateway RPC method directly."] ])}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/gateway", "docs.openclaw.ai/cli/gateway")}\n`)); addGatewayRunCommand(gateway.command("run").description("Run the WebSocket Gateway (foreground)")); addGatewayServiceCommands(gateway, { statusDescription: "Show gateway service status + probe connectivity/capability" }); gatewayCallOpts(gateway.command("call").description("Call a Gateway method").argument("<method>", "Method name (health/status/system-presence/cron.*)").option("--params <json>", "JSON object string for params", "{}").action(async (method, opts, command) => { await runGatewayCommand(async () => { const rpcOpts = resolveGatewayRpcOptions(opts, command); const result = await callGatewayCli(method, rpcOpts, JSON.parse(String(opts.params ?? "{}"))); if (rpcOpts.json) { defaultRuntime.writeJson(result); return; } const rich = isRich(); defaultRuntime.log(`${colorize(rich, theme.heading, "Gateway call")}: ${colorize(rich, theme.muted, String(method))}`); defaultRuntime.writeJson(result); }, "Gateway call failed"); })); gatewayCallOpts(gateway.command("usage-cost").description("Fetch usage cost summary from session logs").option("--days <days>", "Number of days to include", "30").action(async (opts, command) => { await runGatewayCommand(async () => { const rpcOpts = resolveGatewayRpcOptions(opts, command); const days = parseDaysOption(opts.days); const result = await callGatewayCli("usage.cost", rpcOpts, { days }); if (rpcOpts.json) { defaultRuntime.writeJson(result); return; } const rich = isRich(); const summary = result; for (const line of await renderCostUsageSummaryAsync(summary, days, rich)) defaultRuntime.log(line); }, "Gateway usage cost failed"); })); gatewayCallOpts(gateway.command("health").description("Fetch Gateway health").action(async (opts, command) => { await runGatewayCommand(async () => { const rpcOpts = resolveGatewayRpcOptions(opts, command); const [{ formatHealthChannelLines }, { styleHealthChannelLine }] = await Promise.all([loadGatewayHealthModule(), loadHealthStyleModule()]); const result = await callGatewayCli("health", rpcOpts); if (rpcOpts.json) { defaultRuntime.writeJson(result); return; } const rich = isRich(); const obj = result && typeof result === "object" ? result : {}; const durationMs = typeof obj.durationMs === "number" ? obj.durationMs : null; defaultRuntime.log(colorize(rich, theme.heading, "Gateway Health")); defaultRuntime.log(`${colorize(rich, theme.success, "OK")}${durationMs != null ? ` (${durationMs}ms)` : ""}`); if (obj.channels && typeof obj.channels === "object") for (const line of formatHealthChannelLines(obj)) defaultRuntime.log(styleHealthChannelLine(line, rich)); }, void 0, { json: Boolean(opts.json) }); })); gatewayCallOpts(gateway.command("stability").description("Fetch payload-free Gateway stability diagnostics").option("--limit <limit>", "Maximum number of recent events", "25").option("--type <type>", "Filter by diagnostic event type").option("--since-seq <seq>", "Only include events after this sequence").option("--bundle [path]", "Read a persisted stability bundle instead of calling Gateway; pass \"latest\" for newest").option("--export", "Write a shareable support diagnostics export", false).option("--output <path>", "Diagnostics export output .zip path").action(async (opts, command) => { await runGatewayCommand(async () => { const { normalizeDiagnosticStabilityQuery, selectDiagnosticStabilitySnapshot } = await import("./diagnostic-stability-CzgLT5Mg.js"); const rpcOpts = resolveGatewayRpcOptions(opts, command); const query = normalizeDiagnosticStabilityQuery({ limit: opts.limit, sinceSeq: opts.sinceSeq, type: opts.type }, { defaultLimit: 25 }); const bundleTarget = normalizeStabilityBundleTarget(opts.bundle); if (opts.export) { await writeSupportExportFromCli({ json: rpcOpts.json, output: opts.output, stabilityBundle: bundleTarget ?? "latest", rpc: rpcOpts }); return; } if (bundleTarget) { const result = await readStabilityBundleTarget(bundleTarget); if (result.status !== "found") throw new Error(formatBundleError(result)); const snapshot = selectDiagnosticStabilitySnapshot(result.bundle.snapshot, query); if (rpcOpts.json) { defaultRuntime.writeJson({ path: result.path, mtimeMs: result.mtimeMs, bundle: { ...result.bundle, snapshot } }); return; } const rich = isRich(); for (const line of renderStabilityBundleSummary({ bundle: result.bundle, path: result.path, rich, snapshot })) defaultRuntime.log(line); return; } const result = await callGatewayCli("diagnostics.stability", rpcOpts, { limit: query.limit, ...query.type ? { type: query.type } : {}, ...query.sinceSeq !== void 0 ? { sinceSeq: query.sinceSeq } : {} }); if (rpcOpts.json) { defaultRuntime.writeJson(result); return; } const rich = isRich(); for (const line of renderStabilitySummary(result, rich)) defaultRuntime.log(line); }, "Gateway stability failed"); })); gateway.command("diagnostics").description("Export local support diagnostics").command("export").description("Write a shareable, payload-free diagnostics .zip").option("--output <path>", "Output .zip path").option("--log-lines <count>", "Maximum sanitized log lines to include", "5000").option("--log-bytes <bytes>", "Maximum log bytes to inspect", "1000000").option("--url <url>", "Gateway WebSocket URL for health snapshot").option("--token <token>", "Gateway token for health snapshot").option("--password <password>", "Gateway password for health snapshot").option("--timeout <ms>", "Status/health snapshot timeout in ms", "3000").option("--no-stability-bundle", "Skip persisted stability bundle lookup").option("--json", "Output JSON", false).action(async (opts, command) => { await runGatewayCommand(async () => { const rpcOpts = resolveGatewayRpcOptions(opts, command); await writeSupportExportFromCli({ json: opts.json, output: opts.output, logLines: opts.logLines, logBytes: opts.logBytes, stabilityBundle: opts.stabilityBundle === false ? false : "latest", rpc: rpcOpts }); }, "Gateway diagnostics export failed"); }); gateway.command("probe").description("Show gateway reachability, auth capability, and read-probe summary (local + remote)").option("--url <url>", "Explicit Gateway WebSocket URL (still probes localhost)").option("--ssh <target>", "SSH target for remote gateway tunnel (user@host or user@host:port)").option("--ssh-identity <path>", "SSH identity file path").option("--ssh-auto", "Try to derive an SSH target from Bonjour discovery", false).option("--token <token>", "Gateway token (applies to all probes)").option("--password <password>", "Gateway password (applies to all probes)").option("--timeout <ms>", "Overall probe budget in ms", "3000").option("--json", "Output JSON", false).action(async (opts, command) => { await runGatewayCommand(async () => { const rpcOpts = resolveGatewayRpcOptions(opts, command); const { gatewayStatusCommand } = await loadGatewayStatusModule(); await gatewayStatusCommand(rpcOpts, defaultRuntime); }); }); gateway.command("discover").description("Discover gateways via Bonjour (local + wide-area if configured)").option("--timeout <ms>", "Per-command timeout in ms", "2000").option("--json", "Output JSON", false).action(async (opts) => { await runGatewayCommand(async () => { const [{ readSourceConfigBestEffort }, { discoverGatewayBeacons }, { resolveWideAreaDiscoveryDomain }, { dedupeBeacons, parseDiscoverTimeoutMs, pickBeaconHost, pickGatewayPort, renderBeaconLines }, { withProgress }] = await Promise.all([ loadConfigModule(), loadBonjourDiscoveryModule(), loadWideAreaDnsModule(), import("./discover-qeavWsFr.js"), import("./progress-D8EpIMJk.js") ]); const wideAreaDomain = resolveWideAreaDiscoveryDomain({ configDomain: (await readSourceConfigBestEffort()).discovery?.wideArea?.domain }); const timeoutMs = parseDiscoverTimeoutMs(opts.timeout, 2e3); const domains = ["local.", ...wideAreaDomain ? [wideAreaDomain] : []]; const deduped = dedupeBeacons(await withProgress({ label: "Scanning for gateways…", indeterminate: true, enabled: opts.json !== true, delayMs: 0 }, async () => await discoverGatewayBeacons({ timeoutMs, wideAreaDomain }))).toSorted((a, b) => (a.displayName || a.instanceName).localeCompare(b.displayName || b.instanceName)); if (opts.json) { const enriched = deduped.map((b) => { const host = pickBeaconHost(b); const port = pickGatewayPort(b); return { ...b, wsUrl: host ? `ws://${host}:${port}` : null }; }); defaultRuntime.writeJson({ timeoutMs, domains, count: enriched.length, beacons: enriched }); return; } const rich = isRich(); defaultRuntime.log(colorize(rich, theme.heading, "Gateway Discovery")); defaultRuntime.log(colorize(rich, theme.muted, `Found ${deduped.length} gateway(s) · domains: ${domains.join(", ")}`)); if (deduped.length === 0) return; for (const beacon of deduped) for (const line of renderBeaconLines(beacon, rich)) defaultRuntime.log(line); }, "gateway discover failed"); }); } //#endregion export { registerGatewayCli };