Make daemon status report health without loading agents (#1810)

* fix(cli): avoid agent inventory in daemon status

* fix(cli): keep daemon status reachable on detail failure
This commit is contained in:
Mohamed Boudra
2026-06-29 22:40:11 +02:00
committed by GitHub
parent 111fdb81fd
commit df36a958f8
2 changed files with 28 additions and 99 deletions

View File

@@ -27,15 +27,12 @@ interface DaemonStatus {
startedAt: string | null;
owner: string | null;
logPath: string;
runningAgents: number | null;
idleAgents: number | null;
daemonNode: string;
cliNode: string;
cliVersion: string;
daemonVersion: string | null;
desktopManaged: boolean;
providers: ProviderBinaryStatus[];
agentsUnavailableReason?: string;
note?: string;
}
@@ -134,18 +131,6 @@ function toStatusRows(status: DaemonStatus): StatusRow[] {
{ key: "Daemon Version", value: status.daemonVersion ?? "-" },
];
if (status.runningAgents !== null && status.idleAgents !== null) {
rows.push({
key: "Agents",
value: `${status.runningAgents} running, ${status.idleAgents} idle`,
});
} else {
rows.push({
key: "Agents",
value: `Unavailable (${status.agentsUnavailableReason ?? "daemon API not reachable"})`,
});
}
if (status.note) {
rows.push({ key: "Note", value: status.note });
}
@@ -217,11 +202,8 @@ interface DaemonProbeResult {
connectedDaemon: DaemonStatus["connectedDaemon"];
localDaemonOverride?: DaemonStatus["localDaemon"];
daemonVersion?: string | null;
runningAgents?: number;
idleAgents?: number;
daemonNodeOverride?: string;
daemonProviders?: ProviderBinaryStatus[];
agentsUnavailableReason?: string;
note?: string;
}
@@ -241,11 +223,6 @@ function describeDaemonAuthProbeFailure(host: string, failure: DaemonAuthProbeFa
return `Daemon is reachable at ${host} but the supplied password was rejected. Check PASEO_PASSWORD and retry.`;
}
function describeAgentsUnavailableReason(failure: DaemonAuthProbeFailure): string {
if (failure === "auth_required") return "password required";
return "incorrect password";
}
async function probeDaemonOverWebsocket(args: {
host: string;
state: ReturnType<typeof resolveLocalDaemonState>;
@@ -259,7 +236,6 @@ async function probeDaemonOverWebsocket(args: {
if (authFailure) {
return {
connectedDaemon: authFailure,
agentsUnavailableReason: describeAgentsUnavailableReason(authFailure),
note: describeDaemonAuthProbeFailure(host, authFailure),
};
}
@@ -275,44 +251,23 @@ async function probeDaemonOverWebsocket(args: {
}
const daemonVersion = client.getLastServerInfoMessage()?.version ?? null;
const supportsDaemonStatusRpc =
client.getLastServerInfoMessage()?.features?.daemonStatusRpc === true;
try {
const agentsPayload = await client.fetchAgents({
filter: { includeArchived: true },
const statusPayload = await client.getDaemonStatus({
timeout: DAEMON_STATUS_PROBE_TIMEOUT_MS,
});
const agents = agentsPayload.entries.map((entry) => entry.agent);
const runningAgents = agents.filter((a) => a.status === "running").length;
const idleAgents = agents.filter((a) => a.status === "idle").length;
let daemonProviders: ProviderBinaryStatus[] | undefined;
if (supportsDaemonStatusRpc) {
try {
const statusPayload = await client.getDaemonStatus({
timeout: DAEMON_STATUS_PROBE_TIMEOUT_MS,
});
const labelMap = new Map(PROVIDER_BINARIES.map((p) => [p.binary, p.label]));
daemonProviders = statusPayload.providers.map((p) => ({
label: labelMap.get(p.provider) ?? p.provider,
path: p.available ? "available" : null,
version: p.available ? null : (p.error ?? null),
source: "daemon" as const,
}));
} catch {
// COMPAT(daemon-rpc-rollout): fall back to CLI-side provider resolution while
// old daemons lack daemonStatusRpc. Remove once the daemon floor is past
// v0.1.76; status should come from daemon.get_status.
}
}
const labelMap = new Map(PROVIDER_BINARIES.map((p) => [p.binary, p.label]));
const daemonProviders = statusPayload.providers.map((p) => ({
label: labelMap.get(p.provider) ?? p.provider,
path: p.available ? "available" : null,
version: p.available ? null : (p.error ?? null),
source: "daemon" as const,
}));
if (!state.running) {
return {
connectedDaemon: "reachable",
daemonVersion,
runningAgents,
idleAgents,
daemonNodeOverride: "unknown (API reachable, PID unresolved)",
daemonVersion: statusPayload.version ?? daemonVersion,
daemonNodeOverride: statusPayload.nodePath,
daemonProviders,
note: state.pidInfo
? `Connected daemon is reachable at ${host} even though local daemon PID ${state.pidInfo.pid} is stale`
@@ -322,19 +277,17 @@ async function probeDaemonOverWebsocket(args: {
return {
connectedDaemon: "reachable",
daemonVersion,
runningAgents,
idleAgents,
daemonVersion: statusPayload.version ?? daemonVersion,
daemonNodeOverride: statusPayload.nodePath,
daemonProviders,
};
} catch {
return {
connectedDaemon: "reachable",
daemonVersion,
localDaemonOverride: state.running ? "unresponsive" : undefined,
note: state.running
? `Local daemon PID is running but API requests to ${host} failed`
: `Connected daemon websocket is reachable at ${host} but fetch_agents failed`,
? `Local daemon PID is running but daemon detail request to ${host} failed`
: `Connected daemon websocket is reachable at ${host} but daemon status request failed`,
};
} finally {
await client.close().catch(() => {});
@@ -347,10 +300,7 @@ interface ProbeMergeState {
localDaemon: DaemonStatus["localDaemon"];
daemonNode: string;
daemonVersion: string | null;
runningAgents: number | null;
idleAgents: number | null;
daemonProviders: ProviderBinaryStatus[] | undefined;
agentsUnavailableReason: string | undefined;
note: string | undefined;
}
@@ -361,10 +311,7 @@ function applyProbeToStatus(input: ProbeMergeState): Omit<ProbeMergeState, "prob
localDaemon: probe.localDaemonOverride ?? input.localDaemon,
daemonNode: probe.daemonNodeOverride ?? input.daemonNode,
daemonVersion: probe.daemonVersion !== undefined ? probe.daemonVersion : input.daemonVersion,
runningAgents: probe.runningAgents !== undefined ? probe.runningAgents : input.runningAgents,
idleAgents: probe.idleAgents !== undefined ? probe.idleAgents : input.idleAgents,
daemonProviders: probe.daemonProviders ?? input.daemonProviders,
agentsUnavailableReason: probe.agentsUnavailableReason ?? input.agentsUnavailableReason,
note: probe.note ? appendNote(input.note, probe.note) : input.note,
};
}
@@ -410,11 +357,8 @@ export async function runStatusCommand(
const cliNode = process.execPath;
let localDaemon: DaemonStatus["localDaemon"] = state.running ? "running" : "stopped";
let connectedDaemon: DaemonStatus["connectedDaemon"] = "not_probed";
let runningAgents: number | null = null;
let idleAgents: number | null = null;
let daemonVersion: string | null = null;
let daemonProviders: ProviderBinaryStatus[] | undefined;
let agentsUnavailableReason: string | undefined;
let note: string | undefined;
if (!state.running && state.stalePidFile && state.pidInfo) {
@@ -424,28 +368,16 @@ export async function runStatusCommand(
if (host) {
const probe = await probeDaemonOverWebsocket({ host, state });
({
connectedDaemon,
localDaemon,
daemonNode,
daemonVersion,
runningAgents,
idleAgents,
daemonProviders,
agentsUnavailableReason,
note,
} = applyProbeToStatus({
probe,
connectedDaemon,
localDaemon,
daemonNode,
daemonVersion,
runningAgents,
idleAgents,
daemonProviders,
agentsUnavailableReason,
note,
}));
({ connectedDaemon, localDaemon, daemonNode, daemonVersion, daemonProviders, note } =
applyProbeToStatus({
probe,
connectedDaemon,
localDaemon,
daemonNode,
daemonVersion,
daemonProviders,
note,
}));
} else {
note = appendNote(note, "Daemon is configured for unix socket listen; API probe skipped");
}
@@ -472,15 +404,12 @@ export async function runStatusCommand(
startedAt: state.pidInfo?.startedAt ?? null,
owner,
logPath: state.logPath,
runningAgents,
idleAgents,
daemonNode,
cliNode,
cliVersion,
daemonVersion,
desktopManaged: state.pidInfo?.desktopManaged === true,
providers,
agentsUnavailableReason,
note,
};

View File

@@ -43,8 +43,8 @@ try {
assert.strictEqual(status.localDaemon, "running");
assert.strictEqual(status.connectedDaemon, "auth_required");
assert.strictEqual(status.runningAgents, null);
assert.strictEqual(status.idleAgents, null);
assert(!("runningAgents" in status), "status should not fetch agent counts");
assert(!("idleAgents" in status), "status should not fetch agent counts");
assert.match(status.note, /requires a password/i);
assert.doesNotMatch(status.note, /not reachable/i);
console.log("✓ missing password reports auth_required\n");
@@ -81,8 +81,8 @@ try {
assert.strictEqual(status.localDaemon, "running");
assert.strictEqual(status.connectedDaemon, "reachable");
assert.strictEqual(status.runningAgents, 0);
assert.strictEqual(status.idleAgents, 0);
assert(!("runningAgents" in status), "status should not fetch agent counts");
assert(!("idleAgents" in status), "status should not fetch agent counts");
console.log("✓ password-authenticated status remains reachable\n");
}
} finally {