@gguf/claw
Version:
WhatsApp gateway CLI (Baileys web) with Pi RPC agent
1,304 lines (1,293 loc) • 122 kB
JavaScript
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