UNPKG

@gguf/claw

Version:

WhatsApp gateway CLI (Baileys web) with Pi RPC agent

1,304 lines (1,293 loc) 122 kB
import { Ct as probeGatewayReachable, Dt as summarizeExistingConfig, G as readRestartSentinel, K as summarizeRestartSentinel, Nn as getMemorySearchManager, Nt as getRemoteSkillEligibility, Ot as waitForGatewayReachable, St as printWizardHeader, Tt as resolveControlUiLinks, at as formatUsageReportLines, br as peekSystemEvents, bt as normalizeGatewayTokenInput, dt as DEFAULT_WORKSPACE, ft as applyWizardMetadata, gt as formatControlUiSshHint, ht as ensureWorkspaceAndSessions, it as loadProviderUsageSummary, jt as normalizeControlUiBasePath, mt as detectBrowserOpenSupport, p as listAgentsForGateway, pr as lookupContextTokens, vt as handleReset, wt as randomToken, xt as openUrl } from "./reply-B8pOiUNN.js"; import { n as DEFAULT_GATEWAY_PORT, u as resolveGatewayPort } from "./paths-scjhy7N2.js"; import { _ as parseAgentSessionKey, c as normalizeAgentId } from "./session-key-Dm2EOhrH.js"; import { D as info, L as isRich, R as theme, c as defaultRuntime, l as restoreTerminalState } from "./subsystem-CAq3uyo7.js"; import { h as resolveUserPath, v as sleep } from "./utils-CKSrBNwq.js"; import { n as runExec } from "./exec-HEWTMJ7j.js"; import { t as resolveOpenClawPackageRoot } from "./openclaw-root-Cvotktkd.js"; import { c as resolveDefaultAgentId, h as DEFAULT_IDENTITY_FILENAME, p as DEFAULT_BOOTSTRAP_FILENAME, r as resolveAgentDir, s as resolveAgentWorkspaceDir } from "./agent-scope-CMs5Y7l-.js"; import { bt as DEFAULT_PROVIDER, d as resolveConfiguredModelRef, lt as ensureAuthProfileStore, vt as DEFAULT_CONTEXT_TOKENS, yt as DEFAULT_MODEL } from "./model-selection-DMUrNhQP.js"; import { r as resolveCliName, t as formatCliCommand } from "./command-format-ChfKqObn.js"; import { c as writeConfigFile, i as loadConfig, j as VERSION, o as readConfigFileSnapshot } from "./config-CAuZ-EkU.js"; import { n as listChannelPlugins } from "./plugins-BYIWo0Cp.js"; import { L as resolveMainSessionKey, d as loadSessionStore } from "./sandbox-CV8VwPij.js"; import { d as inspectPortUsage, f as resolveLsofCommandSync, p as formatPortDiagnostics } from "./chrome-BNSd7Bie.js"; import { o as resolveStorePath } from "./paths-Bb0nwPeu.js"; import { t as buildChannelSummary } from "./channel-summary-DUiKDBLv.js"; import { n as callGateway, t as buildGatewayConnectionDetails } from "./call-BTbA5OB4.js"; import { n as withProgress } from "./progress-xpLtQsNY.js"; import { a as findTailscaleBinary, o as getTailnetHostname, s as readTailscaleStatusJson } from "./tailscale-BVGD9gSD.js"; import { a as gatewayInstallErrorHint, i as buildGatewayInstallPlan, n as GATEWAY_DAEMON_RUNTIME_OPTIONS, t as DEFAULT_GATEWAY_DAEMON_RUNTIME } from "./daemon-runtime-DMd0mgTK.js"; import { t as resolveChannelDefaultAccountId } from "./helpers-BIc7L8EF.js"; import { n as logConfigUpdated } from "./logging-TXWhN8jG.js"; import { t as WizardCancelledError } from "./prompts-FbZThK8w.js"; import { c as promptDefaultModel, d as warnIfModelConfigLooksOff, f as applyAuthChoice, g as promptAuthChoiceGrouped, r as promptRemoteGatewayConfig, s as applyPrimaryModel, t as setupSkills, u as resolvePreferredProviderForAuthChoice } from "./onboard-skills-s8J5xbUr.js"; import { n as setupChannels } from "./onboard-channels-DKT27PdN.js"; import { l as healthCommand, n as ensureControlUiAssetsBuilt, s as formatHealthChannelLines, t as formatHealthCheckFailure, u as resolveHeartbeatSummaryForAgent } from "./health-format-B3eStY5r.js"; import { C as resolveEffectiveUpdateChannel, S as normalizeUpdateChannel, d as formatUpdateOneLiner, f as getUpdateCheckResult, h as compareSemverStrings, l as ensureCompletionCacheExists, m as checkUpdateStatus, p as resolveUpdateAvailability, s as checkShellCompletionStatus, u as formatUpdateAvailableHint, x as formatUpdateChannelLabel } from "./update-runner-BDdk_K2S.js"; import { i as probeGateway, t as runSecurityAudit } from "./audit-ZY6Dk5Ec.js"; import { t as renderTable } from "./table-CLtGjVsx.js"; import { t as buildWorkspaceSkillStatus } from "./skills-status-CEvVUD3U.js"; import { o as resolveGatewayLogPaths, t as resolveGatewayService } from "./service-BZesBIaG.js"; import { r as isSystemdUserServiceAvailable } from "./systemd-B-3NdMmA.js"; import { o as readLastGatewayErrorLine } from "./service-audit-C-IA4omi.js"; import { t as resolveNodeService } from "./node-service-BAYHx0E7.js"; import { t as collectChannelStatusIssues } from "./channels-status-issues-CrS1r5sr.js"; import { i as redactSecrets, n as formatDuration$1, r as formatGatewayAuthUsed, t as formatAge$1 } from "./format-B7OjpGnt.js"; import { r as installCompletion } from "./completion-cli-BPIeQDFy.js"; import { t as buildWorkspaceHookStatus } from "./hooks-status-DepPyfBb.js"; import { t as runTui } from "./tui-XH6_v0qC.js"; import os from "node:os"; import path from "node:path"; import fs from "node:fs"; import fs$1 from "node:fs/promises"; import { execFileSync, spawnSync } from "node:child_process"; import crypto from "node:crypto"; //#region src/cli/ports.ts function parseLsofOutput(output) { const lines = output.split(/\r?\n/).filter(Boolean); const results = []; let current = {}; for (const line of lines) if (line.startsWith("p")) { if (current.pid) results.push(current); current = { pid: Number.parseInt(line.slice(1), 10) }; } else if (line.startsWith("c")) current.command = line.slice(1); if (current.pid) results.push(current); return results; } function listPortListeners(port) { try { return parseLsofOutput(execFileSync(resolveLsofCommandSync(), [ "-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-FpFc" ], { encoding: "utf-8" })); } catch (err) { const status = err.status; if (err.code === "ENOENT") throw new Error("lsof not found; required for --force", { cause: err }); if (status === 1) return []; throw err instanceof Error ? err : new Error(String(err)); } } function forceFreePort(port) { const listeners = listPortListeners(port); for (const proc of listeners) try { process.kill(proc.pid, "SIGTERM"); } catch (err) { throw new Error(`failed to kill pid ${proc.pid}${proc.command ? ` (${proc.command})` : ""}: ${String(err)}`, { cause: err }); } return listeners; } function killPids(listeners, signal) { for (const proc of listeners) try { process.kill(proc.pid, signal); } catch (err) { throw new Error(`failed to kill pid ${proc.pid}${proc.command ? ` (${proc.command})` : ""}: ${String(err)}`, { cause: err }); } } async function forceFreePortAndWait(port, opts = {}) { const timeoutMs = Math.max(opts.timeoutMs ?? 1500, 0); const intervalMs = Math.max(opts.intervalMs ?? 100, 1); const sigtermTimeoutMs = Math.min(Math.max(opts.sigtermTimeoutMs ?? 600, 0), timeoutMs); const killed = forceFreePort(port); if (killed.length === 0) return { killed, waitedMs: 0, escalatedToSigkill: false }; let waitedMs = 0; const triesSigterm = intervalMs > 0 ? Math.ceil(sigtermTimeoutMs / intervalMs) : 0; for (let i = 0; i < triesSigterm; i++) { if (listPortListeners(port).length === 0) return { killed, waitedMs, escalatedToSigkill: false }; await sleep(intervalMs); waitedMs += intervalMs; } if (listPortListeners(port).length === 0) return { killed, waitedMs, escalatedToSigkill: false }; killPids(listPortListeners(port), "SIGKILL"); const remainingBudget = Math.max(timeoutMs - waitedMs, 0); const triesSigkill = intervalMs > 0 ? Math.ceil(remainingBudget / intervalMs) : 0; for (let i = 0; i < triesSigkill; i++) { if (listPortListeners(port).length === 0) return { killed, waitedMs, escalatedToSigkill: true }; await sleep(intervalMs); waitedMs += intervalMs; } const still = listPortListeners(port); if (still.length === 0) return { killed, waitedMs, escalatedToSigkill: true }; throw new Error(`port ${port} still has listeners after --force: ${still.map((p) => p.pid).join(", ")}`); } //#endregion //#region src/agents/identity-file.ts const IDENTITY_PLACEHOLDER_VALUES = new Set([ "pick something you like", "ai? robot? familiar? ghost in the machine? something weirder?", "how do you come across? sharp? warm? chaotic? calm?", "your signature - pick one that feels right", "workspace-relative path, http(s) url, or data uri" ]); function normalizeIdentityValue(value) { let normalized = value.trim(); normalized = normalized.replace(/^[*_]+|[*_]+$/g, "").trim(); if (normalized.startsWith("(") && normalized.endsWith(")")) normalized = normalized.slice(1, -1).trim(); normalized = normalized.replace(/[\u2013\u2014]/g, "-"); normalized = normalized.replace(/\s+/g, " ").toLowerCase(); return normalized; } function isIdentityPlaceholder(value) { const normalized = normalizeIdentityValue(value); return IDENTITY_PLACEHOLDER_VALUES.has(normalized); } function parseIdentityMarkdown(content) { const identity = {}; const lines = content.split(/\r?\n/); for (const line of lines) { const cleaned = line.trim().replace(/^\s*-\s*/, ""); const colonIndex = cleaned.indexOf(":"); if (colonIndex === -1) continue; const label = cleaned.slice(0, colonIndex).replace(/[*_]/g, "").trim().toLowerCase(); const value = cleaned.slice(colonIndex + 1).replace(/^[*_]+|[*_]+$/g, "").trim(); if (!value) continue; if (isIdentityPlaceholder(value)) continue; if (label === "name") identity.name = value; if (label === "emoji") identity.emoji = value; if (label === "creature") identity.creature = value; if (label === "vibe") identity.vibe = value; if (label === "theme") identity.theme = value; if (label === "avatar") identity.avatar = value; } return identity; } function identityHasValues(identity) { return Boolean(identity.name || identity.emoji || identity.theme || identity.creature || identity.vibe || identity.avatar); } function loadIdentityFromFile(identityPath) { try { const parsed = parseIdentityMarkdown(fs.readFileSync(identityPath, "utf-8")); if (!identityHasValues(parsed)) return null; return parsed; } catch { return null; } } function loadAgentIdentityFromWorkspace(workspace) { return loadIdentityFromFile(path.join(workspace, DEFAULT_IDENTITY_FILENAME)); } //#endregion //#region src/commands/agents.config.ts function listAgentEntries(cfg) { const list = cfg.agents?.list; if (!Array.isArray(list)) return []; return list.filter((entry) => Boolean(entry && typeof entry === "object")); } function findAgentEntryIndex(list, agentId) { const id = normalizeAgentId(agentId); return list.findIndex((entry) => normalizeAgentId(entry.id) === id); } function resolveAgentName(cfg, agentId) { return listAgentEntries(cfg).find((agent) => normalizeAgentId(agent.id) === normalizeAgentId(agentId))?.name?.trim() || void 0; } function resolveAgentModel(cfg, agentId) { const entry = listAgentEntries(cfg).find((agent) => normalizeAgentId(agent.id) === normalizeAgentId(agentId)); if (entry?.model) { if (typeof entry.model === "string" && entry.model.trim()) return entry.model.trim(); if (typeof entry.model === "object") { const primary = entry.model.primary?.trim(); if (primary) return primary; } } const raw = cfg.agents?.defaults?.model; if (typeof raw === "string") return raw; return raw?.primary?.trim() || void 0; } function loadAgentIdentity(workspace) { const parsed = loadAgentIdentityFromWorkspace(workspace); if (!parsed) return null; return identityHasValues(parsed) ? parsed : null; } function buildAgentSummaries(cfg) { const defaultAgentId = normalizeAgentId(resolveDefaultAgentId(cfg)); const configuredAgents = listAgentEntries(cfg); const orderedIds = configuredAgents.length > 0 ? configuredAgents.map((agent) => normalizeAgentId(agent.id)) : [defaultAgentId]; const bindingCounts = /* @__PURE__ */ new Map(); for (const binding of cfg.bindings ?? []) { const agentId = normalizeAgentId(binding.agentId); bindingCounts.set(agentId, (bindingCounts.get(agentId) ?? 0) + 1); } return orderedIds.filter((id, index) => orderedIds.indexOf(id) === index).map((id) => { const workspace = resolveAgentWorkspaceDir(cfg, id); const identity = loadAgentIdentity(workspace); const configIdentity = configuredAgents.find((agent) => normalizeAgentId(agent.id) === id)?.identity; const identityName = identity?.name ?? configIdentity?.name?.trim(); const identityEmoji = identity?.emoji ?? configIdentity?.emoji?.trim(); const identitySource = identity ? "identity" : configIdentity && (identityName || identityEmoji) ? "config" : void 0; return { id, name: resolveAgentName(cfg, id), identityName, identityEmoji, identitySource, workspace, agentDir: resolveAgentDir(cfg, id), model: resolveAgentModel(cfg, id), bindings: bindingCounts.get(id) ?? 0, isDefault: id === defaultAgentId }; }); } function applyAgentConfig(cfg, params) { const agentId = normalizeAgentId(params.agentId); const name = params.name?.trim(); const list = listAgentEntries(cfg); const index = findAgentEntryIndex(list, agentId); const nextEntry = { ...index >= 0 ? list[index] : { id: agentId }, ...name ? { name } : {}, ...params.workspace ? { workspace: params.workspace } : {}, ...params.agentDir ? { agentDir: params.agentDir } : {}, ...params.model ? { model: params.model } : {} }; const nextList = [...list]; if (index >= 0) nextList[index] = nextEntry; else { if (nextList.length === 0 && agentId !== normalizeAgentId(resolveDefaultAgentId(cfg))) nextList.push({ id: resolveDefaultAgentId(cfg) }); nextList.push(nextEntry); } return { ...cfg, agents: { ...cfg.agents, list: nextList } }; } function pruneAgentConfig(cfg, agentId) { const id = normalizeAgentId(agentId); const nextAgentsList = listAgentEntries(cfg).filter((entry) => normalizeAgentId(entry.id) !== id); const nextAgents = nextAgentsList.length > 0 ? nextAgentsList : void 0; const bindings = cfg.bindings ?? []; const filteredBindings = bindings.filter((binding) => normalizeAgentId(binding.agentId) !== id); const allow = cfg.tools?.agentToAgent?.allow ?? []; const filteredAllow = allow.filter((entry) => entry !== id); const nextAgentsConfig = cfg.agents ? { ...cfg.agents, list: nextAgents } : nextAgents ? { list: nextAgents } : void 0; const nextTools = cfg.tools?.agentToAgent ? { ...cfg.tools, agentToAgent: { ...cfg.tools.agentToAgent, allow: filteredAllow.length > 0 ? filteredAllow : void 0 } } : cfg.tools; return { config: { ...cfg, agents: nextAgentsConfig, bindings: filteredBindings.length > 0 ? filteredBindings : void 0, tools: nextTools }, removedBindings: bindings.length - filteredBindings.length, removedAllow: allow.length - filteredAllow.length }; } //#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 sha256HexPrefix(value, len = 8) { return crypto.createHash("sha256").update(value).digest("hex").slice(0, len); } function formatTokenHint(token, opts) { const t = token.trim(); if (!t) return "empty"; if (!opts.showSecrets) return `sha256:${sha256HexPrefix(t)} · 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 buildAccountSnapshot = (params) => { const described = params.plugin.config.describeAccount?.(params.account, params.cfg); return { enabled: params.enabled, configured: params.configured, ...described, accountId: params.accountId }; }; const formatAllowFrom = (params) => { if (params.plugin.config.formatAllowFrom) return params.plugin.config.formatAllowFrom({ cfg: params.cfg, accountId: params.accountId, allowFrom: params.allowFrom }); return params.allowFrom.map((entry) => String(entry).trim()).filter(Boolean); }; 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 = formatAllowFrom({ 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 hasBotOrAppTokenFields = accountRecs.some((r) => "botToken" in r || "appToken" in r); const hasTokenField = accountRecs.some((r) => "token" in r); if (!hasBotOrAppTokenFields && !hasTokenField) return { state: null, detail: null }; if (hasBotOrAppTokenFields) { 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}` }; } 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 = buildAccountSnapshot({ 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 ${formatAge$1(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) ]; } function pickGatewaySelfPresence$1(presence) { if (!Array.isArray(presence)) return null; const self = presence.find((e) => e.mode === "gateway" && e.reason === "self") ?? 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/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)} · ${formatAge$1(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 ? formatAge$1(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 channelInfo = resolveEffectiveUpdateChannel({ configChannel: normalizeUpdateChannel(cfg.update?.channel), installKind: update.installKind, git: update.git ? { tag: update.git.tag, branch: update.git.branch } : void 0 }); const channelLabel = formatUpdateChannelLabel({ channel: channelInfo.channel, source: channelInfo.source, gitTag: update.git?.tag ?? null, gitBranch: update.git?.branch ?? null }); const gitLabel = update.installKind === "git" ? (() => { const shortSha = update.git?.sha ? update.git.sha.slice(0, 8) : null; const branch = update.git?.branch && update.git.branch !== "HEAD" ? update.git.branch : null; const tag = update.git?.tag ?? null; return [ branch ?? (tag ? "detached" : "git"), tag ? `tag ${tag}` : null, shortSha ? `@ ${shortSha}` : null ].filter(Boolean).join(" · "); })() : null; 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 resolveProbeAuth = (mode) => { const authToken = cfg.gateway?.auth?.token; const authPassword = cfg.gateway?.auth?.password; const remote = cfg.gateway?.remote; return { token: mode === "remote" ? typeof remote?.token === "string" && remote.token.trim() ? remote.token.trim() : void 0 : process.env.OPENCLAW_GATEWAY_TOKEN?.trim() || (typeof authToken === "string" && authToken.trim() ? authToken.trim() : void 0), password: process.env.OPENCLAW_GATEWAY_PASSWORD?.trim() || (mode === "remote" ? typeof remote?.password === "string" && remote.password.trim() ? remote.password.trim() : void 0 : typeof authPassword === "string" && authPassword.trim() ? authPassword.trim() : void 0) }; }; const localFallbackAuth = resolveProbeAuth("local"); const remoteAuth = resolveProbeAuth("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$1(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?.timeout