UNPKG

@gguf/claw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,356 lines (1,341 loc) 76.1 kB
import { u as resolveGatewayPort } from "./paths-B4BZAPZh.js"; import { B as theme, k as info, z as isRich } from "./utils-CP9YLh6M.js"; import { D as resolveSessionModelRef, G as readRestartSentinel, Jt as sha256HexPrefix, K as summarizeRestartSentinel, St as formatUsageReportLines, a as buildChannelSummary, ar as lookupContextTokens, b as classifySessionKey, gr as peekSystemEvents, o as buildChannelAccountSnapshot, s as formatChannelAllowFrom, x as listAgentsForGateway, xt as loadProviderUsageSummary } from "./reply-BylFHBd4.js"; import { x as parseAgentSessionKey } from "./session-key-CZ6OwgSB.js"; import { t as resolveOpenClawPackageRoot } from "./openclaw-root-BM-yQZH6.js"; import { n as runExec } from "./exec-DYqRzFbo.js"; import { c as resolveAgentWorkspaceDir } from "./agent-scope-BnZW9Gh2.js"; import { Gt as DEFAULT_PROVIDER, Ut as DEFAULT_CONTEXT_TOKENS, Wt as DEFAULT_MODEL, p as resolveConfiguredModelRef } from "./model-selection-CqaTAlhy.js"; import { t as formatCliCommand } from "./command-format-DEKzLnLg.js"; import { V as VERSION, i as loadConfig, o as readConfigFileSnapshot } from "./config-PQiujvsf.js"; import { o as getTailnetHostname, s as readTailscaleStatusJson } from "./tailscale-C3JsBEiY.js"; import { a as inspectPortUsage, s as formatPortDiagnostics } from "./ports-DwDxX6cS.js"; import { L as resolveFreshSessionTotalTokens, V as resolveMainSessionKey, a as loadSessionStore } from "./sessions-Z1BZU3xh.js"; import { n as listChannelPlugins } from "./plugins-CKbXkuXd.js"; import { c as resolveStorePath } from "./paths-C2NfoGZE.js"; import { r as getMemorySearchManager } from "./memory-cli-DxmtLhh_.js"; import { n as callGateway, t as buildGatewayConnectionDetails } from "./call-BCz_8mqq.js"; import { n as formatTimeAgo } from "./format-relative-DdvuDKIv.js"; import { n as withProgress } from "./progress-CaVMHLaE.js"; import { r as formatDurationPrecise } from "./format-duration-pIcWg99c.js"; import { o as getRemoteSkillEligibility } from "./skill-commands-N8Sho46-.js"; import { C as normalizeControlUiBasePath, g as resolveControlUiLinks } from "./onboard-helpers-DbjfBSMX.js"; import { t as buildWorkspaceSkillStatus } from "./skills-status-kQ1GipOi.js"; import { t as resolveChannelDefaultAccountId } from "./helpers-tGq4L3A8.js"; import { t as collectChannelStatusIssues } from "./channels-status-issues-ClkP4ZUC.js"; import { s as resolveGatewayLogPaths, t as resolveGatewayService } from "./service-Bw9h81em.js"; import { t as formatRuntimeStatusWithDetails } from "./runtime-status-D0P5lDGf.js"; import { t as readLastGatewayErrorLine } from "./diagnostics-CArN76wS.js"; import { t as renderTable } from "./table-D-EOwMue.js"; import { a as resolveGatewayProbeAuth$1, c as probeGateway, t as runSecurityAudit } from "./audit-BjSHYL1a.js"; import { a as resolveHeartbeatSummaryForAgent, t as formatHealthChannelLines } from "./health-Bt3xmMjK.js"; import { g as resolveUpdateChannelDisplay, i as formatGitInstallLabel, m as normalizeUpdateChannel, t as checkUpdateStatus } from "./update-check-CHLDbXI4.js"; import { t as resolveNodeService } from "./node-service-x4yUQJuC.js"; import { n as redactSecrets, t as formatGatewayAuthUsed } from "./format-B0yJsjEo.js"; import { i as resolveUpdateAvailability, n as formatUpdateOneLiner, r as getUpdateCheckResult, t as formatUpdateAvailableHint } from "./status.update-BdrAB0yT.js"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import fs$1 from "node:fs/promises"; import { spawnSync } from "node:child_process"; //#region src/commands/gateway-presence.ts function pickGatewaySelfPresence(presence) { if (!Array.isArray(presence)) return null; const entries = presence; const self = entries.find((e) => e.mode === "gateway" && e.reason === "self") ?? entries.find((e) => typeof e.text === "string" && String(e.text).startsWith("Gateway:")) ?? null; if (!self) return null; return { host: typeof self.host === "string" ? self.host : void 0, ip: typeof self.ip === "string" ? self.ip : void 0, version: typeof self.version === "string" ? self.version : void 0, platform: typeof self.platform === "string" ? self.platform : void 0 }; } //#endregion //#region src/memory/status-format.ts function resolveMemoryVectorState(vector) { if (!vector.enabled) return { tone: "muted", state: "disabled" }; if (vector.available === true) return { tone: "ok", state: "ready" }; if (vector.available === false) return { tone: "warn", state: "unavailable" }; return { tone: "muted", state: "unknown" }; } function resolveMemoryFtsState(fts) { if (!fts.enabled) return { tone: "muted", state: "disabled" }; return fts.available ? { tone: "ok", state: "ready" } : { tone: "warn", state: "unavailable" }; } function resolveMemoryCacheSummary(cache) { if (!cache.enabled) return { tone: "muted", text: "cache off" }; return { tone: "ok", text: `cache on${typeof cache.entries === "number" ? ` (${cache.entries})` : ""}` }; } //#endregion //#region src/infra/os-summary.ts function safeTrim(value) { return typeof value === "string" ? value.trim() : ""; } function macosVersion() { return safeTrim(spawnSync("sw_vers", ["-productVersion"], { encoding: "utf-8" }).stdout) || os.release(); } function resolveOsSummary() { const platform = os.platform(); const release = os.release(); const arch = os.arch(); return { platform, arch, release, label: (() => { if (platform === "darwin") return `macos ${macosVersion()} (${arch})`; if (platform === "win32") return `windows ${release} (${arch})`; return `${platform} ${release} (${arch})`; })() }; } //#endregion //#region src/commands/status-all/agents.ts async function fileExists$1(p) { try { await fs$1.access(p); return true; } catch { return false; } } async function getAgentLocalStatuses$1(cfg) { const agentList = listAgentsForGateway(cfg); const now = Date.now(); const agents = await Promise.all(agentList.agents.map(async (agent) => { const workspaceDir = (() => { try { return resolveAgentWorkspaceDir(cfg, agent.id); } catch { return null; } })(); const bootstrapPending = workspaceDir != null ? await fileExists$1(path.join(workspaceDir, "BOOTSTRAP.md")) : null; const sessionsPath = resolveStorePath(cfg.session?.store, { agentId: agent.id }); const store = (() => { try { return loadSessionStore(sessionsPath); } catch { return {}; } })(); const updatedAt = Object.values(store).reduce((max, entry) => Math.max(max, entry?.updatedAt ?? 0), 0); const lastUpdatedAt = updatedAt > 0 ? updatedAt : null; const lastActiveAgeMs = lastUpdatedAt ? now - lastUpdatedAt : null; const sessionsCount = Object.keys(store).filter((k) => k !== "global" && k !== "unknown").length; return { id: agent.id, name: agent.name, workspaceDir, bootstrapPending, sessionsPath, sessionsCount, lastUpdatedAt, lastActiveAgeMs }; })); const totalSessions = agents.reduce((sum, a) => sum + a.sessionsCount, 0); const bootstrapPendingCount = agents.reduce((sum, a) => sum + (a.bootstrapPending ? 1 : 0), 0); return { defaultId: agentList.defaultId, agents, totalSessions, bootstrapPendingCount }; } //#endregion //#region src/commands/status-all/channels.ts const asRecord = (value) => value && typeof value === "object" ? value : {}; function summarizeSources(sources) { const counts = /* @__PURE__ */ new Map(); for (const s of sources) { const key = s?.trim() ? s.trim() : "unknown"; counts.set(key, (counts.get(key) ?? 0) + 1); } const parts = [...counts.entries()].toSorted((a, b) => b[1] - a[1]).map(([key, n]) => `${key}${n > 1 ? `×${n}` : ""}`); return { label: parts.length > 0 ? parts.join("+") : "unknown", parts }; } function existsSyncMaybe(p) { const path = p?.trim() || ""; if (!path) return null; try { return fs.existsSync(path); } catch { return null; } } function formatTokenHint(token, opts) { const t = token.trim(); if (!t) return "empty"; if (!opts.showSecrets) return `sha256:${sha256HexPrefix(t, 8)} · len ${t.length}`; const head = t.slice(0, 4); const tail = t.slice(-4); if (t.length <= 10) return `${t} · len ${t.length}`; return `${head}…${tail} · len ${t.length}`; } const formatAccountLabel = (params) => { const base = params.accountId || "default"; if (params.name?.trim()) return `${base} (${params.name.trim()})`; return base; }; const resolveAccountEnabled = (plugin, account, cfg) => { if (plugin.config.isEnabled) return plugin.config.isEnabled(account, cfg); return asRecord(account).enabled !== false; }; const resolveAccountConfigured = async (plugin, account, cfg) => { if (plugin.config.isConfigured) return await plugin.config.isConfigured(account, cfg); return asRecord(account).configured !== false; }; const buildAccountNotes = (params) => { const { plugin, cfg, entry } = params; const notes = []; const snapshot = entry.snapshot; if (snapshot.enabled === false) notes.push("disabled"); if (snapshot.dmPolicy) notes.push(`dm:${snapshot.dmPolicy}`); if (snapshot.tokenSource && snapshot.tokenSource !== "none") notes.push(`token:${snapshot.tokenSource}`); if (snapshot.botTokenSource && snapshot.botTokenSource !== "none") notes.push(`bot:${snapshot.botTokenSource}`); if (snapshot.appTokenSource && snapshot.appTokenSource !== "none") notes.push(`app:${snapshot.appTokenSource}`); if (snapshot.baseUrl) notes.push(snapshot.baseUrl); if (snapshot.port != null) notes.push(`port:${snapshot.port}`); if (snapshot.cliPath) notes.push(`cli:${snapshot.cliPath}`); if (snapshot.dbPath) notes.push(`db:${snapshot.dbPath}`); const allowFrom = plugin.config.resolveAllowFrom?.({ cfg, accountId: snapshot.accountId }) ?? snapshot.allowFrom; if (allowFrom?.length) { const formatted = formatChannelAllowFrom({ plugin, cfg, accountId: snapshot.accountId, allowFrom }).slice(0, 3); if (formatted.length > 0) notes.push(`allow:${formatted.join(",")}`); } return notes; }; function resolveLinkFields(summary) { const rec = asRecord(summary); const linked = typeof rec.linked === "boolean" ? rec.linked : null; const authAgeMs = typeof rec.authAgeMs === "number" ? rec.authAgeMs : null; const self = asRecord(rec.self); return { linked, authAgeMs, selfE164: typeof self.e164 === "string" && self.e164.trim() ? self.e164.trim() : null }; } function collectMissingPaths(accounts) { const missing = []; for (const entry of accounts) { const accountRec = asRecord(entry.account); const snapshotRec = asRecord(entry.snapshot); for (const key of [ "tokenFile", "botTokenFile", "appTokenFile", "cliPath", "dbPath", "authDir" ]) { const raw = accountRec[key] ?? snapshotRec[key]; if (existsSyncMaybe(raw) === false) missing.push(String(raw)); } } return missing; } function summarizeTokenConfig(params) { const enabled = params.accounts.filter((a) => a.enabled); if (enabled.length === 0) return { state: null, detail: null }; const accountRecs = enabled.map((a) => asRecord(a.account)); const hasBotTokenField = accountRecs.some((r) => "botToken" in r); const hasAppTokenField = accountRecs.some((r) => "appToken" in r); const hasTokenField = accountRecs.some((r) => "token" in r); if (!hasBotTokenField && !hasAppTokenField && !hasTokenField) return { state: null, detail: null }; if (hasBotTokenField && hasAppTokenField) { const ready = enabled.filter((a) => { const rec = asRecord(a.account); const bot = typeof rec.botToken === "string" ? rec.botToken.trim() : ""; const app = typeof rec.appToken === "string" ? rec.appToken.trim() : ""; return Boolean(bot) && Boolean(app); }); const partial = enabled.filter((a) => { const rec = asRecord(a.account); const bot = typeof rec.botToken === "string" ? rec.botToken.trim() : ""; const app = typeof rec.appToken === "string" ? rec.appToken.trim() : ""; const hasBot = Boolean(bot); const hasApp = Boolean(app); return hasBot && !hasApp || !hasBot && hasApp; }); if (partial.length > 0) return { state: "warn", detail: `partial tokens (need bot+app) · accounts ${partial.length}` }; if (ready.length === 0) return { state: "setup", detail: "no tokens (need bot+app)" }; const botSources = summarizeSources(ready.map((a) => a.snapshot.botTokenSource ?? "none")); const appSources = summarizeSources(ready.map((a) => a.snapshot.appTokenSource ?? "none")); const sample = ready[0]?.account ? asRecord(ready[0].account) : {}; const botToken = typeof sample.botToken === "string" ? sample.botToken : ""; const appToken = typeof sample.appToken === "string" ? sample.appToken : ""; const botHint = botToken.trim() ? formatTokenHint(botToken, { showSecrets: params.showSecrets }) : ""; const appHint = appToken.trim() ? formatTokenHint(appToken, { showSecrets: params.showSecrets }) : ""; const hint = botHint || appHint ? ` (bot ${botHint || "?"}, app ${appHint || "?"})` : ""; return { state: "ok", detail: `tokens ok (bot ${botSources.label}, app ${appSources.label})${hint} · accounts ${ready.length}/${enabled.length || 1}` }; } if (hasBotTokenField) { const ready = enabled.filter((a) => { const rec = asRecord(a.account); const bot = typeof rec.botToken === "string" ? rec.botToken.trim() : ""; return Boolean(bot); }); if (ready.length === 0) return { state: "setup", detail: "no bot token" }; const sample = ready[0]?.account ? asRecord(ready[0].account) : {}; const botToken = typeof sample.botToken === "string" ? sample.botToken : ""; const botHint = botToken.trim() ? formatTokenHint(botToken, { showSecrets: params.showSecrets }) : ""; return { state: "ok", detail: `bot token config${botHint ? ` (${botHint})` : ""} · accounts ${ready.length}/${enabled.length || 1}` }; } const ready = enabled.filter((a) => { const rec = asRecord(a.account); return typeof rec.token === "string" ? Boolean(rec.token.trim()) : false; }); if (ready.length === 0) return { state: "setup", detail: "no token" }; const sources = summarizeSources(ready.map((a) => a.snapshot.tokenSource)); const sample = ready[0]?.account ? asRecord(ready[0].account) : {}; const token = typeof sample.token === "string" ? sample.token : ""; const hint = token.trim() ? ` (${formatTokenHint(token, { showSecrets: params.showSecrets })})` : ""; return { state: "ok", detail: `token ${sources.label}${hint} · accounts ${ready.length}/${enabled.length || 1}` }; } async function buildChannelsTable(cfg, opts) { const showSecrets = opts?.showSecrets === true; const rows = []; const details = []; for (const plugin of listChannelPlugins()) { const accountIds = plugin.config.listAccountIds(cfg); const defaultAccountId = resolveChannelDefaultAccountId({ plugin, cfg, accountIds }); const resolvedAccountIds = accountIds.length > 0 ? accountIds : [defaultAccountId]; const accounts = []; for (const accountId of resolvedAccountIds) { const account = plugin.config.resolveAccount(cfg, accountId); const enabled = resolveAccountEnabled(plugin, account, cfg); const configured = await resolveAccountConfigured(plugin, account, cfg); const snapshot = buildChannelAccountSnapshot({ plugin, cfg, accountId, account, enabled, configured }); accounts.push({ accountId, account, enabled, configured, snapshot }); } const anyEnabled = accounts.some((a) => a.enabled); const enabledAccounts = accounts.filter((a) => a.enabled); const configuredAccounts = enabledAccounts.filter((a) => a.configured); const defaultEntry = accounts.find((a) => a.accountId === defaultAccountId) ?? accounts[0]; const link = resolveLinkFields(plugin.status?.buildChannelSummary ? await plugin.status.buildChannelSummary({ account: defaultEntry?.account ?? {}, cfg, defaultAccountId, snapshot: defaultEntry?.snapshot ?? { accountId: defaultAccountId } }) : void 0); const missingPaths = collectMissingPaths(enabledAccounts); const tokenSummary = summarizeTokenConfig({ plugin, cfg, accounts, showSecrets }); const issues = plugin.status?.collectStatusIssues ? plugin.status.collectStatusIssues(accounts.map((a) => a.snapshot)) : []; const label = plugin.meta.label ?? plugin.id; const state = (() => { if (!anyEnabled) return "off"; if (missingPaths.length > 0) return "warn"; if (issues.length > 0) return "warn"; if (link.linked === false) return "setup"; if (tokenSummary.state) return tokenSummary.state; if (link.linked === true) return "ok"; if (configuredAccounts.length > 0) return "ok"; return "setup"; })(); const detail = (() => { if (!anyEnabled) { if (!defaultEntry) return "disabled"; return plugin.config.disabledReason?.(defaultEntry.account, cfg) ?? "disabled"; } if (missingPaths.length > 0) return `missing file (${missingPaths[0]})`; if (issues.length > 0) return issues[0]?.message ?? "misconfigured"; if (link.linked !== null) { const base = link.linked ? "linked" : "not linked"; const extra = []; if (link.linked && link.selfE164) extra.push(link.selfE164); if (link.linked && link.authAgeMs != null && link.authAgeMs >= 0) extra.push(`auth ${formatTimeAgo(link.authAgeMs)}`); if (accounts.length > 1 || plugin.meta.forceAccountBinding) extra.push(`accounts ${accounts.length || 1}`); return extra.length > 0 ? `${base} · ${extra.join(" · ")}` : base; } if (tokenSummary.detail) return tokenSummary.detail; if (configuredAccounts.length > 0) { const head = "configured"; if (accounts.length <= 1 && !plugin.meta.forceAccountBinding) return head; return `${head} · accounts ${configuredAccounts.length}/${enabledAccounts.length || 1}`; } return (defaultEntry && plugin.config.unconfiguredReason ? plugin.config.unconfiguredReason(defaultEntry.account, cfg) : null) ?? "not configured"; })(); rows.push({ id: plugin.id, label, enabled: anyEnabled, state, detail }); if (configuredAccounts.length > 0) details.push({ title: `${label} accounts`, columns: [ "Account", "Status", "Notes" ], rows: configuredAccounts.map((entry) => { const notes = buildAccountNotes({ plugin, cfg, entry }); return { Account: formatAccountLabel({ accountId: entry.accountId, name: entry.snapshot.name }), Status: entry.enabled ? "OK" : "WARN", Notes: notes.join(" · ") }; }) }); } return { rows, details }; } //#endregion //#region src/commands/status-all/gateway.ts async function readFileTailLines(filePath, maxLines) { const raw = await fs$1.readFile(filePath, "utf8").catch(() => ""); if (!raw.trim()) return []; const lines = raw.replace(/\r/g, "").split("\n"); return lines.slice(Math.max(0, lines.length - maxLines)).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0); } function countMatches(haystack, needle) { if (!haystack || !needle) return 0; return haystack.split(needle).length - 1; } function shorten(message, maxLen) { const cleaned = message.replace(/\s+/g, " ").trim(); if (cleaned.length <= maxLen) return cleaned; return `${cleaned.slice(0, 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 parts = [startLine.slice(braceAt)]; let depth = countMatches(parts[0] ?? "", "{") - countMatches(parts[0] ?? "", "}"); let i = startIndex; while (depth > 0 && i + 1 < lines.length) { i += 1; const next = lines[i] ?? ""; parts.push(next); depth += countMatches(next, "{") - countMatches(next, "}"); } return { json: parts.join("\n"), endIndex: i }; } 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 = (() => { try { return JSON.parse(block.json); } catch { return null; } })(); const code = parsed?.error?.code?.trim() || null; const msg = parsed?.error?.message?.trim() || null; const msgShort = msg ? msg.toLowerCase().includes("signing in again") ? "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 = embedded[1]?.trim() || "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 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}`); }; lines.push(""); lines.push(muted("Gateway connection details:")); for (const line of redactSecrets(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(` - ${issue.path}: ${issue.message}`); 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.")}`); } if (params.sentinel?.payload) { emitCheck("Restart sentinel present", "warn"); lines.push(` ${muted(`${summarizeRestartSentinel(params.sentinel.payload)} · ${formatTimeAgo(Date.now() - params.sentinel.payload.ts)}`)}`); } else emitCheck("Restart sentinel: none", "ok"); const lastErrClean = params.lastErr?.trim() ?? ""; const isTrivialLastErr = lastErrClean.length < 8 || lastErrClean === "}" || lastErrClean === "{"; if (lastErrClean && !isTrivialLastErr) { lines.push(""); lines.push(muted("Gateway last log line:")); lines.push(` ${muted(redactSecrets(lastErrClean))}`); } if (params.portUsage) { const portOk = params.portUsage.listeners.length === 0; emitCheck(`Port ${params.port}`, portOk ? "ok" : "warn"); if (!portOk) for (const line of formatPortDiagnostics(params.portUsage)) lines.push(` ${muted(line)}`); } { const backend = params.tailscale.backendState ?? "unknown"; const okBackend = backend === "Running"; const hasDns = Boolean(params.tailscale.dnsName); emitCheck(params.tailscaleMode === "off" ? `Tailscale: off · ${backend}${params.tailscale.dnsName ? ` · ${params.tailscale.dnsName}` : ""}` : `Tailscale: ${params.tailscaleMode} · ${backend}${params.tailscale.dnsName ? ` · ${params.tailscale.dnsName}` : ""}`, okBackend && (params.tailscaleMode === "off" || 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"); } params.progress.setLabel("Reading logs…"); const logPaths = (() => { try { return resolveGatewayLogPaths(process.env); } catch { return null; } })(); if (logPaths) { params.progress.setLabel("Reading logs…"); const [stderrTail, stdoutTail] = await Promise.all([readFileTailLines(logPaths.stderrPath, 40).catch(() => []), readFileTailLines(logPaths.stdoutPath, 40).catch(() => [])]); if (stderrTail.length > 0 || stdoutTail.length > 0) { lines.push(""); lines.push(muted(`Gateway logs (tail, summarized): ${logPaths.logDir}`)); lines.push(` ${muted(`# stderr: ${logPaths.stderrPath}`)}`); for (const line of summarizeLogTail(stderrTail, { maxLines: 22 }).map(redactSecrets)) lines.push(` ${muted(line)}`); lines.push(` ${muted(`# stdout: ${logPaths.stdoutPath}`)}`); for (const line of summarizeLogTail(stdoutTail, { maxLines: 22 }).map(redactSecrets)) 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 emitCheck(`Channel issues skipped (gateway ${params.gatewayReachable ? "query failed" : "unreachable"})`, "warn"); const healthErr = (() => { if (!params.health || typeof params.health !== "object") return ""; const record = params.health; if (!("error" in record)) return ""; const value = record.error; if (!value) return ""; if (typeof value === "string") return value; try { return JSON.stringify(value, null, 2); } catch { return "[unserializable error]"; } })(); if (healthErr) { lines.push(""); lines.push(muted("Gateway health:")); lines.push(` ${muted(redactSecrets(healthErr))}`); } 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 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 = Math.max(60, (process.stdout.columns ?? 120) - 1); const overview = renderTable({ width: tableWidth, columns: [{ key: "Item", header: "Item", minWidth: 10 }, { key: "Value", header: "Value", flex: true, minWidth: 24 }], rows: params.overviewRows }); const channelRows = params.channels.rows.map((row) => ({ channelId: row.id, Channel: row.label, Enabled: row.enabled ? ok("ON") : muted("OFF"), State: row.state === "ok" ? ok("OK") : row.state === "warn" ? warn("WARN") : row.state === "off" ? muted("OFF") : theme.accentDim("SETUP"), Detail: row.detail })); const channelIssuesByChannel = (() => { const map = /* @__PURE__ */ new Map(); for (const issue of params.channelIssues) { const key = issue.channel; const list = map.get(key); if (list) list.push(issue); else map.set(key, [issue]); } return map; })(); const channelsTable = renderTable({ width: tableWidth, columns: [ { key: "Channel", header: "Channel", minWidth: 10 }, { key: "Enabled", header: "Enabled", minWidth: 7 }, { key: "State", header: "State", minWidth: 8 }, { key: "Detail", header: "Detail", flex: true, minWidth: 28 } ], rows: channelRows.map((row) => { const issues = channelIssuesByChannel.get(row.channelId) ?? []; if (issues.length === 0) return row; const issue = issues[0]; const suffix = ` · ${warn(`gateway: ${String(issue.message).slice(0, 90)}`)}`; return { ...row, State: warn("WARN"), Detail: `${row.Detail}${suffix}` }; }) }); const agentsTable = renderTable({ width: tableWidth, columns: [ { key: "Agent", header: "Agent", minWidth: 12 }, { key: "Bootstrap", header: "Bootstrap", minWidth: 10 }, { key: "Sessions", header: "Sessions", align: "right", minWidth: 8 }, { key: "Active", header: "Active", minWidth: 10 }, { key: "Store", header: "Store", flex: true, minWidth: 34 } ], rows: params.agentStatus.agents.map((a) => ({ Agent: a.name?.trim() ? `${a.id} (${a.name.trim()})` : a.id, Bootstrap: a.bootstrapPending === true ? warn("PENDING") : a.bootstrapPending === false ? ok("OK") : "unknown", Sessions: String(a.sessionsCount), Active: a.lastActiveAgeMs != null ? formatTimeAgo(a.lastActiveAgeMs) : "unknown", Store: a.sessionsPath })) }); const lines = []; lines.push(heading("OpenClaw status --all")); lines.push(""); lines.push(heading("Overview")); lines.push(overview.trimEnd()); lines.push(""); lines.push(heading("Channels")); lines.push(channelsTable.trimEnd()); for (const detail of params.channels.details) { lines.push(""); lines.push(heading(detail.title)); lines.push(renderTable({ width: tableWidth, columns: detail.columns.map((c) => ({ key: c, header: c, flex: c === "Notes", minWidth: c === "Notes" ? 28 : 10 })), rows: detail.rows.map((r) => ({ ...r, ...r.Status === "OK" ? { Status: ok("OK") } : r.Status === "WARN" ? { Status: warn("WARN") } : {} })) }).trimEnd()); } lines.push(""); lines.push(heading("Agents")); lines.push(agentsTable.trimEnd()); lines.push(""); lines.push(heading("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 async function statusAllCommand(runtime, opts) { await withProgress({ label: "Scanning status --all…", total: 11 }, async (progress) => { progress.setLabel("Loading config…"); const cfg = loadConfig(); const osSummary = resolveOsSummary(); const snap = await readConfigFileSnapshot().catch(() => null); progress.tick(); progress.setLabel("Checking Tailscale…"); const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off"; const tailscale = await (async () => { try { const parsed = await readTailscaleStatusJson(runExec, { timeoutMs: 1200 }); const backendState = typeof parsed.BackendState === "string" ? parsed.BackendState : null; const self = typeof parsed.Self === "object" && parsed.Self !== null ? parsed.Self : null; const dnsNameRaw = self && typeof self.DNSName === "string" ? self.DNSName : null; return { ok: true, backendState, dnsName: dnsNameRaw ? dnsNameRaw.replace(/\.$/, "") : null, ips: self && Array.isArray(self.TailscaleIPs) ? self.TailscaleIPs.filter((v) => typeof v === "string" && v.trim().length > 0).map((v) => v.trim()) : [], error: null }; } catch (err) { return { ok: false, backendState: null, dnsName: null, ips: [], error: String(err) }; } })(); const tailscaleHttpsUrl = tailscaleMode !== "off" && tailscale.dnsName ? `https://${tailscale.dnsName}${normalizeControlUiBasePath(cfg.gateway?.controlUi?.basePath)}` : null; progress.tick(); progress.setLabel("Checking for updates…"); const update = await checkUpdateStatus({ root: await resolveOpenClawPackageRoot({ moduleUrl: import.meta.url, argv1: process.argv[1], cwd: process.cwd() }), timeoutMs: 6500, fetchGit: true, includeRegistry: true }); const channelLabel = resolveUpdateChannelDisplay({ configChannel: normalizeUpdateChannel(cfg.update?.channel), installKind: update.installKind, gitTag: update.git?.tag ?? null, gitBranch: update.git?.branch ?? null }).label; const gitLabel = formatGitInstallLabel(update); progress.tick(); progress.setLabel("Probing gateway…"); const connection = buildGatewayConnectionDetails({ config: cfg }); const isRemoteMode = cfg.gateway?.mode === "remote"; const remoteUrlRaw = typeof cfg.gateway?.remote?.url === "string" ? cfg.gateway.remote.url.trim() : ""; const remoteUrlMissing = isRemoteMode && !remoteUrlRaw; const gatewayMode = isRemoteMode ? "remote" : "local"; const localFallbackAuth = resolveGatewayProbeAuth$1({ cfg, mode: "local" }); const remoteAuth = resolveGatewayProbeAuth$1({ cfg, mode: "remote" }); const probeAuth = isRemoteMode && !remoteUrlMissing ? remoteAuth : localFallbackAuth; const gatewayProbe = await probeGateway({ url: connection.url, auth: probeAuth, timeoutMs: Math.min(5e3, opts?.timeoutMs ?? 1e4) }).catch(() => null); const gatewayReachable = gatewayProbe?.ok === true; const gatewaySelf = pickGatewaySelfPresence(gatewayProbe?.presence ?? null); progress.tick(); progress.setLabel("Checking services…"); const readServiceSummary = async (service) => { try { const [loaded, runtimeInfo, command] = await Promise.all([ service.isLoaded({ env: process.env }).catch(() => false), service.readRuntime(process.env).catch(() => void 0), service.readCommand(process.env).catch(() => null) ]); const installed = command != null; return { label: service.label, installed, loaded, loadedText: loaded ? service.loadedText : service.notLoadedText, runtime: runtimeInfo }; } catch { return null; } }; const daemon = await readServiceSummary(resolveGatewayService()); const nodeService = await readServiceSummary(resolveNodeService()); progress.tick(); progress.setLabel("Scanning agents…"); const agentStatus = await getAgentLocalStatuses$1(cfg); progress.tick(); progress.setLabel("Summarizing channels…"); const channels = await buildChannelsTable(cfg, { showSecrets: false }); progress.tick(); const connectionDetailsForReport = (() => { if (!remoteUrlMissing) return connection.message; const bindMode = cfg.gateway?.bind ?? "loopback"; return [ "Gateway mode: remote", "Gateway target: (missing gateway.remote.url)", `Config: ${snap?.path?.trim() ? snap.path.trim() : "(unknown config path)"}`, `Bind: ${bindMode}`, `Local fallback (used for probes): ${connection.url}`, "Fix: set gateway.remote.url, or set gateway.mode=local." ].join("\n"); })(); const callOverrides = remoteUrlMissing ? { url: connection.url, token: localFallbackAuth.token, password: localFallbackAuth.password } : {}; progress.setLabel("Querying gateway…"); const health = gatewayReachable ? await callGateway({ method: "health", timeoutMs: Math.min(8e3, opts?.timeoutMs ?? 1e4), ...callOverrides }).catch((err) => ({ error: String(err) })) : { error: gatewayProbe?.error ?? "gateway unreachable" }; const channelsStatus = gatewayReachable ? await callGateway({ method: "channels.status", params: { probe: false, timeoutMs: opts?.timeoutMs ?? 1e4 }, timeoutMs: Math.min(8e3, opts?.timeoutMs ?? 1e4), ...callOverrides }).catch(() => null) : null; const channelIssues = channelsStatus ? collectChannelStatusIssues(channelsStatus) : []; progress.tick(); progress.setLabel("Checking local state…"); const sentinel = await readRestartSentinel().catch(() => null); const lastErr = await readLastGatewayErrorLine(process.env).catch(() => null); const port = resolveGatewayPort(cfg); const portUsage = await inspectPortUsage(port).catch(() => null); progress.tick(); const defaultWorkspace = agentStatus.agents.find((a) => a.id === agentStatus.defaultId)?.workspaceDir ?? agentStatus.agents[0]?.workspaceDir ?? null; const skillStatus = defaultWorkspace != null ? (() => { try { return buildWorkspaceSkillStatus(defaultWorkspace, { config: cfg, eligibility: { remote: getRemoteSkillEligibility() } }); } catch { return null; } })() : null; const dashboard = cfg.gateway?.controlUi?.enabled ?? true ? resolveControlUiLinks({ port, bind: cfg.gateway?.bind, customBindHost: cfg.gateway?.customBindHost, basePath: cfg.gateway?.controlUi?.basePath }).httpUrl : null; const updateLine = formatUpdateOneLiner(update).replace(/^Update:\s*/i, ""); const gatewayTarget = remoteUrlMissing ? `fallback ${connection.url}` : connection.url; const gatewayStatus = gatewayReachable ? `reachable ${formatDurationPrecise(gatewayProbe?.connectLatencyMs ?? 0)}` : gatewayProbe?.error ? `unreachable (${gatewayProbe.error})` : "unreachable"; const gatewayAuth = gatewayReachable ? ` · auth ${formatGatewayAuthUsed(probeAuth)}` : ""; const gatewaySelfLine = gatewaySelf?.host || gatewaySelf?.ip || gatewaySelf?.version || gatewaySelf?.platform ? [ gatewaySelf.host ? gatewaySelf.host : null, gatewaySelf.ip ? `(${gatewaySelf.ip})` : null, gatewaySelf.version ? `app ${gatewaySelf.version}` : null, gatewaySelf.platform ? gatewaySelf.platform : null ].filter(Boolean).join(" ") : null; const aliveThresholdMs = 10 * 6e4; const aliveAgents = agentStatus.agents.filter((a) => a.lastActiveAgeMs != null && a.lastActiveAgeMs <= aliveThresholdMs).length; const lines = await buildStatusAllReportLines({ progress, overviewRows: [ { Item: "Version", Value: VERSION }, { Item: "OS", Value: osSummary.label }, { Item: "Node", Value: process.versions.node }, { Item: "Config", Value: snap?.path?.trim() ? snap.path.trim() : "(unknown config path)" }, dashboard ? { Item: "Dashboard", Value: dashboard } : { Item: "Dashboard", Value: "disabled" }, { Item: "Tailscale", Value: tailscaleMode === "off" ? `off${tailscale.backendState ? ` · ${tailscale.backendState}` : ""}${tailscale.dnsName ? ` · ${tailscale.dnsName}` : ""}` : tailscale.dnsName && tailscaleHttpsUrl ? `${tailscaleMode} · ${tailscale.backendState ?? "unknown"} · ${tailscale.dnsName} · ${tailscaleHttpsUrl}` : `${tailscaleMode} · ${tailscale.backendState ?? "unknown"} · magicdns unknown` }, { Item: "Channel", Value: channelLabel }, ...gitLabel ? [{ Item: "Git", Value: gitLabel }] : [], { Item: "Update", Value: updateLine }, { Item: "Gateway", Value: `${gatewayMode}${remoteUrlMissing ? " (remote.url missing)" : ""} · ${gatewayTarget} (${connection.urlSource}) · ${gatewayStatus}${gatewayAuth}` }, { Item: "Security", Value: `Run: ${formatCliCommand("openclaw security audit --deep")}` }, gatewaySelfLine ? { Item: "Gateway self", Value: gatewaySelfLine } : { Item: "Gateway self", Value: "unknown" }, daemon ? { Item: "Gateway service", Value: !daemon.installed ? `${daemon.label} not installed` : `${daemon.label} ${daemon.installed ? "installed · " : ""}${daemon.loadedText}${daemon.runtime?.status ? ` · ${daemon.runtime.status}` : ""}${daemon.runtime?.pid ? ` (pid ${daemon.runtime.pid})` : ""}` } : { Item: "Gateway service", Value: "unknown" }, nodeService ? { Item: "Node service", Value: !nodeService.installed ? `${nodeService.label} not installed` : `${nodeService.label} ${nodeService.installed ? "installed · " : ""}${nodeService.loadedText}${nodeService.runtime?.status ? ` · ${nodeService.runtime.status}` : ""}${nodeService.runtime?.pid ? ` (pid ${nodeService.runtime.pid})` : ""}` } : { Item: "Node service", Value: "unknown" }, { Item: "Agents", Value: `${agentStatus.agents.length} total · ${agentStatus.bootstrapPendingCount} bootstrapping · ${aliveAgents} active · ${agentStatus.totalSessions} sessions` } ], channels, channelIssues: channelIssues.map((issue) => ({ channel: issue.channel, message: issue.message })), agentStatus, connectionDetailsForReport, diagnosis: { snap, remoteUrlMissing, sentinel, lastErr, port, portUsage, tailscaleMode, tailscale, tailscaleHttpsUrl, skillStatus, channelsStatus, channelIssues, gatewayReachable, health } }); progress.setLabel("Rendering…"); runtime.log(lines.join("\n")); progress.tick(); }); } //#endregion //#region src/commands/status.format.ts const formatKTokens = (value) => `${(value / 1e3).toFixed(value >= 1e4 ? 0 : 1)}k`; const formatDuration = (ms) => { if (ms == null || !Number.isFinite(ms)) return "unknown"; return formatDurationPrecise(ms, { decimals: 1 }); }; const shortenText = (value, maxLen) => { const chars = Array.from(value); if (chars.length <= maxLen) return value; return `${chars.slice(0, Math.max(0, maxLen - 1)).join("")}…`; }; const formatTokensCompact = (sess) => { const used = sess.totalTokens; const ctx = sess.contextTokens; const cacheRead = sess.cacheRead; const cacheWrite = sess.cacheWrite; let result = ""; if (used == null) result = ctx ? `unknown/${formatKTokens(ctx)} (?%)` : "unknown used"; else if (!ctx) result = `${formatKTokens(used)} used`; else { const pctLabel = sess.percentUsed != null ? `${sess.percentUsed}%` : "?%"; result = `${formatKTokens(used)}/${formatKTokens(ctx)} (${pctLabel})`; } if (typeof cacheRead === "number" && cacheRead > 0) { const total = typeof used === "number" ? used : cacheRead + (typeof cacheWrite === "number" ? cacheWrite : 0); const hitRate = Math.round(cacheRead / total * 100); result += ` · 🗄️ ${hitRate}% cached`; } return result; }; const formatDaemonRuntimeShort = (runtime) => { if (!runtime) return null; const details = []; const detail = runtime.detail?.replace(/\s+/g, " ").trim() || ""; const noisyLaunchctlDetail = runtime.missingUnit === true && detail.toLowerCase().includes("could not find service"); if (detail && !noisyLaunchctlDetail) details.push(detail); return formatRuntimeStatusWithDetails({ status: runtime.status, pid: runtime.pid, state: runtime.state, details }); }; //#endregion //#region src/commands/status.daemon.ts async function buildDaemonStatusSummary(service, fallbackLabel) { try { const [loaded, runtime, command] = await Promise.all([ service.isLoaded({ env: process.env }).catch(() => false), service.readRuntime(process.env).catch(() => void 0), service.readCommand(process.env).catch(() => null) ]); const installed = command != null; const loadedText = loaded ? service.loadedText : service.notLoadedText; const runtimeShort = formatDaemonRuntimeShort(runtime); return { label: service.label, installed, loadedText, runtimeShort }; } catch { return { label: fallbackLabel, installed: null, loadedText: "unknown", runtimeShort: null }; } } async function getDaemonStatusSummary() { return await buildDaemonStatusSummary(resolveGatewayService(), "Daemon"); } async function getNodeDaemonStatusSummary() { return await buildDaemonStatusSummary(resolveNodeService(), "Node"); } //#endregion //#region src/commands/status.gateway-probe.ts function resolveGatewayProbeAuth(cfg) { const isRemoteMode = cfg.gateway?.mode === "remote"; const remote = isRemoteMode ? cfg.gateway?.remote : void 0; const authToken = cfg.gateway?.auth?.token; const authPassword = cfg.gateway?.auth?.password; return { token: isRemoteMode ? typeof remote?.token === "string" && remote.token.trim().length > 0 ? remote.token.trim() : void 0 : process.env.OPENCLAW_GATEWAY_TOKEN?.trim() || (typeof authToken === "string" && authToken.trim().length > 0 ? authToken.trim() : void 0), password: process.env.OPENCLAW_GATEWAY_PASSWORD?.trim() || (isRemoteMode ? typeof remote?.password === "string" && remote.password.trim().length > 0 ? remote.password.trim() : void 0 : typeof authPassword === "string" && authPassword.trim().length > 0 ? authPassword.trim() : void 0) }; } //#endregion //#region src/commands/status.agent-local.ts async function fileExists(p) { try { await fs$1.access(p); return true; } catch { return false; } } async function getAgentLocalStatuses() { const cfg = loadConfig(); const agentList = listAgentsForGateway(cfg); const now = Date.now(); const statuses = []; for (const agent of agentList.agents) { const agentId = agent.id; const workspaceDir = (() => { try { return resolveAgentWorkspaceDir(cfg, agentId); } catch { return null; } })(); const bootstrapPath = workspaceDir != null ? path.join(workspaceDir, "BOOTSTRAP.md") : null; const bootstrapPending = bootstrapPath != null ? await fileExists(bootstrapPath) : null; const sessionsPath = resolveStorePath(cfg.session?.store, { agentId }); const store = (() => { try { return loadSessionStore(sessionsPath); } catch { return {}; } })(); const sessions = Object.entries(store).filter(([key]) => key !== "global" && key !== "unknown").map(([, entry]) => entry); const sessionsCount = sessions.length; const lastUpdatedAt = sessions.reduce((max, e) => Math.max(max, e?.updatedAt ?? 0), 0); const resolvedLastUpdatedAt = lastUpdatedAt > 0 ? lastUpdatedAt : null; const lastActiveAgeMs = resolvedLastUpdatedAt ? now - resolvedLastUpdatedAt : null; statuses.push({ id: agentId, name: agent.name, workspaceDir, bootstrapPending, sessionsPath, sessionsCount, lastUpdatedAt: resolvedLastUpdatedAt, lastActiveAgeMs }); } const totalSessions = statuses.reduce((sum, s) => sum + s.sessionsCount, 0); const bootstrapPendingCount = statuses.reduce((sum, s) => sum + (s.bootstrapPending ? 1 : 0), 0); return { defaultId: agentList.defaultId, agents: statuses, totalSessions, bootstrapPendingCount }; } //#endregion //#region src/commands/status.link-channel.ts async function resolveLinkChannelContext(cfg) { for (const plugin of listChannelPlugins()) { const defaultAccountId = resolveChannelDefaultAccountId({ plugin, cfg, accountIds: plugin.config.listAccountIds(cfg) }); const account = plugin.config.resolveAccount(cfg, defaultAccountId); const enabled = plugin.config.isEnabled ? plugin.config.isEnabled(account, cfg) : true; const configured = plugin.config.isConfigured ? await plugin.config.isConfigured(account, cfg) : true; const snapshot = plugin.config.describeAccount ? plugin.config.describeAccount(account, cfg) : { accountId: defaultAccountId, enabled, configured }; const summaryRecord = plugin.status?.buildChannelSummary ? await plugin.status.buildChannelSummary({ account, cfg, defaultAccountId, snapshot }) : void 0; const linked = summaryRecord && typeof summaryRecord.linked === "boolean" ? summaryRecord.linked : null; if (linked === null) continue; return { linked, authAgeMs: summaryRecord && typeof summaryRecord.authAgeMs === "number" ? summaryRecord.authAgeMs : null, account, accountId: defaultAccountId, plugin }; } return null; } //#endregion //#region src/commands/status.summary.ts const buildFlags = (entry) => { if (!entry) return []; const flags = []; con