| import type { ProgressReporter } from "../../cli/progress.js"; |
| import { getTerminalTableWidth, renderTable } from "../../terminal/table.js"; |
| import { isRich, theme } from "../../terminal/theme.js"; |
| import { groupChannelIssuesByChannel } from "./channel-issues.js"; |
| import { appendStatusAllDiagnosis } from "./diagnosis.js"; |
| import { formatTimeAgo } from "./format.js"; |
|
|
| type OverviewRow = { Item: string; Value: string }; |
|
|
| type ChannelsTable = { |
| rows: Array<{ |
| id: string; |
| label: string; |
| enabled: boolean; |
| state: "ok" | "warn" | "off" | "setup"; |
| detail: string; |
| }>; |
| details: Array<{ |
| title: string; |
| columns: string[]; |
| rows: Array<Record<string, string>>; |
| }>; |
| }; |
|
|
| type ChannelIssueLike = { |
| channel: string; |
| message: string; |
| }; |
|
|
| type AgentStatusLike = { |
| agents: Array<{ |
| id: string; |
| name?: string | null; |
| bootstrapPending?: boolean | null; |
| sessionsCount: number; |
| lastActiveAgeMs?: number | null; |
| sessionsPath: string; |
| }>; |
| }; |
|
|
| export async function buildStatusAllReportLines(params: { |
| progress: ProgressReporter; |
| overviewRows: OverviewRow[]; |
| channels: ChannelsTable; |
| channelIssues: ChannelIssueLike[]; |
| agentStatus: AgentStatusLike; |
| connectionDetailsForReport: string; |
| diagnosis: Omit< |
| Parameters<typeof appendStatusAllDiagnosis>[0], |
| "lines" | "progress" | "muted" | "ok" | "warn" | "fail" | "connectionDetailsForReport" |
| >; |
| }) { |
| const rich = isRich(); |
| const heading = (text: string) => (rich ? theme.heading(text) : text); |
| const ok = (text: string) => (rich ? theme.success(text) : text); |
| const warn = (text: string) => (rich ? theme.warn(text) : text); |
| const fail = (text: string) => (rich ? theme.error(text) : text); |
| const muted = (text: string) => (rich ? theme.muted(text) : text); |
|
|
| const tableWidth = getTerminalTableWidth(); |
|
|
| 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 = groupChannelIssuesByChannel(params.channelIssues); |
| const channelRowsWithIssues = 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 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: channelRowsWithIssues, |
| }); |
|
|
| const agentRows = params.agentStatus.agents.map((a) => ({ |
| Agent: a.name?.trim() ? `${a.id} (${a.name.trim()})` : a.id, |
| BootstrapFile: |
| a.bootstrapPending === true |
| ? warn("PRESENT") |
| : a.bootstrapPending === false |
| ? ok("ABSENT") |
| : "unknown", |
| Sessions: String(a.sessionsCount), |
| Active: a.lastActiveAgeMs != null ? formatTimeAgo(a.lastActiveAgeMs) : "unknown", |
| Store: a.sessionsPath, |
| })); |
|
|
| const agentsTable = renderTable({ |
| width: tableWidth, |
| columns: [ |
| { key: "Agent", header: "Agent", minWidth: 12 }, |
| { key: "BootstrapFile", header: "Bootstrap file", minWidth: 14 }, |
| { key: "Sessions", header: "Sessions", align: "right", minWidth: 8 }, |
| { key: "Active", header: "Active", minWidth: 10 }, |
| { key: "Store", header: "Store", flex: true, minWidth: 34 }, |
| ], |
| rows: agentRows, |
| }); |
|
|
| const lines: string[] = []; |
| 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; |
| } |
|
|