mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
refactor: share status overview and json helpers
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
import { canExecRequestNode } from "../../agents/exec-defaults.js";
|
||||
import { buildWorkspaceSkillStatus } from "../../agents/skills-status.js";
|
||||
import { formatCliCommand } from "../../cli/command-format.js";
|
||||
import { readConfigFileSnapshot, resolveGatewayPort } from "../../config/config.js";
|
||||
import { readLastGatewayErrorLine } from "../../daemon/diagnostics.js";
|
||||
import { inspectPortUsage } from "../../infra/ports.js";
|
||||
import { readRestartSentinel } from "../../infra/restart-sentinel.js";
|
||||
import { getRemoteSkillEligibility } from "../../infra/skills-remote.js";
|
||||
import { buildPluginCompatibilityNotices } from "../../plugins/status.js";
|
||||
import { VERSION } from "../../version.js";
|
||||
import { buildStatusAllAgentsValue, buildStatusSecretsValue } from "../status-overview-values.ts";
|
||||
import { buildStatusAllOverviewRows } from "../status-overview-rows.ts";
|
||||
import {
|
||||
buildStatusOverviewSurfaceFromOverview,
|
||||
type StatusOverviewSurface,
|
||||
} from "../status-overview-surface.ts";
|
||||
import {
|
||||
resolveStatusGatewayHealthSafe,
|
||||
type resolveStatusServiceSummaries,
|
||||
@@ -16,7 +18,6 @@ import {
|
||||
import { resolveStatusAllConnectionDetails } from "../status.gateway-connection.ts";
|
||||
import type { NodeOnlyGatewayInfo } from "../status.node-mode.js";
|
||||
import type { StatusScanOverviewResult } from "../status.scan-overview.ts";
|
||||
import { buildStatusOverviewSurfaceRows } from "./format.js";
|
||||
|
||||
type StatusServiceSummaries = Awaited<ReturnType<typeof resolveStatusServiceSummaries>>;
|
||||
type StatusGatewayServiceSummary = StatusServiceSummaries[0];
|
||||
@@ -166,46 +167,19 @@ export async function buildStatusAllReportData(params: {
|
||||
timeoutMs: params.timeoutMs,
|
||||
});
|
||||
|
||||
const overviewRows = buildStatusOverviewSurfaceRows({
|
||||
cfg: params.overview.cfg,
|
||||
update: params.overview.update,
|
||||
tailscaleMode: params.overview.tailscaleMode,
|
||||
tailscaleDns: params.overview.tailscaleDns,
|
||||
tailscaleHttpsUrl: params.overview.tailscaleHttpsUrl,
|
||||
tailscaleBackendState: diagnosis.tailscale.backendState,
|
||||
includeBackendStateWhenOff: true,
|
||||
includeBackendStateWhenOn: true,
|
||||
includeDnsNameWhenOff: true,
|
||||
gatewayMode: gatewaySnapshot.gatewayMode,
|
||||
remoteUrlMissing: gatewaySnapshot.remoteUrlMissing,
|
||||
gatewayConnection: gatewaySnapshot.gatewayConnection,
|
||||
gatewayReachable: gatewaySnapshot.gatewayReachable,
|
||||
gatewayProbe: gatewaySnapshot.gatewayProbe,
|
||||
gatewayProbeAuth: gatewaySnapshot.gatewayProbeAuth,
|
||||
gatewayProbeAuthWarning: gatewaySnapshot.gatewayProbeAuthWarning,
|
||||
gatewaySelf: gatewaySnapshot.gatewaySelf,
|
||||
const overviewSurface: StatusOverviewSurface = buildStatusOverviewSurfaceFromOverview({
|
||||
overview: params.overview,
|
||||
gatewayService: params.daemon,
|
||||
nodeService: params.nodeService,
|
||||
nodeOnlyGateway: params.nodeOnlyGateway,
|
||||
prefixRows: [
|
||||
{ Item: "Version", Value: VERSION },
|
||||
{ Item: "OS", Value: params.overview.osSummary.label },
|
||||
{ Item: "Node", Value: process.versions.node },
|
||||
{ Item: "Config", Value: configPath },
|
||||
],
|
||||
middleRows: [
|
||||
{ Item: "Security", Value: `Run: ${formatCliCommand("openclaw security audit --deep")}` },
|
||||
],
|
||||
agentsValue: buildStatusAllAgentsValue({
|
||||
agentStatus: params.overview.agentStatus,
|
||||
}),
|
||||
suffixRows: [
|
||||
{
|
||||
Item: "Secrets",
|
||||
Value: buildStatusSecretsValue(params.overview.secretDiagnostics.length),
|
||||
},
|
||||
],
|
||||
gatewaySelfFallbackValue: "unknown",
|
||||
});
|
||||
const overviewRows = buildStatusAllOverviewRows({
|
||||
surface: overviewSurface,
|
||||
osLabel: params.overview.osSummary.label,
|
||||
configPath,
|
||||
secretDiagnosticsCount: params.overview.secretDiagnostics.length,
|
||||
agentStatus: params.overview.agentStatus,
|
||||
tailscaleBackendState: diagnosis.tailscale.backendState,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
77
src/commands/status-json-command.test.ts
Normal file
77
src/commands/status-json-command.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { runStatusJsonCommand } from "./status-json-command.ts";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
writeRuntimeJson: vi.fn(),
|
||||
resolveStatusJsonOutput: vi.fn(async (input) => ({ built: true, input })),
|
||||
}));
|
||||
|
||||
vi.mock("../runtime.js", () => ({
|
||||
writeRuntimeJson: mocks.writeRuntimeJson,
|
||||
}));
|
||||
|
||||
vi.mock("./status-json-runtime.ts", () => ({
|
||||
resolveStatusJsonOutput: mocks.resolveStatusJsonOutput,
|
||||
}));
|
||||
|
||||
describe("runStatusJsonCommand", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("shares the fast-json scan and output flow", async () => {
|
||||
const runtime = {
|
||||
log: vi.fn(),
|
||||
error: vi.fn(),
|
||||
exit: vi.fn(),
|
||||
} as never;
|
||||
const scan = {
|
||||
cfg: { gateway: {} },
|
||||
sourceConfig: { gateway: {} },
|
||||
summary: { ok: true },
|
||||
update: { installKind: "package", packageManager: "npm" },
|
||||
osSummary: { platform: "linux" },
|
||||
memory: null,
|
||||
memoryPlugin: null,
|
||||
gatewayMode: "local" as const,
|
||||
gatewayConnection: { url: "ws://127.0.0.1:18789", urlSource: "config" },
|
||||
remoteUrlMissing: false,
|
||||
gatewayReachable: true,
|
||||
gatewayProbe: null,
|
||||
gatewayProbeAuth: { token: "tok" },
|
||||
gatewaySelf: null,
|
||||
gatewayProbeAuthWarning: null,
|
||||
agentStatus: [],
|
||||
secretDiagnostics: [],
|
||||
};
|
||||
const scanStatusJsonFast = vi.fn(async () => scan);
|
||||
|
||||
await runStatusJsonCommand({
|
||||
opts: { deep: true, usage: true, timeoutMs: 1234, all: true },
|
||||
runtime,
|
||||
scanStatusJsonFast,
|
||||
includeSecurityAudit: true,
|
||||
includePluginCompatibility: true,
|
||||
suppressHealthErrors: true,
|
||||
});
|
||||
|
||||
expect(scanStatusJsonFast).toHaveBeenCalledWith({ timeoutMs: 1234, all: true }, runtime);
|
||||
expect(mocks.resolveStatusJsonOutput).toHaveBeenCalledWith({
|
||||
scan,
|
||||
opts: { deep: true, usage: true, timeoutMs: 1234, all: true },
|
||||
includeSecurityAudit: true,
|
||||
includePluginCompatibility: true,
|
||||
suppressHealthErrors: true,
|
||||
});
|
||||
expect(mocks.writeRuntimeJson).toHaveBeenCalledWith(runtime, {
|
||||
built: true,
|
||||
input: {
|
||||
scan,
|
||||
opts: { deep: true, usage: true, timeoutMs: 1234, all: true },
|
||||
includeSecurityAudit: true,
|
||||
includePluginCompatibility: true,
|
||||
suppressHealthErrors: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
36
src/commands/status-json-command.ts
Normal file
36
src/commands/status-json-command.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { resolveStatusJsonOutput } from "./status-json-runtime.ts";
|
||||
|
||||
export type StatusJsonCommandOptions = {
|
||||
deep?: boolean;
|
||||
usage?: boolean;
|
||||
timeoutMs?: number;
|
||||
all?: boolean;
|
||||
};
|
||||
|
||||
export async function runStatusJsonCommand(params: {
|
||||
opts: StatusJsonCommandOptions;
|
||||
runtime: RuntimeEnv;
|
||||
includeSecurityAudit: boolean;
|
||||
includePluginCompatibility?: boolean;
|
||||
suppressHealthErrors?: boolean;
|
||||
scanStatusJsonFast: (
|
||||
opts: { timeoutMs?: number; all?: boolean },
|
||||
runtime: RuntimeEnv,
|
||||
) => Promise<Parameters<typeof resolveStatusJsonOutput>[0]["scan"]>;
|
||||
}) {
|
||||
const scan = await params.scanStatusJsonFast(
|
||||
{ timeoutMs: params.opts.timeoutMs, all: params.opts.all },
|
||||
params.runtime,
|
||||
);
|
||||
writeRuntimeJson(
|
||||
params.runtime,
|
||||
await resolveStatusJsonOutput({
|
||||
scan,
|
||||
opts: params.opts,
|
||||
includeSecurityAudit: params.includeSecurityAudit,
|
||||
includePluginCompatibility: params.includePluginCompatibility,
|
||||
suppressHealthErrors: params.suppressHealthErrors,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -59,25 +59,29 @@ describe("status-json-payload", () => {
|
||||
expect(
|
||||
buildStatusJsonPayload({
|
||||
summary: { ok: true },
|
||||
updateConfigChannel: "stable",
|
||||
update: {
|
||||
root: "/tmp/openclaw",
|
||||
installKind: "package",
|
||||
packageManager: "npm",
|
||||
registry: { latestVersion: "1.2.3" },
|
||||
surface: {
|
||||
cfg: { update: { channel: "stable" }, gateway: {} },
|
||||
update: {
|
||||
root: "/tmp/openclaw",
|
||||
installKind: "package",
|
||||
packageManager: "npm",
|
||||
registry: { latestVersion: "1.2.3" },
|
||||
} as never,
|
||||
tailscaleMode: "serve",
|
||||
gatewayMode: "remote",
|
||||
remoteUrlMissing: false,
|
||||
gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" },
|
||||
gatewayReachable: true,
|
||||
gatewayProbe: { connectLatencyMs: 42, error: null },
|
||||
gatewayProbeAuth: { token: "tok" },
|
||||
gatewaySelf: { host: "gateway" },
|
||||
gatewayProbeAuthWarning: "warn",
|
||||
gatewayService: { label: "LaunchAgent" },
|
||||
nodeService: { label: "node" },
|
||||
},
|
||||
osSummary: { platform: "linux" },
|
||||
memory: null,
|
||||
memoryPlugin: { enabled: true },
|
||||
gatewayMode: "remote",
|
||||
gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" },
|
||||
remoteUrlMissing: false,
|
||||
gatewayReachable: true,
|
||||
gatewayProbe: { connectLatencyMs: 42, error: null },
|
||||
gatewaySelf: { host: "gateway" },
|
||||
gatewayProbeAuthWarning: "warn",
|
||||
gatewayService: { label: "LaunchAgent" },
|
||||
nodeService: { label: "node" },
|
||||
agents: [{ id: "main" }],
|
||||
secretDiagnostics: ["diag"],
|
||||
securityAudit: { summary: { critical: 1 } },
|
||||
@@ -143,24 +147,28 @@ describe("status-json-payload", () => {
|
||||
expect(
|
||||
buildStatusJsonPayload({
|
||||
summary: { ok: true },
|
||||
updateConfigChannel: null,
|
||||
update: {
|
||||
root: "/tmp/openclaw",
|
||||
installKind: "package",
|
||||
packageManager: "npm",
|
||||
surface: {
|
||||
cfg: { gateway: {} },
|
||||
update: {
|
||||
root: "/tmp/openclaw",
|
||||
installKind: "package",
|
||||
packageManager: "npm",
|
||||
} as never,
|
||||
tailscaleMode: "off",
|
||||
gatewayMode: "local",
|
||||
remoteUrlMissing: false,
|
||||
gatewayConnection: { url: "ws://127.0.0.1:18789" },
|
||||
gatewayReachable: false,
|
||||
gatewayProbe: null,
|
||||
gatewayProbeAuth: null,
|
||||
gatewaySelf: null,
|
||||
gatewayProbeAuthWarning: null,
|
||||
gatewayService: { label: "LaunchAgent", installed: false, loadedText: "not installed" },
|
||||
nodeService: { label: "node", installed: false, loadedText: "not installed" },
|
||||
},
|
||||
osSummary: { platform: "linux" },
|
||||
memory: null,
|
||||
memoryPlugin: null,
|
||||
gatewayMode: "local",
|
||||
gatewayConnection: { url: "ws://127.0.0.1:18789" },
|
||||
remoteUrlMissing: false,
|
||||
gatewayReachable: false,
|
||||
gatewayProbe: null,
|
||||
gatewaySelf: null,
|
||||
gatewayProbeAuthWarning: null,
|
||||
gatewayService: null,
|
||||
nodeService: null,
|
||||
agents: [],
|
||||
secretDiagnostics: [],
|
||||
}),
|
||||
|
||||
@@ -1,47 +1,17 @@
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import type { UpdateCheckResult } from "../infra/update-check.js";
|
||||
import { resolveStatusUpdateChannelInfo } from "./status-all/format.js";
|
||||
import {
|
||||
buildGatewayStatusJsonPayload,
|
||||
resolveStatusUpdateChannelInfo,
|
||||
} from "./status-all/format.js";
|
||||
buildStatusGatewayJsonPayloadFromSurface,
|
||||
type StatusOverviewSurface,
|
||||
} from "./status-overview-surface.ts";
|
||||
|
||||
export { resolveStatusUpdateChannelInfo } from "./status-all/format.js";
|
||||
|
||||
type UpdateConfigChannel = NonNullable<OpenClawConfig["update"]>["channel"];
|
||||
|
||||
export function buildStatusJsonPayload(params: {
|
||||
summary: Record<string, unknown>;
|
||||
updateConfigChannel?: UpdateConfigChannel | null;
|
||||
update: UpdateCheckResult;
|
||||
surface: StatusOverviewSurface;
|
||||
osSummary: unknown;
|
||||
memory: unknown;
|
||||
memoryPlugin: unknown;
|
||||
gatewayMode: "local" | "remote";
|
||||
gatewayConnection: {
|
||||
url: string;
|
||||
urlSource?: string;
|
||||
};
|
||||
remoteUrlMissing: boolean;
|
||||
gatewayReachable: boolean;
|
||||
gatewayProbe:
|
||||
| {
|
||||
connectLatencyMs?: number | null;
|
||||
error?: string | null;
|
||||
}
|
||||
| null
|
||||
| undefined;
|
||||
gatewaySelf:
|
||||
| {
|
||||
host?: string | null;
|
||||
ip?: string | null;
|
||||
version?: string | null;
|
||||
platform?: string | null;
|
||||
}
|
||||
| null
|
||||
| undefined;
|
||||
gatewayProbeAuthWarning?: string | null;
|
||||
gatewayService: unknown;
|
||||
nodeService: unknown;
|
||||
agents: unknown;
|
||||
secretDiagnostics: string[];
|
||||
securityAudit?: unknown;
|
||||
@@ -51,28 +21,20 @@ export function buildStatusJsonPayload(params: {
|
||||
pluginCompatibility?: Array<Record<string, unknown>> | null | undefined;
|
||||
}) {
|
||||
const channelInfo = resolveStatusUpdateChannelInfo({
|
||||
updateConfigChannel: params.updateConfigChannel ?? undefined,
|
||||
update: params.update,
|
||||
updateConfigChannel: params.surface.cfg.update?.channel ?? undefined,
|
||||
update: params.surface.update,
|
||||
});
|
||||
return {
|
||||
...params.summary,
|
||||
os: params.osSummary,
|
||||
update: params.update,
|
||||
update: params.surface.update,
|
||||
updateChannel: channelInfo.channel,
|
||||
updateChannelSource: channelInfo.source,
|
||||
memory: params.memory,
|
||||
memoryPlugin: params.memoryPlugin,
|
||||
gateway: buildGatewayStatusJsonPayload({
|
||||
gatewayMode: params.gatewayMode,
|
||||
gatewayConnection: params.gatewayConnection,
|
||||
remoteUrlMissing: params.remoteUrlMissing,
|
||||
gatewayReachable: params.gatewayReachable,
|
||||
gatewayProbe: params.gatewayProbe,
|
||||
gatewaySelf: params.gatewaySelf,
|
||||
gatewayProbeAuthWarning: params.gatewayProbeAuthWarning,
|
||||
}),
|
||||
gatewayService: params.gatewayService,
|
||||
nodeService: params.nodeService,
|
||||
gateway: buildStatusGatewayJsonPayloadFromSurface({ surface: params.surface }),
|
||||
gatewayService: params.surface.gatewayService,
|
||||
nodeService: params.surface.nodeService,
|
||||
agents: params.agents,
|
||||
secretDiagnostics: params.secretDiagnostics,
|
||||
...(params.securityAudit ? { securityAudit: params.securityAudit } : {}),
|
||||
|
||||
@@ -32,6 +32,7 @@ function createScan() {
|
||||
remoteUrlMissing: false,
|
||||
gatewayReachable: true,
|
||||
gatewayProbe: { connectLatencyMs: 42, error: null },
|
||||
gatewayProbeAuth: { token: "tok" },
|
||||
gatewaySelf: { host: "gateway" },
|
||||
gatewayProbeAuthWarning: null,
|
||||
agentStatus: { agents: [{ id: "main" }], defaultId: "main" },
|
||||
@@ -80,6 +81,12 @@ describe("status-json-runtime", () => {
|
||||
});
|
||||
expect(mocks.buildStatusJsonPayload).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
surface: expect.objectContaining({
|
||||
gatewayConnection: { url: "ws://127.0.0.1:18789", urlSource: "config" },
|
||||
gatewayProbeAuth: { token: "tok" },
|
||||
gatewayService: { label: "LaunchAgent" },
|
||||
nodeService: { label: "node" },
|
||||
}),
|
||||
securityAudit: { summary: { critical: 1 } },
|
||||
usage: { providers: [] },
|
||||
health: { ok: true },
|
||||
@@ -126,6 +133,9 @@ describe("status-json-runtime", () => {
|
||||
});
|
||||
expect(mocks.buildStatusJsonPayload).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
surface: expect.objectContaining({
|
||||
gatewayProbeAuth: { token: "tok" },
|
||||
}),
|
||||
securityAudit: undefined,
|
||||
usage: undefined,
|
||||
health: undefined,
|
||||
@@ -154,6 +164,9 @@ describe("status-json-runtime", () => {
|
||||
|
||||
expect(mocks.buildStatusJsonPayload).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
surface: expect.objectContaining({
|
||||
gatewayProbeAuth: { token: "tok" },
|
||||
}),
|
||||
health: undefined,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import type { UpdateCheckResult } from "../infra/update-check.js";
|
||||
import { buildStatusJsonPayload } from "./status-json-payload.ts";
|
||||
import { buildStatusOverviewSurfaceFromScan } from "./status-overview-surface.ts";
|
||||
import { resolveStatusRuntimeSnapshot } from "./status-runtime-shared.ts";
|
||||
|
||||
type StatusJsonScanLike = {
|
||||
@@ -25,6 +26,13 @@ type StatusJsonScanLike = {
|
||||
}
|
||||
| null
|
||||
| undefined;
|
||||
gatewayProbeAuth:
|
||||
| {
|
||||
token?: string;
|
||||
password?: string;
|
||||
}
|
||||
| null
|
||||
| undefined;
|
||||
gatewaySelf:
|
||||
| {
|
||||
host?: string | null;
|
||||
@@ -66,20 +74,14 @@ export async function resolveStatusJsonOutput(params: {
|
||||
|
||||
return buildStatusJsonPayload({
|
||||
summary: scan.summary,
|
||||
updateConfigChannel: scan.cfg.update?.channel,
|
||||
update: scan.update,
|
||||
surface: buildStatusOverviewSurfaceFromScan({
|
||||
scan,
|
||||
gatewayService,
|
||||
nodeService,
|
||||
}),
|
||||
osSummary: scan.osSummary,
|
||||
memory: scan.memory,
|
||||
memoryPlugin: scan.memoryPlugin,
|
||||
gatewayMode: scan.gatewayMode,
|
||||
gatewayConnection: scan.gatewayConnection,
|
||||
remoteUrlMissing: scan.remoteUrlMissing,
|
||||
gatewayReachable: scan.gatewayReachable,
|
||||
gatewayProbe: scan.gatewayProbe,
|
||||
gatewaySelf: scan.gatewaySelf,
|
||||
gatewayProbeAuthWarning: scan.gatewayProbeAuthWarning,
|
||||
gatewayService,
|
||||
nodeService,
|
||||
agents: scan.agentStatus,
|
||||
secretDiagnostics: scan.secretDiagnostics,
|
||||
securityAudit,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { resolveStatusJsonOutput } from "./status-json-runtime.ts";
|
||||
import { type RuntimeEnv } from "../runtime.js";
|
||||
import { runStatusJsonCommand } from "./status-json-command.ts";
|
||||
import { scanStatusJsonFast } from "./status.scan.fast-json.js";
|
||||
|
||||
export async function statusJsonCommand(
|
||||
@@ -11,14 +11,11 @@ export async function statusJsonCommand(
|
||||
},
|
||||
runtime: RuntimeEnv,
|
||||
) {
|
||||
const scan = await scanStatusJsonFast({ timeoutMs: opts.timeoutMs, all: opts.all }, runtime);
|
||||
writeRuntimeJson(
|
||||
await runStatusJsonCommand({
|
||||
opts,
|
||||
runtime,
|
||||
await resolveStatusJsonOutput({
|
||||
scan,
|
||||
opts,
|
||||
includeSecurityAudit: opts.all === true,
|
||||
suppressHealthErrors: true,
|
||||
}),
|
||||
);
|
||||
scanStatusJsonFast,
|
||||
includeSecurityAudit: opts.all === true,
|
||||
suppressHealthErrors: true,
|
||||
});
|
||||
}
|
||||
|
||||
162
src/commands/status-overview-rows.test.ts
Normal file
162
src/commands/status-overview-rows.test.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildStatusAllOverviewRows,
|
||||
buildStatusCommandOverviewRows,
|
||||
} from "./status-overview-rows.ts";
|
||||
|
||||
describe("status-overview-rows", () => {
|
||||
it("builds command overview rows from the shared surface", () => {
|
||||
expect(
|
||||
buildStatusCommandOverviewRows({
|
||||
opts: { deep: true },
|
||||
surface: {
|
||||
cfg: { update: { channel: "stable" }, gateway: { bind: "loopback" } },
|
||||
update: {
|
||||
installKind: "git",
|
||||
git: {
|
||||
branch: "main",
|
||||
tag: "v1.2.3",
|
||||
upstream: "origin/main",
|
||||
behind: 2,
|
||||
ahead: 0,
|
||||
dirty: false,
|
||||
fetchOk: true,
|
||||
},
|
||||
registry: { latestVersion: "2026.4.9" },
|
||||
} as never,
|
||||
tailscaleMode: "serve",
|
||||
tailscaleDns: "box.tail.ts.net",
|
||||
tailscaleHttpsUrl: "https://box.tail.ts.net",
|
||||
gatewayMode: "remote",
|
||||
remoteUrlMissing: false,
|
||||
gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" },
|
||||
gatewayReachable: true,
|
||||
gatewayProbe: { connectLatencyMs: 42, error: null },
|
||||
gatewayProbeAuth: { token: "tok" },
|
||||
gatewayProbeAuthWarning: "warn-text",
|
||||
gatewaySelf: { host: "gateway", version: "1.2.3" },
|
||||
gatewayService: {
|
||||
label: "LaunchAgent",
|
||||
installed: true,
|
||||
managedByOpenClaw: true,
|
||||
loadedText: "loaded",
|
||||
runtimeShort: "running",
|
||||
},
|
||||
nodeService: {
|
||||
label: "node",
|
||||
installed: true,
|
||||
loadedText: "loaded",
|
||||
runtime: { status: "running", pid: 42 },
|
||||
},
|
||||
nodeOnlyGateway: null,
|
||||
},
|
||||
osLabel: "macOS",
|
||||
summary: {
|
||||
tasks: { total: 3, active: 1, failures: 0, byStatus: { queued: 1, running: 1 } },
|
||||
taskAudit: { errors: 1, warnings: 0 },
|
||||
heartbeat: {
|
||||
agents: [{ agentId: "main", enabled: true, everyMs: 60_000, every: "1m" }],
|
||||
},
|
||||
queuedSystemEvents: ["one", "two"],
|
||||
sessions: {
|
||||
count: 2,
|
||||
paths: ["store.json"],
|
||||
defaults: { model: "gpt-5.4", contextTokens: 12_000 },
|
||||
},
|
||||
},
|
||||
health: { durationMs: 42 },
|
||||
lastHeartbeat: {
|
||||
ts: Date.now() - 30_000,
|
||||
status: "ok",
|
||||
channel: "discord",
|
||||
accountId: "acct",
|
||||
},
|
||||
agentStatus: {
|
||||
defaultId: "main",
|
||||
bootstrapPendingCount: 1,
|
||||
totalSessions: 2,
|
||||
agents: [{ id: "main", lastActiveAgeMs: 60_000 }],
|
||||
},
|
||||
memory: { files: 1, chunks: 2, vector: {}, fts: {}, cache: {} },
|
||||
memoryPlugin: { enabled: true, slot: "memory" },
|
||||
pluginCompatibility: [{ pluginId: "a", severity: "warn", message: "legacy" }],
|
||||
ok: (value) => `ok(${value})`,
|
||||
warn: (value) => `warn(${value})`,
|
||||
muted: (value) => `muted(${value})`,
|
||||
formatTimeAgo: (value) => `${value}ms`,
|
||||
formatKTokens: (value) => `${Math.round(value / 1000)}k`,
|
||||
resolveMemoryVectorState: () => ({ state: "ready", tone: "ok" }),
|
||||
resolveMemoryFtsState: () => ({ state: "ready", tone: "warn" }),
|
||||
resolveMemoryCacheSummary: () => ({ text: "cache warm", tone: "muted" }),
|
||||
updateValue: "available · custom update",
|
||||
}),
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ Item: "OS", Value: `macOS · node ${process.versions.node}` },
|
||||
{
|
||||
Item: "Memory",
|
||||
Value:
|
||||
"1 files · 2 chunks · plugin memory · ok(vector ready) · warn(fts ready) · muted(cache warm)",
|
||||
},
|
||||
{ Item: "Plugin compatibility", Value: "warn(1 notice · 1 plugin)" },
|
||||
{ Item: "Sessions", Value: "2 active · default gpt-5.4 (12k ctx) · store.json" },
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("builds status-all overview rows from the shared surface", () => {
|
||||
expect(
|
||||
buildStatusAllOverviewRows({
|
||||
surface: {
|
||||
cfg: { update: { channel: "stable" }, gateway: { bind: "loopback" } },
|
||||
update: {
|
||||
installKind: "git",
|
||||
git: { branch: "main", tag: "v1.2.3", upstream: "origin/main" },
|
||||
} as never,
|
||||
tailscaleMode: "off",
|
||||
tailscaleDns: "box.tail.ts.net",
|
||||
tailscaleHttpsUrl: null,
|
||||
gatewayMode: "remote",
|
||||
remoteUrlMissing: false,
|
||||
gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" },
|
||||
gatewayReachable: true,
|
||||
gatewayProbe: { connectLatencyMs: 42, error: null },
|
||||
gatewayProbeAuth: { token: "tok" },
|
||||
gatewayProbeAuthWarning: "warn-text",
|
||||
gatewaySelf: { host: "gateway", version: "1.2.3" },
|
||||
gatewayService: {
|
||||
label: "LaunchAgent",
|
||||
installed: true,
|
||||
managedByOpenClaw: true,
|
||||
loadedText: "loaded",
|
||||
runtimeShort: "running",
|
||||
},
|
||||
nodeService: {
|
||||
label: "node",
|
||||
installed: true,
|
||||
loadedText: "loaded",
|
||||
runtime: { status: "running", pid: 42 },
|
||||
},
|
||||
nodeOnlyGateway: null,
|
||||
},
|
||||
osLabel: "macOS",
|
||||
configPath: "/tmp/openclaw.json",
|
||||
secretDiagnosticsCount: 2,
|
||||
agentStatus: {
|
||||
bootstrapPendingCount: 1,
|
||||
totalSessions: 2,
|
||||
agents: [{ id: "main", lastActiveAgeMs: 60_000 }],
|
||||
},
|
||||
tailscaleBackendState: "Running",
|
||||
}),
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ Item: "Version", Value: expect.any(String) },
|
||||
{ Item: "OS", Value: "macOS" },
|
||||
{ Item: "Config", Value: "/tmp/openclaw.json" },
|
||||
{ Item: "Security", Value: "Run: openclaw security audit --deep" },
|
||||
{ Item: "Secrets", Value: "2 diagnostics" },
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
208
src/commands/status-overview-rows.ts
Normal file
208
src/commands/status-overview-rows.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import {
|
||||
buildStatusOverviewRowsFromSurface,
|
||||
type StatusOverviewSurface,
|
||||
} from "./status-overview-surface.ts";
|
||||
import {
|
||||
buildStatusAllAgentsValue,
|
||||
buildStatusEventsValue,
|
||||
buildStatusPluginCompatibilityValue,
|
||||
buildStatusProbesValue,
|
||||
buildStatusSecretsValue,
|
||||
buildStatusSessionsOverviewValue,
|
||||
} from "./status-overview-values.ts";
|
||||
import {
|
||||
buildStatusAgentsValue,
|
||||
buildStatusHeartbeatValue,
|
||||
buildStatusLastHeartbeatValue,
|
||||
buildStatusMemoryValue,
|
||||
buildStatusTasksValue,
|
||||
} from "./status.command-sections.js";
|
||||
|
||||
export function buildStatusCommandOverviewRows(params: {
|
||||
opts: {
|
||||
deep?: boolean;
|
||||
};
|
||||
surface: StatusOverviewSurface;
|
||||
osLabel: string;
|
||||
summary: {
|
||||
tasks: {
|
||||
total: number;
|
||||
active: number;
|
||||
failures: number;
|
||||
byStatus: { queued: number; running: number };
|
||||
};
|
||||
taskAudit: {
|
||||
errors: number;
|
||||
warnings: number;
|
||||
};
|
||||
heartbeat: {
|
||||
agents: Array<{
|
||||
agentId: string;
|
||||
enabled?: boolean | null;
|
||||
everyMs?: number | null;
|
||||
every: string;
|
||||
}>;
|
||||
};
|
||||
queuedSystemEvents: string[];
|
||||
sessions: {
|
||||
count: number;
|
||||
paths: string[];
|
||||
defaults: {
|
||||
model?: string | null;
|
||||
contextTokens?: number | null;
|
||||
};
|
||||
};
|
||||
};
|
||||
health?: unknown;
|
||||
lastHeartbeat: unknown;
|
||||
agentStatus: {
|
||||
defaultId?: string | null;
|
||||
bootstrapPendingCount: number;
|
||||
totalSessions: number;
|
||||
agents: Array<{
|
||||
id: string;
|
||||
lastActiveAgeMs?: number | null;
|
||||
}>;
|
||||
};
|
||||
memory: {
|
||||
files: number;
|
||||
chunks: number;
|
||||
dirty?: boolean;
|
||||
sources?: string[];
|
||||
vector?: unknown;
|
||||
fts?: unknown;
|
||||
cache?: unknown;
|
||||
} | null;
|
||||
memoryPlugin: {
|
||||
enabled: boolean;
|
||||
reason?: string | null;
|
||||
slot?: string | null;
|
||||
};
|
||||
pluginCompatibility: Array<{ severity?: "warn" | "info" | null } & Record<string, unknown>>;
|
||||
ok: (value: string) => string;
|
||||
warn: (value: string) => string;
|
||||
muted: (value: string) => string;
|
||||
formatTimeAgo: (ageMs: number) => string;
|
||||
formatKTokens: (value: number) => string;
|
||||
resolveMemoryVectorState: (value: unknown) => { state: string; tone: "ok" | "warn" | "muted" };
|
||||
resolveMemoryFtsState: (value: unknown) => { state: string; tone: "ok" | "warn" | "muted" };
|
||||
resolveMemoryCacheSummary: (value: unknown) => { text: string; tone: "ok" | "warn" | "muted" };
|
||||
updateValue?: string;
|
||||
}) {
|
||||
const agentsValue = buildStatusAgentsValue({
|
||||
agentStatus: params.agentStatus,
|
||||
formatTimeAgo: params.formatTimeAgo,
|
||||
});
|
||||
const eventsValue = buildStatusEventsValue({
|
||||
queuedSystemEvents: params.summary.queuedSystemEvents,
|
||||
});
|
||||
const tasksValue = buildStatusTasksValue({
|
||||
summary: params.summary,
|
||||
warn: params.warn,
|
||||
muted: params.muted,
|
||||
});
|
||||
const probesValue = buildStatusProbesValue({
|
||||
health: params.health,
|
||||
ok: params.ok,
|
||||
muted: params.muted,
|
||||
});
|
||||
const heartbeatValue = buildStatusHeartbeatValue({ summary: params.summary });
|
||||
const lastHeartbeatValue = buildStatusLastHeartbeatValue({
|
||||
deep: params.opts.deep,
|
||||
gatewayReachable: params.surface.gatewayReachable,
|
||||
lastHeartbeat: params.lastHeartbeat as never,
|
||||
warn: params.warn,
|
||||
muted: params.muted,
|
||||
formatTimeAgo: params.formatTimeAgo,
|
||||
});
|
||||
const memoryValue = buildStatusMemoryValue({
|
||||
memory: params.memory,
|
||||
memoryPlugin: params.memoryPlugin,
|
||||
ok: params.ok,
|
||||
warn: params.warn,
|
||||
muted: params.muted,
|
||||
resolveMemoryVectorState: params.resolveMemoryVectorState,
|
||||
resolveMemoryFtsState: params.resolveMemoryFtsState,
|
||||
resolveMemoryCacheSummary: params.resolveMemoryCacheSummary,
|
||||
});
|
||||
const pluginCompatibilityValue = buildStatusPluginCompatibilityValue({
|
||||
notices: params.pluginCompatibility,
|
||||
ok: params.ok,
|
||||
warn: params.warn,
|
||||
});
|
||||
|
||||
return buildStatusOverviewRowsFromSurface({
|
||||
surface: params.surface,
|
||||
decorateOk: params.ok,
|
||||
decorateWarn: params.warn,
|
||||
decorateTailscaleOff: params.muted,
|
||||
decorateTailscaleWarn: params.warn,
|
||||
prefixRows: [{ Item: "OS", Value: `${params.osLabel} · node ${process.versions.node}` }],
|
||||
updateValue: params.updateValue,
|
||||
agentsValue,
|
||||
suffixRows: [
|
||||
{ Item: "Memory", Value: memoryValue },
|
||||
{ Item: "Plugin compatibility", Value: pluginCompatibilityValue },
|
||||
{ Item: "Probes", Value: probesValue },
|
||||
{ Item: "Events", Value: eventsValue },
|
||||
{ Item: "Tasks", Value: tasksValue },
|
||||
{ Item: "Heartbeat", Value: heartbeatValue },
|
||||
...(lastHeartbeatValue ? [{ Item: "Last heartbeat", Value: lastHeartbeatValue }] : []),
|
||||
{
|
||||
Item: "Sessions",
|
||||
Value: buildStatusSessionsOverviewValue({
|
||||
sessions: params.summary.sessions,
|
||||
formatKTokens: params.formatKTokens,
|
||||
}),
|
||||
},
|
||||
],
|
||||
gatewayAuthWarningValue: params.surface.gatewayProbeAuthWarning
|
||||
? params.warn(params.surface.gatewayProbeAuthWarning)
|
||||
: null,
|
||||
});
|
||||
}
|
||||
|
||||
export function buildStatusAllOverviewRows(params: {
|
||||
surface: StatusOverviewSurface;
|
||||
osLabel: string;
|
||||
configPath: string;
|
||||
secretDiagnosticsCount: number;
|
||||
agentStatus: {
|
||||
bootstrapPendingCount: number;
|
||||
totalSessions: number;
|
||||
agents: Array<{
|
||||
id: string;
|
||||
lastActiveAgeMs?: number | null;
|
||||
}>;
|
||||
};
|
||||
tailscaleBackendState?: string | null;
|
||||
}) {
|
||||
return buildStatusOverviewRowsFromSurface({
|
||||
surface: params.surface,
|
||||
tailscaleBackendState: params.tailscaleBackendState,
|
||||
includeBackendStateWhenOff: true,
|
||||
includeBackendStateWhenOn: true,
|
||||
includeDnsNameWhenOff: true,
|
||||
prefixRows: [
|
||||
{ Item: "Version", Value: VERSION },
|
||||
{ Item: "OS", Value: params.osLabel },
|
||||
{ Item: "Node", Value: process.versions.node },
|
||||
{ Item: "Config", Value: params.configPath },
|
||||
],
|
||||
middleRows: [
|
||||
{ Item: "Security", Value: `Run: ${formatCliCommand("openclaw security audit --deep")}` },
|
||||
],
|
||||
agentsValue: buildStatusAllAgentsValue({
|
||||
agentStatus: params.agentStatus,
|
||||
}),
|
||||
suffixRows: [
|
||||
{
|
||||
Item: "Secrets",
|
||||
Value: buildStatusSecretsValue(params.secretDiagnosticsCount),
|
||||
},
|
||||
],
|
||||
gatewaySelfFallbackValue: "unknown",
|
||||
});
|
||||
}
|
||||
266
src/commands/status-overview-surface.test.ts
Normal file
266
src/commands/status-overview-surface.test.ts
Normal file
@@ -0,0 +1,266 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildStatusGatewayJsonPayloadFromSurface,
|
||||
buildStatusOverviewRowsFromSurface,
|
||||
buildStatusOverviewSurfaceFromOverview,
|
||||
buildStatusOverviewSurfaceFromScan,
|
||||
} from "./status-overview-surface.ts";
|
||||
|
||||
describe("status-overview-surface", () => {
|
||||
it("builds the shared overview surface from a status scan result", () => {
|
||||
expect(
|
||||
buildStatusOverviewSurfaceFromScan({
|
||||
scan: {
|
||||
cfg: { update: { channel: "stable" }, gateway: { bind: "loopback" } },
|
||||
update: { installKind: "git", git: { branch: "main", tag: "v1.2.3" } } as never,
|
||||
tailscaleMode: "serve",
|
||||
tailscaleDns: "box.tail.ts.net",
|
||||
tailscaleHttpsUrl: "https://box.tail.ts.net",
|
||||
gatewayMode: "remote",
|
||||
remoteUrlMissing: false,
|
||||
gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" },
|
||||
gatewayReachable: true,
|
||||
gatewayProbe: { connectLatencyMs: 42, error: null },
|
||||
gatewayProbeAuth: { token: "tok" },
|
||||
gatewayProbeAuthWarning: "warn-text",
|
||||
gatewaySelf: { host: "gateway", version: "1.2.3" },
|
||||
},
|
||||
gatewayService: {
|
||||
label: "LaunchAgent",
|
||||
installed: true,
|
||||
managedByOpenClaw: true,
|
||||
loadedText: "loaded",
|
||||
runtimeShort: "running",
|
||||
},
|
||||
nodeService: {
|
||||
label: "node",
|
||||
installed: true,
|
||||
loadedText: "loaded",
|
||||
runtime: { status: "running", pid: 42 },
|
||||
},
|
||||
nodeOnlyGateway: null,
|
||||
}),
|
||||
).toEqual({
|
||||
cfg: { update: { channel: "stable" }, gateway: { bind: "loopback" } },
|
||||
update: { installKind: "git", git: { branch: "main", tag: "v1.2.3" } },
|
||||
tailscaleMode: "serve",
|
||||
tailscaleDns: "box.tail.ts.net",
|
||||
tailscaleHttpsUrl: "https://box.tail.ts.net",
|
||||
gatewayMode: "remote",
|
||||
remoteUrlMissing: false,
|
||||
gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" },
|
||||
gatewayReachable: true,
|
||||
gatewayProbe: { connectLatencyMs: 42, error: null },
|
||||
gatewayProbeAuth: { token: "tok" },
|
||||
gatewayProbeAuthWarning: "warn-text",
|
||||
gatewaySelf: { host: "gateway", version: "1.2.3" },
|
||||
gatewayService: {
|
||||
label: "LaunchAgent",
|
||||
installed: true,
|
||||
managedByOpenClaw: true,
|
||||
loadedText: "loaded",
|
||||
runtimeShort: "running",
|
||||
},
|
||||
nodeService: {
|
||||
label: "node",
|
||||
installed: true,
|
||||
loadedText: "loaded",
|
||||
runtime: { status: "running", pid: 42 },
|
||||
},
|
||||
nodeOnlyGateway: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("builds the shared overview surface from scan overview data", () => {
|
||||
expect(
|
||||
buildStatusOverviewSurfaceFromOverview({
|
||||
overview: {
|
||||
cfg: { update: { channel: "stable" }, gateway: { bind: "loopback" } },
|
||||
update: { installKind: "git", git: { branch: "main", tag: "v1.2.3" } } as never,
|
||||
tailscaleMode: "serve",
|
||||
tailscaleDns: "box.tail.ts.net",
|
||||
tailscaleHttpsUrl: "https://box.tail.ts.net",
|
||||
gatewaySnapshot: {
|
||||
gatewayMode: "remote",
|
||||
remoteUrlMissing: false,
|
||||
gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" },
|
||||
gatewayReachable: true,
|
||||
gatewayProbe: { connectLatencyMs: 42, error: null },
|
||||
gatewayProbeAuth: { token: "tok" },
|
||||
gatewayProbeAuthWarning: "warn-text",
|
||||
gatewaySelf: { host: "gateway", version: "1.2.3" },
|
||||
},
|
||||
} as never,
|
||||
gatewayService: {
|
||||
label: "LaunchAgent",
|
||||
installed: true,
|
||||
managedByOpenClaw: true,
|
||||
loadedText: "loaded",
|
||||
runtimeShort: "running",
|
||||
},
|
||||
nodeService: {
|
||||
label: "node",
|
||||
installed: true,
|
||||
loadedText: "loaded",
|
||||
runtime: { status: "running", pid: 42 },
|
||||
},
|
||||
nodeOnlyGateway: null,
|
||||
}),
|
||||
).toEqual({
|
||||
cfg: { update: { channel: "stable" }, gateway: { bind: "loopback" } },
|
||||
update: { installKind: "git", git: { branch: "main", tag: "v1.2.3" } },
|
||||
tailscaleMode: "serve",
|
||||
tailscaleDns: "box.tail.ts.net",
|
||||
tailscaleHttpsUrl: "https://box.tail.ts.net",
|
||||
gatewayMode: "remote",
|
||||
remoteUrlMissing: false,
|
||||
gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" },
|
||||
gatewayReachable: true,
|
||||
gatewayProbe: { connectLatencyMs: 42, error: null },
|
||||
gatewayProbeAuth: { token: "tok" },
|
||||
gatewayProbeAuthWarning: "warn-text",
|
||||
gatewaySelf: { host: "gateway", version: "1.2.3" },
|
||||
gatewayService: {
|
||||
label: "LaunchAgent",
|
||||
installed: true,
|
||||
managedByOpenClaw: true,
|
||||
loadedText: "loaded",
|
||||
runtimeShort: "running",
|
||||
},
|
||||
nodeService: {
|
||||
label: "node",
|
||||
installed: true,
|
||||
loadedText: "loaded",
|
||||
runtime: { status: "running", pid: 42 },
|
||||
},
|
||||
nodeOnlyGateway: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("builds overview rows from the shared surface bundle", () => {
|
||||
expect(
|
||||
buildStatusOverviewRowsFromSurface({
|
||||
surface: {
|
||||
cfg: { update: { channel: "stable" }, gateway: { bind: "loopback" } },
|
||||
update: {
|
||||
installKind: "git",
|
||||
git: {
|
||||
branch: "main",
|
||||
tag: "v1.2.3",
|
||||
upstream: "origin/main",
|
||||
behind: 2,
|
||||
ahead: 0,
|
||||
dirty: false,
|
||||
fetchOk: true,
|
||||
},
|
||||
registry: { latestVersion: "2026.4.9" },
|
||||
} as never,
|
||||
tailscaleMode: "off",
|
||||
tailscaleDns: "box.tail.ts.net",
|
||||
tailscaleHttpsUrl: null,
|
||||
gatewayMode: "remote",
|
||||
remoteUrlMissing: false,
|
||||
gatewayConnection: {
|
||||
url: "wss://gateway.example.com",
|
||||
urlSource: "config",
|
||||
},
|
||||
gatewayReachable: true,
|
||||
gatewayProbe: { connectLatencyMs: 42, error: null },
|
||||
gatewayProbeAuth: { token: "tok" },
|
||||
gatewayProbeAuthWarning: "warn-text",
|
||||
gatewaySelf: { host: "gateway", version: "1.2.3" },
|
||||
gatewayService: {
|
||||
label: "LaunchAgent",
|
||||
installed: true,
|
||||
managedByOpenClaw: true,
|
||||
loadedText: "loaded",
|
||||
runtimeShort: "running",
|
||||
},
|
||||
nodeService: {
|
||||
label: "node",
|
||||
installed: true,
|
||||
loadedText: "loaded",
|
||||
runtime: { status: "running", pid: 42 },
|
||||
},
|
||||
nodeOnlyGateway: null,
|
||||
},
|
||||
prefixRows: [{ Item: "OS", Value: "macOS · node 22" }],
|
||||
suffixRows: [{ Item: "Secrets", Value: "none" }],
|
||||
agentsValue: "2 total",
|
||||
updateValue: "available · custom update",
|
||||
gatewayAuthWarningValue: "warn(warn-text)",
|
||||
gatewaySelfFallbackValue: "gateway-self",
|
||||
includeBackendStateWhenOff: true,
|
||||
includeDnsNameWhenOff: true,
|
||||
decorateOk: (value) => `ok(${value})`,
|
||||
decorateWarn: (value) => `warn(${value})`,
|
||||
decorateTailscaleOff: (value) => `muted(${value})`,
|
||||
}),
|
||||
).toEqual([
|
||||
{ Item: "OS", Value: "macOS · node 22" },
|
||||
{ Item: "Dashboard", Value: "http://127.0.0.1:18789/" },
|
||||
{ Item: "Tailscale", Value: "muted(off · box.tail.ts.net)" },
|
||||
{ Item: "Channel", Value: "stable (config)" },
|
||||
{ Item: "Git", Value: "main · tag v1.2.3" },
|
||||
{ Item: "Update", Value: "available · custom update" },
|
||||
{
|
||||
Item: "Gateway",
|
||||
Value:
|
||||
"remote · wss://gateway.example.com (config) · ok(reachable 42ms) · auth token · gateway app 1.2.3",
|
||||
},
|
||||
{ Item: "Gateway auth warning", Value: "warn(warn-text)" },
|
||||
{ Item: "Gateway self", Value: "gateway-self" },
|
||||
{ Item: "Gateway service", Value: "LaunchAgent installed · loaded · running" },
|
||||
{ Item: "Node service", Value: "node loaded · running (pid 42)" },
|
||||
{ Item: "Agents", Value: "2 total" },
|
||||
{ Item: "Secrets", Value: "none" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("builds the shared gateway json payload from the overview surface", () => {
|
||||
expect(
|
||||
buildStatusGatewayJsonPayloadFromSurface({
|
||||
surface: {
|
||||
cfg: { update: { channel: "stable" }, gateway: { bind: "loopback" } },
|
||||
update: { installKind: "package", packageManager: "npm" } as never,
|
||||
tailscaleMode: "serve",
|
||||
gatewayMode: "remote",
|
||||
remoteUrlMissing: false,
|
||||
gatewayConnection: {
|
||||
url: "wss://gateway.example.com",
|
||||
urlSource: "config",
|
||||
},
|
||||
gatewayReachable: true,
|
||||
gatewayProbe: { connectLatencyMs: 42, error: null },
|
||||
gatewayProbeAuth: { token: "tok" },
|
||||
gatewayProbeAuthWarning: "warn-text",
|
||||
gatewaySelf: { host: "gateway", version: "1.2.3" },
|
||||
gatewayService: {
|
||||
label: "LaunchAgent",
|
||||
installed: true,
|
||||
managedByOpenClaw: true,
|
||||
loadedText: "loaded",
|
||||
runtimeShort: "running",
|
||||
},
|
||||
nodeService: {
|
||||
label: "node",
|
||||
installed: true,
|
||||
loadedText: "loaded",
|
||||
runtime: { status: "running", pid: 42 },
|
||||
},
|
||||
nodeOnlyGateway: null,
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
mode: "remote",
|
||||
url: "wss://gateway.example.com",
|
||||
urlSource: "config",
|
||||
misconfigured: false,
|
||||
reachable: true,
|
||||
connectLatencyMs: 42,
|
||||
self: { host: "gateway", version: "1.2.3" },
|
||||
error: null,
|
||||
authWarning: "warn-text",
|
||||
});
|
||||
});
|
||||
});
|
||||
212
src/commands/status-overview-surface.ts
Normal file
212
src/commands/status-overview-surface.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import type { UpdateCheckResult } from "../infra/update-check.js";
|
||||
import {
|
||||
buildGatewayStatusJsonPayload,
|
||||
buildStatusOverviewSurfaceRows,
|
||||
type StatusOverviewRow,
|
||||
} from "./status-all/format.js";
|
||||
import type { NodeOnlyGatewayInfo } from "./status.node-mode.js";
|
||||
import type { StatusScanOverviewResult } from "./status.scan-overview.ts";
|
||||
import type { StatusScanResult } from "./status.scan-result.ts";
|
||||
|
||||
type StatusGatewayConnection = {
|
||||
url: string;
|
||||
urlSource?: string;
|
||||
};
|
||||
|
||||
type StatusGatewayProbe = {
|
||||
connectLatencyMs?: number | null;
|
||||
error?: string | null;
|
||||
} | null;
|
||||
|
||||
type StatusGatewayAuth = {
|
||||
token?: string;
|
||||
password?: string;
|
||||
} | null;
|
||||
|
||||
type StatusGatewaySelf =
|
||||
| {
|
||||
host?: string | null;
|
||||
ip?: string | null;
|
||||
version?: string | null;
|
||||
platform?: string | null;
|
||||
}
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
type StatusServiceSummary = {
|
||||
label: string;
|
||||
installed: boolean | null;
|
||||
managedByOpenClaw?: boolean;
|
||||
loadedText: string;
|
||||
runtimeShort?: string | null;
|
||||
runtime?: {
|
||||
status?: string | null;
|
||||
pid?: number | null;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type StatusOverviewSurface = {
|
||||
cfg: Pick<OpenClawConfig, "update" | "gateway">;
|
||||
update: UpdateCheckResult;
|
||||
tailscaleMode: string;
|
||||
tailscaleDns?: string | null;
|
||||
tailscaleHttpsUrl?: string | null;
|
||||
gatewayMode: "local" | "remote";
|
||||
remoteUrlMissing: boolean;
|
||||
gatewayConnection: StatusGatewayConnection;
|
||||
gatewayReachable: boolean;
|
||||
gatewayProbe: StatusGatewayProbe;
|
||||
gatewayProbeAuth: StatusGatewayAuth;
|
||||
gatewayProbeAuthWarning?: string | null;
|
||||
gatewaySelf: StatusGatewaySelf;
|
||||
gatewayService: StatusServiceSummary;
|
||||
nodeService: StatusServiceSummary;
|
||||
nodeOnlyGateway?: NodeOnlyGatewayInfo | null;
|
||||
};
|
||||
|
||||
export function buildStatusOverviewSurfaceFromScan(params: {
|
||||
scan: Pick<
|
||||
StatusScanResult,
|
||||
| "cfg"
|
||||
| "update"
|
||||
| "tailscaleMode"
|
||||
| "tailscaleDns"
|
||||
| "tailscaleHttpsUrl"
|
||||
| "gatewayMode"
|
||||
| "remoteUrlMissing"
|
||||
| "gatewayConnection"
|
||||
| "gatewayReachable"
|
||||
| "gatewayProbe"
|
||||
| "gatewayProbeAuth"
|
||||
| "gatewayProbeAuthWarning"
|
||||
| "gatewaySelf"
|
||||
>;
|
||||
gatewayService: StatusServiceSummary;
|
||||
nodeService: StatusServiceSummary;
|
||||
nodeOnlyGateway?: NodeOnlyGatewayInfo | null;
|
||||
}): StatusOverviewSurface {
|
||||
return {
|
||||
cfg: params.scan.cfg,
|
||||
update: params.scan.update,
|
||||
tailscaleMode: params.scan.tailscaleMode,
|
||||
tailscaleDns: params.scan.tailscaleDns,
|
||||
tailscaleHttpsUrl: params.scan.tailscaleHttpsUrl,
|
||||
gatewayMode: params.scan.gatewayMode,
|
||||
remoteUrlMissing: params.scan.remoteUrlMissing,
|
||||
gatewayConnection: params.scan.gatewayConnection,
|
||||
gatewayReachable: params.scan.gatewayReachable,
|
||||
gatewayProbe: params.scan.gatewayProbe,
|
||||
gatewayProbeAuth: params.scan.gatewayProbeAuth,
|
||||
gatewayProbeAuthWarning: params.scan.gatewayProbeAuthWarning,
|
||||
gatewaySelf: params.scan.gatewaySelf,
|
||||
gatewayService: params.gatewayService,
|
||||
nodeService: params.nodeService,
|
||||
nodeOnlyGateway: params.nodeOnlyGateway,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildStatusOverviewSurfaceFromOverview(params: {
|
||||
overview: Pick<
|
||||
StatusScanOverviewResult,
|
||||
"cfg" | "update" | "tailscaleMode" | "tailscaleDns" | "tailscaleHttpsUrl" | "gatewaySnapshot"
|
||||
>;
|
||||
gatewayService: StatusServiceSummary;
|
||||
nodeService: StatusServiceSummary;
|
||||
nodeOnlyGateway?: NodeOnlyGatewayInfo | null;
|
||||
}): StatusOverviewSurface {
|
||||
return {
|
||||
cfg: params.overview.cfg,
|
||||
update: params.overview.update,
|
||||
tailscaleMode: params.overview.tailscaleMode,
|
||||
tailscaleDns: params.overview.tailscaleDns,
|
||||
tailscaleHttpsUrl: params.overview.tailscaleHttpsUrl,
|
||||
gatewayMode: params.overview.gatewaySnapshot.gatewayMode,
|
||||
remoteUrlMissing: params.overview.gatewaySnapshot.remoteUrlMissing,
|
||||
gatewayConnection: params.overview.gatewaySnapshot.gatewayConnection,
|
||||
gatewayReachable: params.overview.gatewaySnapshot.gatewayReachable,
|
||||
gatewayProbe: params.overview.gatewaySnapshot.gatewayProbe,
|
||||
gatewayProbeAuth: params.overview.gatewaySnapshot.gatewayProbeAuth,
|
||||
gatewayProbeAuthWarning: params.overview.gatewaySnapshot.gatewayProbeAuthWarning,
|
||||
gatewaySelf: params.overview.gatewaySnapshot.gatewaySelf,
|
||||
gatewayService: params.gatewayService,
|
||||
nodeService: params.nodeService,
|
||||
nodeOnlyGateway: params.nodeOnlyGateway,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildStatusOverviewRowsFromSurface(params: {
|
||||
surface: StatusOverviewSurface;
|
||||
prefixRows?: StatusOverviewRow[];
|
||||
middleRows?: StatusOverviewRow[];
|
||||
suffixRows?: StatusOverviewRow[];
|
||||
agentsValue: string;
|
||||
updateValue?: string;
|
||||
gatewayAuthWarningValue?: string | null;
|
||||
gatewaySelfFallbackValue?: string | null;
|
||||
tailscaleBackendState?: string | null;
|
||||
includeBackendStateWhenOff?: boolean;
|
||||
includeBackendStateWhenOn?: boolean;
|
||||
includeDnsNameWhenOff?: boolean;
|
||||
decorateOk?: (value: string) => string;
|
||||
decorateWarn?: (value: string) => string;
|
||||
decorateTailscaleOff?: (value: string) => string;
|
||||
decorateTailscaleWarn?: (value: string) => string;
|
||||
}) {
|
||||
return buildStatusOverviewSurfaceRows({
|
||||
cfg: params.surface.cfg,
|
||||
update: params.surface.update,
|
||||
tailscaleMode: params.surface.tailscaleMode,
|
||||
tailscaleDns: params.surface.tailscaleDns,
|
||||
tailscaleHttpsUrl: params.surface.tailscaleHttpsUrl,
|
||||
tailscaleBackendState: params.tailscaleBackendState,
|
||||
includeBackendStateWhenOff: params.includeBackendStateWhenOff,
|
||||
includeBackendStateWhenOn: params.includeBackendStateWhenOn,
|
||||
includeDnsNameWhenOff: params.includeDnsNameWhenOff,
|
||||
decorateTailscaleOff: params.decorateTailscaleOff,
|
||||
decorateTailscaleWarn: params.decorateTailscaleWarn,
|
||||
gatewayMode: params.surface.gatewayMode,
|
||||
remoteUrlMissing: params.surface.remoteUrlMissing,
|
||||
gatewayConnection: params.surface.gatewayConnection,
|
||||
gatewayReachable: params.surface.gatewayReachable,
|
||||
gatewayProbe: params.surface.gatewayProbe,
|
||||
gatewayProbeAuth: params.surface.gatewayProbeAuth,
|
||||
gatewayProbeAuthWarning: params.surface.gatewayProbeAuthWarning,
|
||||
gatewaySelf: params.surface.gatewaySelf,
|
||||
gatewayService: params.surface.gatewayService,
|
||||
nodeService: params.surface.nodeService,
|
||||
nodeOnlyGateway: params.surface.nodeOnlyGateway,
|
||||
decorateOk: params.decorateOk,
|
||||
decorateWarn: params.decorateWarn,
|
||||
prefixRows: params.prefixRows,
|
||||
middleRows: params.middleRows,
|
||||
suffixRows: params.suffixRows,
|
||||
agentsValue: params.agentsValue,
|
||||
updateValue: params.updateValue,
|
||||
gatewayAuthWarningValue: params.gatewayAuthWarningValue,
|
||||
gatewaySelfFallbackValue: params.gatewaySelfFallbackValue,
|
||||
});
|
||||
}
|
||||
|
||||
export function buildStatusGatewayJsonPayloadFromSurface(params: {
|
||||
surface: Pick<
|
||||
StatusOverviewSurface,
|
||||
| "gatewayMode"
|
||||
| "gatewayConnection"
|
||||
| "remoteUrlMissing"
|
||||
| "gatewayReachable"
|
||||
| "gatewayProbe"
|
||||
| "gatewaySelf"
|
||||
| "gatewayProbeAuthWarning"
|
||||
>;
|
||||
}) {
|
||||
return buildGatewayStatusJsonPayload({
|
||||
gatewayMode: params.surface.gatewayMode,
|
||||
gatewayConnection: params.surface.gatewayConnection,
|
||||
remoteUrlMissing: params.surface.remoteUrlMissing,
|
||||
gatewayReachable: params.surface.gatewayReachable,
|
||||
gatewayProbe: params.surface.gatewayProbe,
|
||||
gatewaySelf: params.surface.gatewaySelf,
|
||||
gatewayProbeAuthWarning: params.surface.gatewayProbeAuthWarning,
|
||||
});
|
||||
}
|
||||
@@ -5,46 +5,48 @@ describe("buildStatusCommandReportData", () => {
|
||||
it("builds report inputs from shared status surfaces", async () => {
|
||||
const result = await buildStatusCommandReportData({
|
||||
opts: { deep: true, verbose: true },
|
||||
cfg: { update: { channel: "stable" }, gateway: { bind: "loopback" } },
|
||||
update: {
|
||||
installKind: "git",
|
||||
git: {
|
||||
branch: "main",
|
||||
tag: "v1.2.3",
|
||||
upstream: "origin/main",
|
||||
behind: 2,
|
||||
ahead: 0,
|
||||
dirty: false,
|
||||
fetchOk: true,
|
||||
surface: {
|
||||
cfg: { update: { channel: "stable" }, gateway: { bind: "loopback" } },
|
||||
update: {
|
||||
installKind: "git",
|
||||
git: {
|
||||
branch: "main",
|
||||
tag: "v1.2.3",
|
||||
upstream: "origin/main",
|
||||
behind: 2,
|
||||
ahead: 0,
|
||||
dirty: false,
|
||||
fetchOk: true,
|
||||
},
|
||||
registry: { latestVersion: "2026.4.9" },
|
||||
} as never,
|
||||
tailscaleMode: "serve",
|
||||
tailscaleDns: "box.tail.ts.net",
|
||||
tailscaleHttpsUrl: "https://box.tail.ts.net",
|
||||
gatewayMode: "remote",
|
||||
remoteUrlMissing: false,
|
||||
gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" },
|
||||
gatewayReachable: true,
|
||||
gatewayProbe: { connectLatencyMs: 123, error: null },
|
||||
gatewayProbeAuth: { token: "tok" },
|
||||
gatewayProbeAuthWarning: "warn-text",
|
||||
gatewaySelf: { host: "gateway", version: "1.2.3" },
|
||||
gatewayService: {
|
||||
label: "LaunchAgent",
|
||||
installed: true,
|
||||
managedByOpenClaw: true,
|
||||
loadedText: "loaded",
|
||||
runtimeShort: "running",
|
||||
},
|
||||
registry: { latestVersion: "2026.4.9" },
|
||||
nodeService: {
|
||||
label: "node",
|
||||
installed: true,
|
||||
loadedText: "loaded",
|
||||
runtime: { status: "running", pid: 42 },
|
||||
},
|
||||
nodeOnlyGateway: null,
|
||||
},
|
||||
osSummary: { label: "macOS" },
|
||||
tailscaleMode: "serve",
|
||||
tailscaleDns: "box.tail.ts.net",
|
||||
tailscaleHttpsUrl: "https://box.tail.ts.net",
|
||||
gatewayMode: "remote",
|
||||
remoteUrlMissing: false,
|
||||
gatewayConnection: { url: "wss://gateway.example.com", urlSource: "config" },
|
||||
gatewayReachable: true,
|
||||
gatewayProbe: { connectLatencyMs: 123, error: null },
|
||||
gatewayProbeAuth: { token: "tok" },
|
||||
gatewayProbeAuthWarning: "warn-text",
|
||||
gatewaySelf: { host: "gateway", version: "1.2.3" },
|
||||
gatewayService: {
|
||||
label: "LaunchAgent",
|
||||
installed: true,
|
||||
managedByOpenClaw: true,
|
||||
loadedText: "loaded",
|
||||
runtimeShort: "running",
|
||||
},
|
||||
nodeService: {
|
||||
label: "node",
|
||||
installed: true,
|
||||
loadedText: "loaded",
|
||||
runtime: { status: "running", pid: 42 },
|
||||
},
|
||||
nodeOnlyGateway: null,
|
||||
summary: {
|
||||
tasks: { total: 3, active: 1, failures: 0, byStatus: { queued: 1, running: 1 } },
|
||||
taskAudit: { errors: 1, warnings: 0 },
|
||||
|
||||
@@ -2,27 +2,17 @@ import {
|
||||
buildStatusChannelsTableRows,
|
||||
statusChannelsTableColumns,
|
||||
} from "./status-all/channels-table.js";
|
||||
import { buildStatusOverviewSurfaceRows } from "./status-all/format.js";
|
||||
import { buildStatusCommandOverviewRows } from "./status-overview-rows.ts";
|
||||
import { type StatusOverviewSurface } from "./status-overview-surface.ts";
|
||||
import {
|
||||
buildStatusEventsValue,
|
||||
buildStatusPluginCompatibilityValue,
|
||||
buildStatusProbesValue,
|
||||
buildStatusSessionsOverviewValue,
|
||||
} from "./status-overview-values.ts";
|
||||
import {
|
||||
buildStatusAgentsValue,
|
||||
buildStatusFooterLines,
|
||||
buildStatusHealthRows,
|
||||
buildStatusHeartbeatValue,
|
||||
buildStatusLastHeartbeatValue,
|
||||
buildStatusMemoryValue,
|
||||
buildStatusPairingRecoveryLines,
|
||||
buildStatusPluginCompatibilityLines,
|
||||
buildStatusSecurityAuditLines,
|
||||
buildStatusSessionsRows,
|
||||
buildStatusSystemEventsRows,
|
||||
buildStatusSystemEventsTrailer,
|
||||
buildStatusTasksValue,
|
||||
statusHealthColumns,
|
||||
} from "./status.command-sections.js";
|
||||
|
||||
@@ -31,75 +21,8 @@ export async function buildStatusCommandReportData(params: {
|
||||
deep?: boolean;
|
||||
verbose?: boolean;
|
||||
};
|
||||
cfg: {
|
||||
update?: {
|
||||
channel?: string | null;
|
||||
};
|
||||
gateway?: {
|
||||
bind?: string;
|
||||
customBindHost?: string;
|
||||
controlUi?: {
|
||||
enabled?: boolean;
|
||||
basePath?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
update: Record<string, unknown>;
|
||||
surface: StatusOverviewSurface;
|
||||
osSummary: { label: string };
|
||||
tailscaleMode: string;
|
||||
tailscaleDns?: string | null;
|
||||
tailscaleHttpsUrl?: string | null;
|
||||
gatewayMode: "local" | "remote";
|
||||
remoteUrlMissing: boolean;
|
||||
gatewayConnection: {
|
||||
url: string;
|
||||
urlSource?: string;
|
||||
};
|
||||
gatewayReachable: boolean;
|
||||
gatewayProbe: {
|
||||
connectLatencyMs?: number | null;
|
||||
error?: string | null;
|
||||
close?: {
|
||||
reason?: string | null;
|
||||
} | null;
|
||||
} | null;
|
||||
gatewayProbeAuth: {
|
||||
token?: string;
|
||||
password?: string;
|
||||
} | null;
|
||||
gatewayProbeAuthWarning?: string | null;
|
||||
gatewaySelf:
|
||||
| {
|
||||
host?: string | null;
|
||||
ip?: string | null;
|
||||
version?: string | null;
|
||||
platform?: string | null;
|
||||
}
|
||||
| null
|
||||
| undefined;
|
||||
gatewayService: {
|
||||
label: string;
|
||||
installed: boolean | null;
|
||||
managedByOpenClaw?: boolean;
|
||||
loadedText: string;
|
||||
runtimeShort?: string | null;
|
||||
runtime?: {
|
||||
status?: string | null;
|
||||
pid?: number | null;
|
||||
} | null;
|
||||
};
|
||||
nodeService: {
|
||||
label: string;
|
||||
installed: boolean | null;
|
||||
managedByOpenClaw?: boolean;
|
||||
loadedText: string;
|
||||
runtimeShort?: string | null;
|
||||
runtime?: {
|
||||
status?: string | null;
|
||||
pid?: number | null;
|
||||
} | null;
|
||||
};
|
||||
nodeOnlyGateway: unknown;
|
||||
summary: {
|
||||
tasks: {
|
||||
total: number;
|
||||
@@ -215,6 +138,7 @@ export async function buildStatusCommandReportData(params: {
|
||||
resolveMemoryFtsState: (value: unknown) => { state: string; tone: "ok" | "warn" | "muted" };
|
||||
resolveMemoryCacheSummary: (value: unknown) => { text: string; tone: "ok" | "warn" | "muted" };
|
||||
accentDim: (value: string) => string;
|
||||
updateValue?: string;
|
||||
theme: {
|
||||
heading: (value: string) => string;
|
||||
muted: (value: string) => string;
|
||||
@@ -227,93 +151,26 @@ export async function buildStatusCommandReportData(params: {
|
||||
rows: Array<Record<string, string>>;
|
||||
}) => string;
|
||||
}) {
|
||||
const agentsValue = buildStatusAgentsValue({
|
||||
agentStatus: params.agentStatus,
|
||||
formatTimeAgo: params.formatTimeAgo,
|
||||
});
|
||||
const eventsValue = buildStatusEventsValue({
|
||||
queuedSystemEvents: params.summary.queuedSystemEvents,
|
||||
});
|
||||
const tasksValue = buildStatusTasksValue({
|
||||
const overviewRows = buildStatusCommandOverviewRows({
|
||||
opts: params.opts,
|
||||
surface: params.surface,
|
||||
osLabel: params.osSummary.label,
|
||||
summary: params.summary,
|
||||
warn: params.warn,
|
||||
muted: params.muted,
|
||||
});
|
||||
const probesValue = buildStatusProbesValue({
|
||||
health: params.health,
|
||||
ok: params.ok,
|
||||
muted: params.muted,
|
||||
});
|
||||
const heartbeatValue = buildStatusHeartbeatValue({ summary: params.summary });
|
||||
const lastHeartbeatValue = buildStatusLastHeartbeatValue({
|
||||
deep: params.opts.deep,
|
||||
gatewayReachable: params.gatewayReachable,
|
||||
lastHeartbeat: params.lastHeartbeat as never,
|
||||
warn: params.warn,
|
||||
muted: params.muted,
|
||||
formatTimeAgo: params.formatTimeAgo,
|
||||
});
|
||||
const memoryValue = buildStatusMemoryValue({
|
||||
lastHeartbeat: params.lastHeartbeat,
|
||||
agentStatus: params.agentStatus,
|
||||
memory: params.memory,
|
||||
memoryPlugin: params.memoryPlugin,
|
||||
pluginCompatibility: params.pluginCompatibility,
|
||||
ok: params.ok,
|
||||
warn: params.warn,
|
||||
muted: params.muted,
|
||||
formatTimeAgo: params.formatTimeAgo,
|
||||
formatKTokens: params.formatKTokens,
|
||||
resolveMemoryVectorState: params.resolveMemoryVectorState,
|
||||
resolveMemoryFtsState: params.resolveMemoryFtsState,
|
||||
resolveMemoryCacheSummary: params.resolveMemoryCacheSummary,
|
||||
});
|
||||
const pluginCompatibilityValue = buildStatusPluginCompatibilityValue({
|
||||
notices: params.pluginCompatibility,
|
||||
ok: params.ok,
|
||||
warn: params.warn,
|
||||
});
|
||||
|
||||
const overviewRows = buildStatusOverviewSurfaceRows({
|
||||
cfg: params.cfg,
|
||||
update: params.update as never,
|
||||
tailscaleMode: params.tailscaleMode,
|
||||
tailscaleDns: params.tailscaleDns,
|
||||
tailscaleHttpsUrl: params.tailscaleHttpsUrl,
|
||||
gatewayMode: params.gatewayMode,
|
||||
remoteUrlMissing: params.remoteUrlMissing,
|
||||
gatewayConnection: params.gatewayConnection,
|
||||
gatewayReachable: params.gatewayReachable,
|
||||
gatewayProbe: params.gatewayProbe,
|
||||
gatewayProbeAuth: params.gatewayProbeAuth,
|
||||
gatewayProbeAuthWarning: params.gatewayProbeAuthWarning,
|
||||
gatewaySelf: params.gatewaySelf,
|
||||
gatewayService: params.gatewayService,
|
||||
nodeService: params.nodeService,
|
||||
nodeOnlyGateway: params.nodeOnlyGateway as never,
|
||||
decorateOk: params.ok,
|
||||
decorateWarn: params.warn,
|
||||
decorateTailscaleOff: params.muted,
|
||||
decorateTailscaleWarn: params.warn,
|
||||
prefixRows: [
|
||||
{ Item: "OS", Value: `${params.osSummary.label} · node ${process.versions.node}` },
|
||||
],
|
||||
updateValue: params.updateValue,
|
||||
agentsValue,
|
||||
suffixRows: [
|
||||
{ Item: "Memory", Value: memoryValue },
|
||||
{ Item: "Plugin compatibility", Value: pluginCompatibilityValue },
|
||||
{ Item: "Probes", Value: probesValue },
|
||||
{ Item: "Events", Value: eventsValue },
|
||||
{ Item: "Tasks", Value: tasksValue },
|
||||
{ Item: "Heartbeat", Value: heartbeatValue },
|
||||
...(lastHeartbeatValue ? [{ Item: "Last heartbeat", Value: lastHeartbeatValue }] : []),
|
||||
{
|
||||
Item: "Sessions",
|
||||
Value: buildStatusSessionsOverviewValue({
|
||||
sessions: params.summary.sessions,
|
||||
formatKTokens: params.formatKTokens,
|
||||
}),
|
||||
},
|
||||
],
|
||||
gatewayAuthWarningValue: params.gatewayProbeAuthWarning
|
||||
? params.warn(params.gatewayProbeAuthWarning)
|
||||
: null,
|
||||
});
|
||||
|
||||
const sessionsColumns = [
|
||||
@@ -389,11 +246,11 @@ export async function buildStatusCommandReportData(params: {
|
||||
: undefined,
|
||||
usageLines: params.usageLines,
|
||||
footerLines: buildStatusFooterLines({
|
||||
updateHint: params.formatUpdateAvailableHint(params.update),
|
||||
updateHint: params.formatUpdateAvailableHint(params.surface.update),
|
||||
warn: params.theme.warn,
|
||||
formatCliCommand: params.formatCliCommand,
|
||||
nodeOnlyGateway: params.nodeOnlyGateway as never,
|
||||
gatewayReachable: params.gatewayReachable,
|
||||
nodeOnlyGateway: params.surface.nodeOnlyGateway,
|
||||
gatewayReachable: params.surface.gatewayReachable,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { withProgress } from "../cli/progress.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js";
|
||||
import { resolveStatusJsonOutput } from "./status-json-runtime.ts";
|
||||
import { type RuntimeEnv } from "../runtime.js";
|
||||
import { runStatusJsonCommand } from "./status-json-command.ts";
|
||||
import { buildStatusOverviewSurfaceFromScan } from "./status-overview-surface.ts";
|
||||
import {
|
||||
loadStatusProviderUsageModule,
|
||||
resolveStatusGatewayHealth,
|
||||
@@ -100,26 +101,24 @@ export async function statusCommand(
|
||||
return;
|
||||
}
|
||||
|
||||
const scan = opts.json
|
||||
? await loadStatusScanFastJsonModule().then(({ scanStatusJsonFast }) =>
|
||||
scanStatusJsonFast({ timeoutMs: opts.timeoutMs, all: opts.all }, runtime),
|
||||
)
|
||||
: await loadStatusScanModule().then(({ scanStatus }) =>
|
||||
scanStatus({ json: false, timeoutMs: opts.timeoutMs, all: opts.all }, runtime),
|
||||
);
|
||||
if (opts.json) {
|
||||
writeRuntimeJson(
|
||||
await runStatusJsonCommand({
|
||||
opts,
|
||||
runtime,
|
||||
await resolveStatusJsonOutput({
|
||||
scan,
|
||||
opts,
|
||||
includeSecurityAudit: true,
|
||||
includePluginCompatibility: true,
|
||||
}),
|
||||
);
|
||||
includeSecurityAudit: true,
|
||||
includePluginCompatibility: true,
|
||||
scanStatusJsonFast: async (scanOpts, runtimeForScan) =>
|
||||
await loadStatusScanFastJsonModule().then(({ scanStatusJsonFast }) =>
|
||||
scanStatusJsonFast(scanOpts, runtimeForScan),
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const scan = await loadStatusScanModule().then(({ scanStatus }) =>
|
||||
scanStatus({ json: false, timeoutMs: opts.timeoutMs, all: opts.all }, runtime),
|
||||
);
|
||||
|
||||
const {
|
||||
cfg,
|
||||
osSummary,
|
||||
@@ -253,12 +252,10 @@ export async function statusCommand(
|
||||
formatUsageReportLines(usage),
|
||||
)
|
||||
: undefined;
|
||||
const lines = await buildStatusCommandReportLines(
|
||||
await buildStatusCommandReportData({
|
||||
opts,
|
||||
const overviewSurface = buildStatusOverviewSurfaceFromScan({
|
||||
scan: {
|
||||
cfg,
|
||||
update,
|
||||
osSummary,
|
||||
tailscaleMode,
|
||||
tailscaleDns,
|
||||
tailscaleHttpsUrl,
|
||||
@@ -270,9 +267,16 @@ export async function statusCommand(
|
||||
gatewayProbeAuth,
|
||||
gatewayProbeAuthWarning,
|
||||
gatewaySelf,
|
||||
gatewayService: daemon,
|
||||
nodeService: nodeDaemon,
|
||||
nodeOnlyGateway,
|
||||
},
|
||||
gatewayService: daemon,
|
||||
nodeService: nodeDaemon,
|
||||
nodeOnlyGateway,
|
||||
});
|
||||
const lines = await buildStatusCommandReportLines(
|
||||
await buildStatusCommandReportData({
|
||||
opts,
|
||||
surface: overviewSurface,
|
||||
osSummary,
|
||||
summary,
|
||||
securityAudit,
|
||||
health,
|
||||
|
||||
@@ -284,6 +284,10 @@ const mocks = vi.hoisted(() => ({
|
||||
|
||||
vi.mock("../channels/config-presence.js", () => ({
|
||||
hasPotentialConfiguredChannels: mocks.hasPotentialConfiguredChannels,
|
||||
hasMeaningfulChannelConfig: (entry: unknown) =>
|
||||
Boolean(
|
||||
entry && typeof entry === "object" && Object.keys(entry as Record<string, unknown>).length,
|
||||
),
|
||||
listPotentialConfiguredChannelIds: (cfg: { channels?: Record<string, unknown> }) =>
|
||||
Object.keys(cfg.channels ?? {}).filter((key) => key !== "defaults" && key !== "modelByChannel"),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user