feat: expand daemon status diagnostics
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Command } from "commander";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const callGateway = vi.fn(async () => ({ ok: true }));
|
||||
const resolveGatewayProgramArguments = vi.fn(async () => ({
|
||||
@@ -13,8 +13,8 @@ const serviceIsLoaded = vi.fn().mockResolvedValue(false);
|
||||
const serviceReadCommand = vi.fn().mockResolvedValue(null);
|
||||
const serviceReadRuntime = vi.fn().mockResolvedValue({ status: "running" });
|
||||
const findExtraGatewayServices = vi.fn(async () => []);
|
||||
const inspectPortUsage = vi.fn(async () => ({
|
||||
port: 18789,
|
||||
const inspectPortUsage = vi.fn(async (port: number) => ({
|
||||
port,
|
||||
status: "free",
|
||||
listeners: [],
|
||||
hints: [],
|
||||
@@ -77,6 +77,39 @@ vi.mock("./deps.js", () => ({
|
||||
}));
|
||||
|
||||
describe("daemon-cli coverage", () => {
|
||||
const originalEnv = {
|
||||
CLAWDBOT_STATE_DIR: process.env.CLAWDBOT_STATE_DIR,
|
||||
CLAWDBOT_CONFIG_PATH: process.env.CLAWDBOT_CONFIG_PATH,
|
||||
CLAWDBOT_GATEWAY_PORT: process.env.CLAWDBOT_GATEWAY_PORT,
|
||||
CLAWDBOT_PROFILE: process.env.CLAWDBOT_PROFILE,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.CLAWDBOT_STATE_DIR = "/tmp/clawdbot-cli-state";
|
||||
process.env.CLAWDBOT_CONFIG_PATH = "/tmp/clawdbot-cli-state/clawdbot.json";
|
||||
delete process.env.CLAWDBOT_GATEWAY_PORT;
|
||||
delete process.env.CLAWDBOT_PROFILE;
|
||||
serviceReadCommand.mockResolvedValue(null);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalEnv.CLAWDBOT_STATE_DIR !== undefined)
|
||||
process.env.CLAWDBOT_STATE_DIR = originalEnv.CLAWDBOT_STATE_DIR;
|
||||
else delete process.env.CLAWDBOT_STATE_DIR;
|
||||
|
||||
if (originalEnv.CLAWDBOT_CONFIG_PATH !== undefined)
|
||||
process.env.CLAWDBOT_CONFIG_PATH = originalEnv.CLAWDBOT_CONFIG_PATH;
|
||||
else delete process.env.CLAWDBOT_CONFIG_PATH;
|
||||
|
||||
if (originalEnv.CLAWDBOT_GATEWAY_PORT !== undefined)
|
||||
process.env.CLAWDBOT_GATEWAY_PORT = originalEnv.CLAWDBOT_GATEWAY_PORT;
|
||||
else delete process.env.CLAWDBOT_GATEWAY_PORT;
|
||||
|
||||
if (originalEnv.CLAWDBOT_PROFILE !== undefined)
|
||||
process.env.CLAWDBOT_PROFILE = originalEnv.CLAWDBOT_PROFILE;
|
||||
else delete process.env.CLAWDBOT_PROFILE;
|
||||
});
|
||||
|
||||
it("probes gateway status by default", async () => {
|
||||
runtimeLogs.length = 0;
|
||||
runtimeErrors.length = 0;
|
||||
@@ -97,6 +130,51 @@ describe("daemon-cli coverage", () => {
|
||||
expect(inspectPortUsage).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("derives probe URL from service args + env (json)", async () => {
|
||||
runtimeLogs.length = 0;
|
||||
runtimeErrors.length = 0;
|
||||
callGateway.mockClear();
|
||||
inspectPortUsage.mockClear();
|
||||
|
||||
serviceReadCommand.mockResolvedValueOnce({
|
||||
programArguments: ["/bin/node", "cli", "gateway", "--port", "19001"],
|
||||
environment: {
|
||||
CLAWDBOT_PROFILE: "dev",
|
||||
CLAWDBOT_STATE_DIR: "/tmp/clawdbot-daemon-state",
|
||||
CLAWDBOT_CONFIG_PATH: "/tmp/clawdbot-daemon-state/clawdbot.json",
|
||||
CLAWDBOT_GATEWAY_PORT: "19001",
|
||||
},
|
||||
sourcePath: "/tmp/com.clawdbot.gateway.plist",
|
||||
});
|
||||
|
||||
const { registerDaemonCli } = await import("./daemon-cli.js");
|
||||
const program = new Command();
|
||||
program.exitOverride();
|
||||
registerDaemonCli(program);
|
||||
|
||||
await program.parseAsync(["daemon", "status", "--json"], { from: "user" });
|
||||
|
||||
expect(callGateway).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
url: "ws://127.0.0.1:19001",
|
||||
method: "status",
|
||||
}),
|
||||
);
|
||||
expect(inspectPortUsage).toHaveBeenCalledWith(19001);
|
||||
|
||||
const parsed = JSON.parse(runtimeLogs[0] ?? "{}") as {
|
||||
gateway?: { port?: number; portSource?: string; probeUrl?: string };
|
||||
config?: { mismatch?: boolean };
|
||||
rpc?: { url?: string; ok?: boolean };
|
||||
};
|
||||
expect(parsed.gateway?.port).toBe(19001);
|
||||
expect(parsed.gateway?.portSource).toBe("service args");
|
||||
expect(parsed.gateway?.probeUrl).toBe("ws://127.0.0.1:19001");
|
||||
expect(parsed.config?.mismatch).toBe(true);
|
||||
expect(parsed.rpc?.url).toBe("ws://127.0.0.1:19001");
|
||||
expect(parsed.rpc?.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("passes deep scan flag for daemon status", async () => {
|
||||
findExtraGatewayServices.mockClear();
|
||||
|
||||
|
||||
@@ -18,12 +18,12 @@ import {
|
||||
GATEWAY_SYSTEMD_SERVICE_NAME,
|
||||
GATEWAY_WINDOWS_TASK_NAME,
|
||||
} from "../daemon/constants.js";
|
||||
import { readLastGatewayErrorLine } from "../daemon/diagnostics.js";
|
||||
import {
|
||||
type FindExtraGatewayServicesOptions,
|
||||
findExtraGatewayServices,
|
||||
renderGatewayServiceCleanupHints,
|
||||
} from "../daemon/inspect.js";
|
||||
import { readLastGatewayErrorLine } from "../daemon/diagnostics.js";
|
||||
import { resolveGatewayLogPaths } from "../daemon/launchd.js";
|
||||
import { findLegacyGatewayServices } from "../daemon/legacy.js";
|
||||
import { resolveGatewayProgramArguments } from "../daemon/program-args.js";
|
||||
@@ -165,8 +165,11 @@ function parsePortFromArgs(
|
||||
return null;
|
||||
}
|
||||
|
||||
function pickProbeHostForBind(bindMode: string, tailnetIPv4: string | null) {
|
||||
if (bindMode === "tailnet") return tailnetIPv4;
|
||||
function pickProbeHostForBind(
|
||||
bindMode: string,
|
||||
tailnetIPv4: string | undefined,
|
||||
) {
|
||||
if (bindMode === "tailnet") return tailnetIPv4 ?? "127.0.0.1";
|
||||
if (bindMode === "auto") return tailnetIPv4 ?? "127.0.0.1";
|
||||
return "127.0.0.1";
|
||||
}
|
||||
@@ -330,7 +333,10 @@ async function gatherDaemonStatus(opts: {
|
||||
...(serviceEnv ?? {}),
|
||||
} satisfies Record<string, string | undefined>;
|
||||
|
||||
const cliConfigPath = resolveConfigPath(process.env, resolveStateDir(process.env));
|
||||
const cliConfigPath = resolveConfigPath(
|
||||
process.env,
|
||||
resolveStateDir(process.env),
|
||||
);
|
||||
const daemonConfigPath = resolveConfigPath(
|
||||
mergedDaemonEnv as NodeJS.ProcessEnv,
|
||||
resolveStateDir(mergedDaemonEnv as NodeJS.ProcessEnv),
|
||||
@@ -359,12 +365,15 @@ async function gatherDaemonStatus(opts: {
|
||||
path: daemonSnapshot?.path ?? daemonConfigPath,
|
||||
exists: daemonSnapshot?.exists ?? false,
|
||||
valid: daemonSnapshot?.valid ?? true,
|
||||
...(daemonSnapshot?.issues?.length ? { issues: daemonSnapshot.issues } : {}),
|
||||
...(daemonSnapshot?.issues?.length
|
||||
? { issues: daemonSnapshot.issues }
|
||||
: {}),
|
||||
};
|
||||
const configMismatch = cliConfigSummary.path !== daemonConfigSummary.path;
|
||||
|
||||
const portFromArgs = parsePortFromArgs(command?.programArguments);
|
||||
const daemonPort = portFromArgs ?? resolveGatewayPort(daemonCfg, mergedDaemonEnv);
|
||||
const daemonPort =
|
||||
portFromArgs ?? resolveGatewayPort(daemonCfg, mergedDaemonEnv);
|
||||
const portSource: GatewayStatusSummary["portSource"] = portFromArgs
|
||||
? "service args"
|
||||
: "env/config";
|
||||
@@ -510,7 +519,9 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) {
|
||||
defaultRuntime.log(`Config (cli): ${cliCfg}`);
|
||||
if (!status.config.cli.valid && status.config.cli.issues?.length) {
|
||||
for (const issue of status.config.cli.issues.slice(0, 5)) {
|
||||
defaultRuntime.error(`Config issue: ${issue.path || "<root>"}: ${issue.message}`);
|
||||
defaultRuntime.error(
|
||||
`Config issue: ${issue.path || "<root>"}: ${issue.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (status.config.daemon) {
|
||||
@@ -555,7 +566,9 @@ function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) {
|
||||
} else {
|
||||
defaultRuntime.error("RPC probe: failed");
|
||||
if (rpc.url) defaultRuntime.error(`RPC target: ${rpc.url}`);
|
||||
const lines = String(rpc.error ?? "unknown").split(/\r?\n/).filter(Boolean);
|
||||
const lines = String(rpc.error ?? "unknown")
|
||||
.split(/\r?\n/)
|
||||
.filter(Boolean);
|
||||
for (const line of lines.slice(0, 12)) {
|
||||
defaultRuntime.error(` ${line}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user