mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
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:
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user