Status: route JSON through lean command

This commit is contained in:
Vincent Koc
2026-03-15 20:56:44 -07:00
parent 1f50fed3b2
commit ca2f046668
3 changed files with 130 additions and 0 deletions

View File

@@ -6,6 +6,7 @@ const runConfigUnsetMock = vi.hoisted(() => vi.fn(async () => {}));
const modelsListCommandMock = vi.hoisted(() => vi.fn(async () => {}));
const modelsStatusCommandMock = vi.hoisted(() => vi.fn(async () => {}));
const gatewayStatusCommandMock = vi.hoisted(() => vi.fn(async () => {}));
const statusJsonCommandMock = vi.hoisted(() => vi.fn(async () => {}));
vi.mock("../config-cli.js", () => ({
runConfigGet: runConfigGetMock,
@@ -21,6 +22,10 @@ vi.mock("../../commands/gateway-status.js", () => ({
gatewayStatusCommand: gatewayStatusCommandMock,
}));
vi.mock("../../commands/status-json.js", () => ({
statusJsonCommand: statusJsonCommandMock,
}));
describe("program routes", () => {
beforeEach(() => {
vi.clearAllMocks();
@@ -124,6 +129,26 @@ describe("program routes", () => {
await expectRunFalse(["status"], ["node", "openclaw", "status", "--timeout"]);
});
it("routes status --json through the lean JSON command", async () => {
const route = expectRoute(["status"]);
await expect(
route?.run([
"node",
"openclaw",
"status",
"--json",
"--deep",
"--usage",
"--timeout",
"5000",
]),
).resolves.toBe(true);
expect(statusJsonCommandMock).toHaveBeenCalledWith(
{ deep: true, all: false, usage: true, timeoutMs: 5000 },
expect.any(Object),
);
});
it("returns false for sessions route when --store value is missing", async () => {
await expectRunFalse(["sessions"], ["node", "openclaw", "sessions", "--store"]);
});

View File

@@ -47,6 +47,11 @@ const routeStatus: RouteSpec = {
if (timeoutMs === null) {
return false;
}
if (json) {
const { statusJsonCommand } = await import("../../commands/status-json.js");
await statusJsonCommand({ deep, all, usage, timeoutMs }, defaultRuntime);
return true;
}
const { statusCommand } = await import("../../commands/status.js");
await statusCommand({ json, deep, all, usage, timeoutMs, verbose }, defaultRuntime);
return true;

100
src/commands/status-json.ts Normal file
View File

@@ -0,0 +1,100 @@
import { callGateway } from "../gateway/call.js";
import type { HeartbeatEventPayload } from "../infra/heartbeat-events.js";
import { normalizeUpdateChannel, resolveUpdateChannelDisplay } from "../infra/update-channels.js";
import type { RuntimeEnv } from "../runtime.js";
import { runSecurityAudit } from "../security/audit.js";
import { getDaemonStatusSummary, getNodeDaemonStatusSummary } from "./status.daemon.js";
import { scanStatus } from "./status.scan.js";
let providerUsagePromise: Promise<typeof import("../infra/provider-usage.js")> | undefined;
function loadProviderUsage() {
providerUsagePromise ??= import("../infra/provider-usage.js");
return providerUsagePromise;
}
export async function statusJsonCommand(
opts: {
deep?: boolean;
usage?: boolean;
timeoutMs?: number;
all?: boolean;
},
runtime: RuntimeEnv,
) {
const scan = await scanStatus({ json: true, timeoutMs: opts.timeoutMs, all: opts.all }, runtime);
const securityAudit = await runSecurityAudit({
config: scan.cfg,
sourceConfig: scan.sourceConfig,
deep: false,
includeFilesystem: true,
includeChannelSecurity: true,
});
const usage = opts.usage
? await loadProviderUsage().then(({ loadProviderUsageSummary }) =>
loadProviderUsageSummary({ timeoutMs: opts.timeoutMs }),
)
: undefined;
const health = opts.deep
? await callGateway({
method: "health",
params: { probe: true },
timeoutMs: opts.timeoutMs,
config: scan.cfg,
}).catch(() => undefined)
: undefined;
const lastHeartbeat =
opts.deep && scan.gatewayReachable
? await callGateway<HeartbeatEventPayload | null>({
method: "last-heartbeat",
params: {},
timeoutMs: opts.timeoutMs,
config: scan.cfg,
}).catch(() => null)
: null;
const [daemon, nodeDaemon] = await Promise.all([
getDaemonStatusSummary(),
getNodeDaemonStatusSummary(),
]);
const channelInfo = resolveUpdateChannelDisplay({
configChannel: normalizeUpdateChannel(scan.cfg.update?.channel),
installKind: scan.update.installKind,
gitTag: scan.update.git?.tag ?? null,
gitBranch: scan.update.git?.branch ?? null,
});
runtime.log(
JSON.stringify(
{
...scan.summary,
os: scan.osSummary,
update: scan.update,
updateChannel: channelInfo.channel,
updateChannelSource: channelInfo.source,
memory: scan.memory,
memoryPlugin: scan.memoryPlugin,
gateway: {
mode: scan.gatewayMode,
url: scan.gatewayConnection.url,
urlSource: scan.gatewayConnection.urlSource,
misconfigured: scan.remoteUrlMissing,
reachable: scan.gatewayReachable,
connectLatencyMs: scan.gatewayProbe?.connectLatencyMs ?? null,
self: scan.gatewaySelf,
error: scan.gatewayProbe?.error ?? null,
authWarning: scan.gatewayProbeAuthWarning ?? null,
},
gatewayService: daemon,
nodeService: nodeDaemon,
agents: scan.agentStatus,
securityAudit,
secretDiagnostics: scan.secretDiagnostics,
...(health || usage || lastHeartbeat ? { health, usage, lastHeartbeat } : {}),
},
null,
2,
),
);
}