UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

654 lines (653 loc) 31.4 kB
import { r as extractBalancedJsonPrefix } from "./src-vebZIeLe.js"; import { t as safeParseJson } from "./json-coercion-AulM0PZ6.js"; import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js"; import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js"; import { s as readConfigFileSnapshot } from "./io.runtime-B9iJRs3w.js"; import { _ as resolveGatewayPort } from "./paths-D2sRr1a_.js"; import { o as redactSensitiveUrlLikeString } from "./redact-sensitive-url-BN1NZvXG.js"; import { n as sanitizeTerminalText } from "./safe-text-BGBqp1a4.js"; import { n as resolvePluginControlPlaneWorkspace } from "./control-plane-workspace-BWhgt2gz.js"; import { t as formatConfigIssueLine } from "./issue-format-CQvxWNW8.js"; import "./config-Cs0XXL3x.js"; import { n as isRich, r as theme } from "./theme-vjDs9tao.js"; import { g as resolveGatewayBindHost, v as resolveGatewayRequiredListenHosts } from "./net-DbNPs6Xm.js"; import { a as resolveGatewayRestartLogPath, i as resolveGatewayLogPaths, o as resolveGatewaySupervisorLogPaths } from "./restart-logs-uOTpYNS7.js"; import { a as isExpectedGatewayListeners, i as isDualStackLoopbackGatewayListeners, n as classifyPortListener, r as formatPortDiagnostics } from "./ports-format-D-ReVpaa.js"; import { n as inspectPortUsage } from "./ports-inspect-CHTlDwBh.js"; import "./ports-CU06KPCY.js"; import { n as readLastGatewayErrorLine, t as readGatewayLogTailLines } from "./diagnostics-RQc9481t.js"; import { a as listUpdateRuns, n as findActiveUpdateRun } from "./update-run-ledger-Dse_-g6v.js"; import { o as classifyOAuthRefreshFailureReason } from "./oauth-refresh-failure-DP-bO7C0.js"; import { p as summarizeRestartSentinel, u as readRestartSentinelReadOnly } from "./restart-sentinel-FmKS9LVR.js"; import "./exec-approvals-BSZ-fPIY.js"; import { a as loadExecApprovalsReadOnly } from "./exec-approvals-store-DBR0neS0.js"; import { n as resolveNodeExecEligibility } from "./exec-defaults-CET-_UfH.js"; import { t as buildWorkspaceSkillStatus } from "./status-BDd5nSbi.js"; import { n as formatTimeAgo } from "./format-relative-DiD6ocfu.js"; import { t as getRemoteSkillEligibility } from "./remote-D-j1AEXQ.js"; import { r as withProgress } from "./progress-DSeqxbrQ.js"; import { n as renderTable, t as getTerminalTableWidth } from "./table-DzN9OC44.js"; import { t as formatUsageReportLines } from "./provider-usage.format-BWh4p3OY.js"; import { n as buildPluginCompatibilityNotices } from "./status-DornsoQ9.js"; import { t as formatPluginCompatibilityNotice } from "./status-compatibility-DdQ1VWdc.js"; import { n as renderUpdateRunReport } from "./update-run-report-CEiRumC4.js"; import { t as formatDeliveryQueueHealthLine } from "./health-format-BptmWUeD.js"; import { n as resolveStatusAllConnectionDetails } from "./status.gateway-connection-o1eqDxlb.js"; import { t as formatTelemetryExporterSummary } from "./telemetry-exporter-summary-BzbrNr8-.js"; import { i as redactStatusSecrets } from "./format-q3Hb37m-.js"; import { c as buildStatusOverviewSurfaceFromOverview, i as formatStatusConfigDiagnosticEntries } from "./status.format-C8HHj2VF.js"; import { a as statusAgentsTableColumns, c as statusChannelsTableColumns, i as buildStatusChannelDetailSections, l as buildStatusAllOverviewRows, n as appendStatusSectionHeading, o as statusOverviewTableColumns, r as buildStatusAgentTableRows, s as buildStatusChannelsTableRows, t as appendStatusReportSections } from "./text-report-CQDdyuwF.js"; import { o as resolveStatusServiceSummaries, r as resolveStatusGatewayHealthSafe, s as resolveStatusUsageSummary, t as resolveStatusGatewayDiagnosticsSafe } from "./status-runtime-shared-CSLhydPO.js"; import { n as formatUpdateRestartStatusValue, t as formatUpdateRestartActionLines } from "./status-update-restart-BReWY3n7.js"; import { n as resolveStatusSummaryFromOverview, t as collectStatusScanOverview } from "./status.scan-overview-B24TM9j9.js"; import { t as resolveNodeOnlyGatewayInfo } from "./status.node-mode-fhXS-jYg.js"; //#region src/commands/status-all/report-data.ts function resolveStatusAllConfigPath(path) { const trimmed = path?.trim(); return trimmed && trimmed.length > 0 ? trimmed : "(unknown config path)"; } /** Collects local diagnosis inputs that are not part of the shared overview scan. */ async function resolveStatusAllLocalDiagnosis(params) { const { overview } = params; const snap = await readConfigFileSnapshot({ observe: false }).catch(() => null); const configPath = resolveStatusAllConfigPath(snap?.path); const diagnosticsParams = { config: overview.cfg, timeoutMs: Math.min(5e3, params.timeoutMs ?? 1e4), gatewayReachable: params.gatewayReachable, ...params.gatewayCallOverrides ? { callOverrides: params.gatewayCallOverrides } : {} }; const [health, deliveryDiagnostics, exporterDiagnostics] = params.nodeOnlyGateway ? [ void 0, null, null ] : await Promise.all([ resolveStatusGatewayHealthSafe({ config: overview.cfg, timeoutMs: Math.min(8e3, params.timeoutMs ?? 1e4), gatewayReachable: params.gatewayReachable, gatewayProbeError: params.gatewayProbe?.error ?? null, ...params.gatewayCallOverrides ? { callOverrides: params.gatewayCallOverrides } : {} }), resolveStatusGatewayDiagnosticsSafe(diagnosticsParams), resolveStatusGatewayDiagnosticsSafe({ ...diagnosticsParams, type: "telemetry.exporter" }) ]); params.progress.setLabel("Checking local state…"); const sentinel = await readRestartSentinelReadOnly().catch(() => null); const lastErr = await readLastGatewayErrorLine(process.env).catch(() => null); const port = resolveGatewayPort(overview.cfg); const bindHost = await resolveGatewayBindHost(overview.cfg.gateway?.bind ?? "loopback", overview.cfg.gateway?.customBindHost); const portUsage = await inspectPortUsage(port, { probeHosts: resolveGatewayRequiredListenHosts(bindHost) }).catch(() => null); params.progress.tick(); const controlPlaneWorkspace = resolvePluginControlPlaneWorkspace({ config: overview.cfg, env: process.env }); const defaultWorkspace = controlPlaneWorkspace.workspaceDir ?? null; const skillStatus = defaultWorkspace != null ? (() => { try { const nodeSkills = resolveNodeExecEligibility({ cfg: overview.cfg, execApprovals: loadExecApprovalsReadOnly(), agentId: controlPlaneWorkspace.agentId }); return buildWorkspaceSkillStatus(defaultWorkspace, { config: overview.cfg, eligibility: { nodeSkills, remote: getRemoteSkillEligibility({ advertiseExecNode: nodeSkills.canExec }) } }); } catch { return null; } })() : null; const pluginCompatibility = buildPluginCompatibilityNotices({ config: overview.cfg }); return { configPath, health, diagnosis: { snap, remoteUrlMissing: overview.gatewaySnapshot.remoteUrlMissing, secretDiagnostics: overview.secretDiagnostics, sentinel, lastErr, port, portUsage, tailscaleMode: overview.tailscaleMode, tailscale: { backendState: null, dnsName: overview.tailscaleDns, ips: [], error: null }, tailscaleHttpsUrl: overview.tailscaleHttpsUrl, skillStatus, pluginCompatibility, channelsStatus: overview.channelsStatus, channelIssues: overview.channelIssues, agentStatus: overview.agentStatus, gatewayReachable: params.gatewayReachable, health, deliveryDiagnostics, exporterDiagnostics, nodeOnlyGateway: params.nodeOnlyGateway } }; } /** Builds the full status-all report data model from a completed overview scan. */ async function buildStatusAllReportData(params) { const gatewaySnapshot = params.overview.gatewaySnapshot; const [{ configPath, health, diagnosis }, summary] = await Promise.all([resolveStatusAllLocalDiagnosis({ overview: params.overview, progress: params.progress, gatewayReachable: gatewaySnapshot.gatewayReachable, gatewayProbe: gatewaySnapshot.gatewayProbe, gatewayCallOverrides: gatewaySnapshot.gatewayCallOverrides, nodeOnlyGateway: params.nodeOnlyGateway, timeoutMs: params.timeoutMs }), params.overview.runtimeDegradation ?? resolveStatusSummaryFromOverview({ overview: params.overview })]); const overviewSurface = buildStatusOverviewSurfaceFromOverview({ overview: params.overview, gatewayService: params.daemon, nodeService: params.nodeService, nodeOnlyGateway: params.nodeOnlyGateway }); const updateRun = findActiveUpdateRun() ?? listUpdateRuns({ limit: 1 })[0]; const overviewRows = buildStatusAllOverviewRows({ surface: overviewSurface, osLabel: params.overview.osSummary.label, configPath, summary, secretDiagnosticsCount: params.overview.secretDiagnostics.length, updateValue: updateRun ? renderUpdateRunReport(updateRun).headline : void 0, updateRestartValue: formatUpdateRestartStatusValue(diagnosis.sentinel?.payload), agentStatus: params.overview.agentStatus, tailscaleBackendState: diagnosis.tailscale.backendState }); return { configDiagnostics: params.overview.configDiagnostics, overviewRows, channels: params.overview.channels, channelIssues: params.overview.channelIssues.map((issue) => ({ channel: issue.channel, message: issue.message })), agentStatus: params.overview.agentStatus, connectionDetailsForReport: resolveStatusAllConnectionDetails({ nodeOnlyGateway: params.nodeOnlyGateway, remoteUrlMissing: gatewaySnapshot.remoteUrlMissing, gatewayConnection: gatewaySnapshot.gatewayConnection, bindMode: params.overview.cfg.gateway?.bind ?? "loopback", configPath }), diagnosis: { ...diagnosis, health } }; } //#endregion //#region src/commands/status-all/gateway.ts /** Reads the last non-empty lines from a gateway log file, returning an empty list on read failure. */ async function readFileTailLines(filePath, maxLines) { const lines = await readGatewayLogTailLines(filePath).catch(() => []); if (lines.length === 0) return []; return lines.slice(Math.max(0, lines.length - maxLines)).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0); } function shorten(message, maxLen) { const cleaned = message.replace(/\s+/g, " ").trim(); if (cleaned.length <= maxLen) return cleaned; return `${truncateUtf16Safe(cleaned, Math.max(0, maxLen - 1))}…`; } function normalizeGwsLine(line) { return line.replace(/\s+runId=[^\s]+/g, "").replace(/\s+conn=[^\s]+/g, "").replace(/\s+id=[^\s]+/g, "").replace(/\s+error=Error:.*$/g, "").trim(); } function consumeJsonBlock(lines, startIndex) { const startLine = lines[startIndex] ?? ""; const braceAt = startLine.indexOf("{"); if (braceAt < 0) return null; const raw = [startLine.slice(braceAt), ...lines.slice(startIndex + 1)].join("\n"); const fragment = extractBalancedJsonPrefix(raw); if (!fragment) return { json: raw, endIndex: lines.length - 1 }; const consumedLineOffset = fragment.json.split("\n").length - 1; return { json: fragment.json, endIndex: startIndex + consumedLineOffset }; } /** Summarizes gateway log tail lines, grouping repeated failures and trimming long output. */ function summarizeLogTail(rawLines, opts) { const maxLines = Math.max(6, opts?.maxLines ?? 26); const out = []; const groups = /* @__PURE__ */ new Map(); const addGroup = (key, base) => { const existing = groups.get(key); if (existing) { existing.count += 1; return; } groups.set(key, { count: 1, index: out.length, base }); out.push(base); }; const addLine = (line) => { const trimmed = line.trimEnd(); if (!trimmed) return; out.push(trimmed); }; const lines = rawLines.map((line) => line.trimEnd()).filter(Boolean); for (let i = 0; i < lines.length; i += 1) { const line = lines[i] ?? ""; const trimmedStart = line.trimStart(); if ((trimmedStart.startsWith("\"") || trimmedStart === "}" || trimmedStart === "{" || trimmedStart.startsWith("}") || trimmedStart.startsWith("{")) && !trimmedStart.startsWith("[") && !trimmedStart.startsWith("#")) continue; const tokenRefresh = line.match(/^\[([^\]]+)\]\s+Token refresh failed:\s*(\d+)(?:\s+(\{.*))?\s*$/); if (tokenRefresh) { const tag = tokenRefresh[1] ?? "unknown"; const status = tokenRefresh[2] ?? "unknown"; const block = consumeJsonBlock(lines, i); if (block) { i = block.endIndex; const parsed = safeParseJson(block.json) ?? null; const code = normalizeOptionalString(parsed?.error?.code) ?? null; const msg = normalizeOptionalString(parsed?.error?.message) ?? null; const refreshReason = classifyOAuthRefreshFailureReason(msg ?? ""); const msgShort = msg ? refreshReason ? "re-auth required" : shorten(msg, 52) : null; const base = `[${tag}] token refresh ${status}${code ? ` ${code}` : ""}${msgShort ? ` · ${msgShort}` : ""}`; addGroup(`token:${tag}:${status}:${code ?? ""}:${msgShort ?? ""}`, base); continue; } } const embedded = line.match(/^Embedded agent failed before reply:\s+OAuth token refresh failed for ([^:]+):/); if (embedded) { const provider = normalizeOptionalString(embedded[1]) || "unknown"; addGroup(`embedded:${provider}`, `Embedded agent: OAuth token refresh failed (${provider})`); continue; } if (line.startsWith("[gws]") && line.includes("errorCode=UNAVAILABLE") && line.includes("OAuth token refresh failed")) { const normalized = normalizeGwsLine(line); addGroup(`gws:${normalized}`, normalized); continue; } addLine(line); } for (const g of groups.values()) { if (g.count <= 1) continue; out[g.index] = `${g.base} ×${g.count}`; } const deduped = []; for (const line of out) { if (deduped[deduped.length - 1] === line) continue; deduped.push(line); } if (deduped.length <= maxLines) return deduped; const head = Math.min(6, Math.floor(maxLines / 3)); const tail = Math.max(1, maxLines - head - 1); return [ ...deduped.slice(0, head), `… ${deduped.length - head - tail} lines omitted …`, ...deduped.slice(-tail) ]; } //#endregion //#region src/commands/status-all/diagnosis.ts const AGENT_ACTIVITY_SOFT_WARNING_MS = 18e5; function countRecentAgentSessions(agentStatus, thresholdMs) { return agentStatus.agents.filter((agent) => agent.lastActiveAgeMs != null && agent.lastActiveAgeMs <= thresholdMs).length; } function countGatewayListenerPids(portUsage) { const pids = /* @__PURE__ */ new Set(); for (const listener of portUsage.listeners) { if (classifyPortListener(listener, portUsage.port) !== "gateway") continue; if (typeof listener.pid === "number" && Number.isFinite(listener.pid)) pids.add(listener.pid); } return pids.size; } function isDeliveryDiagnosticsLike(value) { return Boolean(value && typeof value === "object"); } function countDeliveryEvent(snapshot, type) { const value = snapshot.summary?.byType?.[type]; return typeof value === "number" && Number.isFinite(value) ? value : 0; } function latestDeliveryEventAgeMs(snapshot) { const latestTs = (snapshot.events ?? []).filter((event) => [ "message.received", "message.dispatch.started", "message.dispatch.completed", "session.turn.created", "message.processed" ].includes(event.type ?? "")).reduce((max, event) => { const ts = event.ts; return typeof ts === "number" && Number.isFinite(ts) ? Math.max(max, ts) : max; }, 0); return latestTs > 0 ? Date.now() - latestTs : null; } /** Appends config, gateway, channel, delivery, and log diagnostics to the status-all report. */ async function appendStatusAllDiagnosis(params) { const { lines, muted, ok, warn, fail } = params; const emitCheck = (label, status) => { const icon = status === "ok" ? ok("✓") : status === "warn" ? warn("!") : fail("✗"); const colored = status === "ok" ? ok(label) : status === "warn" ? warn(label) : fail(label); lines.push(`${icon} ${colored}`); }; const emitUnavailableDiagnostics = (diagnostic) => { emitCheck(`${diagnostic.label}: unavailable`, "warn"); lines.push(` ${muted(sanitizeTerminalText(redactStatusSecrets(redactSensitiveUrlLikeString(diagnostic.detail))))}`); lines.push(` ${muted(`Retry: ${diagnostic.retry}`)}`); }; lines.push(""); lines.push(muted("Gateway connection details:")); for (const line of redactStatusSecrets(params.connectionDetailsForReport).split("\n").map((l) => l.trimEnd())) lines.push(` ${muted(line)}`); lines.push(""); if (params.snap) { const status = !params.snap.exists ? "fail" : params.snap.valid ? "ok" : "warn"; emitCheck(`Config: ${params.snap.path ?? "(unknown)"}`, status); const issues = [...params.snap.legacyIssues ?? [], ...params.snap.issues ?? []]; const uniqueIssues = issues.filter((issue, index) => issues.findIndex((x) => x.path === issue.path && x.message === issue.message) === index); for (const issue of uniqueIssues.slice(0, 12)) lines.push(` ${formatConfigIssueLine(issue, "-")}`); if (uniqueIssues.length > 12) lines.push(` ${muted(`… +${uniqueIssues.length - 12} more`)}`); } else emitCheck("Config: read failed", "warn"); if (params.remoteUrlMissing) { lines.push(""); emitCheck("Gateway remote mode misconfigured (gateway.remote.url missing)", "warn"); lines.push(` ${muted("Fix: set gateway.remote.url, or set gateway.mode=local.")}`); } emitCheck(`Secret diagnostics (${params.secretDiagnostics.length})`, params.secretDiagnostics.length === 0 ? "ok" : "warn"); for (const diagnostic of params.secretDiagnostics.slice(0, 10)) lines.push(` - ${muted(redactStatusSecrets(diagnostic))}`); if (params.secretDiagnostics.length > 10) lines.push(` ${muted(`… +${params.secretDiagnostics.length - 10} more`)}`); if (params.sentinel?.payload) { emitCheck("Restart sentinel present", "warn"); lines.push(` ${muted(`${summarizeRestartSentinel(params.sentinel.payload)} · ${formatTimeAgo(Date.now() - params.sentinel.payload.ts)}`)}`); const updateRestartValue = formatUpdateRestartStatusValue(params.sentinel.payload); if (updateRestartValue) lines.push(` ${muted(`Update restart: ${updateRestartValue}`)}`); for (const line of formatUpdateRestartActionLines(params.sentinel.payload)) lines.push(` ${muted(line)}`); } else emitCheck("Restart sentinel: none", "ok"); const lastErrClean = normalizeOptionalString(params.lastErr) ?? ""; const isTrivialLastErr = lastErrClean.length < 8 || lastErrClean === "}" || lastErrClean === "{"; if (lastErrClean && !isTrivialLastErr) { lines.push(""); lines.push(muted("Gateway last log line:")); lines.push(` ${muted(redactStatusSecrets(lastErrClean))}`); } if (params.portUsage) { const benignDualStackLoopback = isDualStackLoopbackGatewayListeners(params.portUsage.listeners, params.port); const expectedGatewayListeners = isExpectedGatewayListeners(params.portUsage.listeners, params.port); const portOk = params.portUsage.status === "free" || params.portUsage.status === "busy" && expectedGatewayListeners; emitCheck(`Port ${params.port}`, portOk ? "ok" : "warn"); if (!portOk) { const gatewayPidCount = countGatewayListenerPids(params.portUsage); if (gatewayPidCount > 1) lines.push(` ${muted(`${gatewayPidCount} OpenClaw gateway processes appear to be listening on port ${params.port}; stop stale gateway processes before trusting channel health.`)}`); for (const line of formatPortDiagnostics(params.portUsage)) lines.push(` ${muted(line)}`); } else if (benignDualStackLoopback) lines.push(` ${muted("Detected dual-stack loopback listeners (127.0.0.1 + ::1) for one gateway process.")}`); else if (expectedGatewayListeners) lines.push(` ${muted("Detected OpenClaw Gateway listener on the configured port.")}`); } { const backend = params.tailscale.backendState ?? "unknown"; const okBackend = backend === "Running"; const hasDns = Boolean(params.tailscale.dnsName); emitCheck(params.tailscaleMode === "off" ? `Tailscale exposure: off · daemon ${backend}${params.tailscale.dnsName ? ` · ${params.tailscale.dnsName}` : ""}` : `Tailscale exposure: ${params.tailscaleMode} · daemon ${backend}${params.tailscale.dnsName ? ` · ${params.tailscale.dnsName}` : ""}`, params.tailscaleMode === "off" || okBackend && hasDns ? "ok" : "warn"); if (params.tailscale.error) lines.push(` ${muted(`error: ${params.tailscale.error}`)}`); if (params.tailscale.ips.length > 0) lines.push(` ${muted(`ips: ${params.tailscale.ips.slice(0, 3).join(", ")}${params.tailscale.ips.length > 3 ? "…" : ""}`)}`); if (params.tailscaleHttpsUrl) lines.push(` ${muted(`https: ${params.tailscaleHttpsUrl}`)}`); } if (params.skillStatus) { const eligible = params.skillStatus.skills.filter((s) => s.eligible).length; const missing = params.skillStatus.skills.filter((s) => s.eligible && Object.values(s.missing).some((arr) => arr.length)).length; emitCheck(`Skills: ${eligible} eligible · ${missing} missing · ${params.skillStatus.workspaceDir}`, missing === 0 ? "ok" : "warn"); } emitCheck(`Plugin compatibility (${params.pluginCompatibility.length || "none"})`, params.pluginCompatibility.length === 0 ? "ok" : "warn"); for (const notice of params.pluginCompatibility.slice(0, 12)) { const severity = notice.severity === "warn" ? "warn" : "info"; lines.push(` - [${severity}] ${formatPluginCompatibilityNotice(notice)}`); } if (params.pluginCompatibility.length > 12) lines.push(` ${muted(`… +${params.pluginCompatibility.length - 12} more`)}`); if (params.agentStatus) { const recentSessions = countRecentAgentSessions(params.agentStatus, AGENT_ACTIVITY_SOFT_WARNING_MS); const shouldWarn = params.agentStatus.totalSessions > 0 && recentSessions === 0; emitCheck(`Agent activity: ${recentSessions} active in 30m · ${params.agentStatus.totalSessions} sessions`, shouldWarn ? "warn" : "ok"); if (shouldWarn) lines.push(` ${muted("No agent session was updated in the last 30m; if channels received messages, verify inbound dispatch and turn creation.")}`); } if (!params.nodeOnlyGateway && params.exporterDiagnostics) { if (params.exporterDiagnostics.ok) { const exporterSummary = formatTelemetryExporterSummary(params.exporterDiagnostics.value); if (exporterSummary) { emitCheck(exporterSummary.title, exporterSummary.status); for (const line of exporterSummary.lines) lines.push(` ${muted(line)}`); } } else emitUnavailableDiagnostics({ label: "Telemetry exporters", detail: `Exporter diagnostics failed: ${params.exporterDiagnostics.error}`, retry: "openclaw gateway stability --type telemetry.exporter" }); } if (!params.nodeOnlyGateway && params.deliveryDiagnostics?.ok) { if (isDeliveryDiagnosticsLike(params.deliveryDiagnostics.value)) { const deliveryDiagnostics = params.deliveryDiagnostics.value; const received = countDeliveryEvent(deliveryDiagnostics, "message.received"); const dispatchStarted = countDeliveryEvent(deliveryDiagnostics, "message.dispatch.started"); const dispatchCompleted = countDeliveryEvent(deliveryDiagnostics, "message.dispatch.completed"); const turnsCreated = countDeliveryEvent(deliveryDiagnostics, "session.turn.created"); const processed = countDeliveryEvent(deliveryDiagnostics, "message.processed"); const hasReceivedWithoutDispatch = received > 0 && dispatchStarted === 0 && processed === 0; const hasDispatchWithoutTurn = dispatchStarted > 0 && turnsCreated === 0 && processed < dispatchStarted; const hasDispatchGap = dispatchStarted - dispatchCompleted >= 2; const latestAgeMs = latestDeliveryEventAgeMs(deliveryDiagnostics); emitCheck(`Inbound delivery telemetry: received ${received} · dispatch ${dispatchStarted}/${dispatchCompleted} · turns ${turnsCreated} · processed ${processed}`, hasReceivedWithoutDispatch || hasDispatchWithoutTurn || hasDispatchGap ? "warn" : "ok"); if (latestAgeMs != null) lines.push(` ${muted(`latest delivery event: ${formatTimeAgo(latestAgeMs)}`)}`); if (hasReceivedWithoutDispatch) lines.push(` ${muted("Messages were received, but no gateway dispatch started; inspect inbound routing and dispatch handoff.")}`); if (hasDispatchWithoutTurn) lines.push(` ${muted("Gateway dispatch started, but no agent turn was created; inspect reply resolver and session creation.")}`); if (hasDispatchGap) lines.push(` ${muted("Multiple gateway dispatches have not completed yet; if this persists, inspect stuck sessions or model runs.")}`); } else emitUnavailableDiagnostics({ label: "Inbound delivery telemetry", detail: "Delivery diagnostics returned an invalid response.", retry: "openclaw gateway stability" }); } else if (!params.nodeOnlyGateway && params.deliveryDiagnostics && !params.deliveryDiagnostics.ok) emitUnavailableDiagnostics({ label: "Inbound delivery telemetry", detail: `Delivery diagnostics failed: ${params.deliveryDiagnostics.error}`, retry: "openclaw gateway stability" }); params.progress.setLabel("Reading logs…"); const logPaths = (() => { try { return process.platform === "darwin" ? resolveGatewaySupervisorLogPaths(process.env, { platform: "darwin" }) : resolveGatewayLogPaths(process.env); } catch { return null; } })(); if (logPaths) { params.progress.setLabel("Reading logs…"); const restartLogPath = resolveGatewayRestartLogPath(process.env); const readStderr = process.platform !== "darwin"; const [stderrTail, stdoutTail, restartTail] = await Promise.all([ readStderr ? readFileTailLines(logPaths.stderrPath, 40).catch(() => []) : [], readFileTailLines(logPaths.stdoutPath, 40).catch(() => []), readFileTailLines(restartLogPath, 30).catch(() => []) ]); if (stderrTail.length > 0 || stdoutTail.length > 0) { lines.push(""); lines.push(muted(`Gateway logs (tail, summarized): ${logPaths.logDir}`)); if (readStderr) { lines.push(` ${muted(`# stderr: ${logPaths.stderrPath}`)}`); for (const line of summarizeLogTail(stderrTail, { maxLines: 22 }).map(redactStatusSecrets)) lines.push(` ${muted(line)}`); } lines.push(` ${muted(`# stdout: ${logPaths.stdoutPath}`)}`); for (const line of summarizeLogTail(stdoutTail, { maxLines: 22 }).map(redactStatusSecrets)) lines.push(` ${muted(line)}`); } if (restartTail.length > 0) { lines.push(""); lines.push(muted(`Gateway restart attempts (tail): ${restartLogPath}`)); for (const line of summarizeLogTail(restartTail, { maxLines: 16 }).map(redactStatusSecrets)) lines.push(` ${muted(line)}`); } } params.progress.tick(); if (params.channelsStatus) { emitCheck(`Channel issues (${params.channelIssues.length || "none"})`, params.channelIssues.length === 0 ? "ok" : "warn"); for (const issue of params.channelIssues.slice(0, 12)) { const fixText = issue.fix ? ` · fix: ${issue.fix}` : ""; lines.push(` - ${issue.channel}[${issue.accountId}] ${issue.kind}: ${issue.message}${fixText}`); } if (params.channelIssues.length > 12) lines.push(` ${muted(`… +${params.channelIssues.length - 12} more`)}`); } else if (params.nodeOnlyGateway) emitCheck(`Channel issues skipped (node-only mode; query ${params.nodeOnlyGateway.gatewayTarget})`, "ok"); else emitCheck(`Channel issues skipped (gateway ${params.gatewayReachable ? "query failed" : "unreachable"})`, "warn"); if (params.health) { if ("error" in params.health) { if (params.health.error) { lines.push(""); lines.push(muted("Gateway health:")); lines.push(` ${muted(redactStatusSecrets(params.health.error))}`); } } else { const deliveryQueueLine = formatDeliveryQueueHealthLine(params.health); if (deliveryQueueLine) emitCheck(redactStatusSecrets(deliveryQueueLine), "warn"); } } lines.push(""); lines.push(muted("Pasteable debug report. Auth tokens redacted.")); lines.push("Troubleshooting: https://docs.openclaw.ai/troubleshooting"); lines.push(""); } //#endregion //#region src/commands/status-all/report-lines.ts /** Builds the complete status-all text report, including overview tables and diagnosis lines. */ async function buildStatusAllReportLines(params) { const rich = isRich(); const heading = (text) => rich ? theme.heading(text) : text; const ok = (text) => rich ? theme.success(text) : text; const warn = (text) => rich ? theme.warn(text) : text; const fail = (text) => rich ? theme.error(text) : text; const muted = (text) => rich ? theme.muted(text) : text; const tableWidth = getTerminalTableWidth(); const lines = []; if (params.configDiagnostics) lines.push(warn("Config diagnostics:"), ...formatStatusConfigDiagnosticEntries(params.configDiagnostics), ""); lines.push(heading("OpenClaw status --all")); appendStatusReportSections({ lines, heading, width: tableWidth, renderTable, sections: [ { kind: "table", title: "Overview", columns: [...statusOverviewTableColumns], rows: params.overviewRows }, { kind: "table", title: "Channels", columns: statusChannelsTableColumns.map((column) => column.key === "Detail" ? Object.assign({}, column, { minWidth: 28 }) : column), rows: buildStatusChannelsTableRows({ rows: params.channels.rows, channelIssues: params.channelIssues, ok, warn, muted, accentDim: theme.accentDim, formatIssueMessage: (message) => truncateUtf16Safe(message, 90) }) }, ...buildStatusChannelDetailSections({ details: params.channels.details, ok, warn }), { kind: "table", title: "Agents", columns: [...statusAgentsTableColumns], rows: buildStatusAgentTableRows({ agentStatus: params.agentStatus, ok, warn }) } ] }); appendStatusSectionHeading({ lines, heading, title: "Diagnosis (read-only)" }); await appendStatusAllDiagnosis({ lines, progress: params.progress, muted, ok, warn, fail, connectionDetailsForReport: params.connectionDetailsForReport, ...params.diagnosis }); return lines; } //#endregion //#region src/commands/status-all.ts /** Runs the full read-only status report and writes it to the runtime logger. */ async function statusAllCommand(runtime, opts) { await withProgress({ label: "Scanning status --all…", total: 11 }, async (progress) => { const overview = await collectStatusScanOverview({ env: process.env, commandName: "status --all", opts: { timeoutMs: opts?.timeoutMs }, showSecrets: false, runtime, useGatewayCallOverridesForChannelsStatus: true, includeAdvertisedControlUiLinks: true, progress, labels: { loadingConfig: "Loading config…", checkingTailscale: "Checking Tailscale…", checkingForUpdates: "Checking for updates…", resolvingAgents: "Scanning agents…", probingGateway: "Probing gateway…", queryingChannelStatus: "Querying gateway…", summarizingChannels: "Summarizing channels…" } }); progress.setLabel("Checking services…"); const [daemon, nodeService] = await resolveStatusServiceSummaries(opts?.timeoutMs); const nodeOnlyGateway = await resolveNodeOnlyGatewayInfo({ daemon, node: nodeService }); progress.tick(); const lines = await buildStatusAllReportLines({ progress, ...await buildStatusAllReportData({ overview, daemon, nodeService, nodeOnlyGateway, progress, timeoutMs: opts?.timeoutMs }) }); if (opts?.usage) { const usage = await resolveStatusUsageSummary({ config: overview.cfg, timeoutMs: opts.timeoutMs, ...opts.agent ? { agentId: opts.agent } : {} }); lines.push("", ...formatUsageReportLines(usage)); } progress.setLabel("Rendering…"); runtime.log(lines.join("\n")); progress.tick(); }); } //#endregion export { statusAllCommand };