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;
|
startedAt: string | null;
|
||||||
owner: string | null;
|
owner: string | null;
|
||||||
logPath: string;
|
logPath: string;
|
||||||
runningAgents: number | null;
|
|
||||||
idleAgents: number | null;
|
|
||||||
daemonNode: string;
|
daemonNode: string;
|
||||||
cliNode: string;
|
cliNode: string;
|
||||||
cliVersion: string;
|
cliVersion: string;
|
||||||
daemonVersion: string | null;
|
daemonVersion: string | null;
|
||||||
desktopManaged: boolean;
|
desktopManaged: boolean;
|
||||||
providers: ProviderBinaryStatus[];
|
providers: ProviderBinaryStatus[];
|
||||||
agentsUnavailableReason?: string;
|
|
||||||
note?: string;
|
note?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,18 +131,6 @@ function toStatusRows(status: DaemonStatus): StatusRow[] {
|
|||||||
{ key: "Daemon Version", value: status.daemonVersion ?? "-" },
|
{ 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) {
|
if (status.note) {
|
||||||
rows.push({ key: "Note", value: status.note });
|
rows.push({ key: "Note", value: status.note });
|
||||||
}
|
}
|
||||||
@@ -217,11 +202,8 @@ interface DaemonProbeResult {
|
|||||||
connectedDaemon: DaemonStatus["connectedDaemon"];
|
connectedDaemon: DaemonStatus["connectedDaemon"];
|
||||||
localDaemonOverride?: DaemonStatus["localDaemon"];
|
localDaemonOverride?: DaemonStatus["localDaemon"];
|
||||||
daemonVersion?: string | null;
|
daemonVersion?: string | null;
|
||||||
runningAgents?: number;
|
|
||||||
idleAgents?: number;
|
|
||||||
daemonNodeOverride?: string;
|
daemonNodeOverride?: string;
|
||||||
daemonProviders?: ProviderBinaryStatus[];
|
daemonProviders?: ProviderBinaryStatus[];
|
||||||
agentsUnavailableReason?: string;
|
|
||||||
note?: 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.`;
|
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: {
|
async function probeDaemonOverWebsocket(args: {
|
||||||
host: string;
|
host: string;
|
||||||
state: ReturnType<typeof resolveLocalDaemonState>;
|
state: ReturnType<typeof resolveLocalDaemonState>;
|
||||||
@@ -259,7 +236,6 @@ async function probeDaemonOverWebsocket(args: {
|
|||||||
if (authFailure) {
|
if (authFailure) {
|
||||||
return {
|
return {
|
||||||
connectedDaemon: authFailure,
|
connectedDaemon: authFailure,
|
||||||
agentsUnavailableReason: describeAgentsUnavailableReason(authFailure),
|
|
||||||
note: describeDaemonAuthProbeFailure(host, authFailure),
|
note: describeDaemonAuthProbeFailure(host, authFailure),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -275,44 +251,23 @@ async function probeDaemonOverWebsocket(args: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const daemonVersion = client.getLastServerInfoMessage()?.version ?? null;
|
const daemonVersion = client.getLastServerInfoMessage()?.version ?? null;
|
||||||
const supportsDaemonStatusRpc =
|
|
||||||
client.getLastServerInfoMessage()?.features?.daemonStatusRpc === true;
|
|
||||||
try {
|
try {
|
||||||
const agentsPayload = await client.fetchAgents({
|
const statusPayload = await client.getDaemonStatus({
|
||||||
filter: { includeArchived: true },
|
|
||||||
timeout: DAEMON_STATUS_PROBE_TIMEOUT_MS,
|
timeout: DAEMON_STATUS_PROBE_TIMEOUT_MS,
|
||||||
});
|
});
|
||||||
const agents = agentsPayload.entries.map((entry) => entry.agent);
|
const labelMap = new Map(PROVIDER_BINARIES.map((p) => [p.binary, p.label]));
|
||||||
const runningAgents = agents.filter((a) => a.status === "running").length;
|
const daemonProviders = statusPayload.providers.map((p) => ({
|
||||||
const idleAgents = agents.filter((a) => a.status === "idle").length;
|
label: labelMap.get(p.provider) ?? p.provider,
|
||||||
|
path: p.available ? "available" : null,
|
||||||
let daemonProviders: ProviderBinaryStatus[] | undefined;
|
version: p.available ? null : (p.error ?? null),
|
||||||
if (supportsDaemonStatusRpc) {
|
source: "daemon" as const,
|
||||||
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.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.running) {
|
if (!state.running) {
|
||||||
return {
|
return {
|
||||||
connectedDaemon: "reachable",
|
connectedDaemon: "reachable",
|
||||||
daemonVersion,
|
daemonVersion: statusPayload.version ?? daemonVersion,
|
||||||
runningAgents,
|
daemonNodeOverride: statusPayload.nodePath,
|
||||||
idleAgents,
|
|
||||||
daemonNodeOverride: "unknown (API reachable, PID unresolved)",
|
|
||||||
daemonProviders,
|
daemonProviders,
|
||||||
note: state.pidInfo
|
note: state.pidInfo
|
||||||
? `Connected daemon is reachable at ${host} even though local daemon PID ${state.pidInfo.pid} is stale`
|
? `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 {
|
return {
|
||||||
connectedDaemon: "reachable",
|
connectedDaemon: "reachable",
|
||||||
daemonVersion,
|
daemonVersion: statusPayload.version ?? daemonVersion,
|
||||||
runningAgents,
|
daemonNodeOverride: statusPayload.nodePath,
|
||||||
idleAgents,
|
|
||||||
daemonProviders,
|
daemonProviders,
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return {
|
return {
|
||||||
connectedDaemon: "reachable",
|
connectedDaemon: "reachable",
|
||||||
daemonVersion,
|
daemonVersion,
|
||||||
localDaemonOverride: state.running ? "unresponsive" : undefined,
|
|
||||||
note: state.running
|
note: state.running
|
||||||
? `Local daemon PID is running but API requests to ${host} failed`
|
? `Local daemon PID is running but daemon detail request to ${host} failed`
|
||||||
: `Connected daemon websocket is reachable at ${host} but fetch_agents failed`,
|
: `Connected daemon websocket is reachable at ${host} but daemon status request failed`,
|
||||||
};
|
};
|
||||||
} finally {
|
} finally {
|
||||||
await client.close().catch(() => {});
|
await client.close().catch(() => {});
|
||||||
@@ -347,10 +300,7 @@ interface ProbeMergeState {
|
|||||||
localDaemon: DaemonStatus["localDaemon"];
|
localDaemon: DaemonStatus["localDaemon"];
|
||||||
daemonNode: string;
|
daemonNode: string;
|
||||||
daemonVersion: string | null;
|
daemonVersion: string | null;
|
||||||
runningAgents: number | null;
|
|
||||||
idleAgents: number | null;
|
|
||||||
daemonProviders: ProviderBinaryStatus[] | undefined;
|
daemonProviders: ProviderBinaryStatus[] | undefined;
|
||||||
agentsUnavailableReason: string | undefined;
|
|
||||||
note: string | undefined;
|
note: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,10 +311,7 @@ function applyProbeToStatus(input: ProbeMergeState): Omit<ProbeMergeState, "prob
|
|||||||
localDaemon: probe.localDaemonOverride ?? input.localDaemon,
|
localDaemon: probe.localDaemonOverride ?? input.localDaemon,
|
||||||
daemonNode: probe.daemonNodeOverride ?? input.daemonNode,
|
daemonNode: probe.daemonNodeOverride ?? input.daemonNode,
|
||||||
daemonVersion: probe.daemonVersion !== undefined ? probe.daemonVersion : input.daemonVersion,
|
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,
|
daemonProviders: probe.daemonProviders ?? input.daemonProviders,
|
||||||
agentsUnavailableReason: probe.agentsUnavailableReason ?? input.agentsUnavailableReason,
|
|
||||||
note: probe.note ? appendNote(input.note, probe.note) : input.note,
|
note: probe.note ? appendNote(input.note, probe.note) : input.note,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -410,11 +357,8 @@ export async function runStatusCommand(
|
|||||||
const cliNode = process.execPath;
|
const cliNode = process.execPath;
|
||||||
let localDaemon: DaemonStatus["localDaemon"] = state.running ? "running" : "stopped";
|
let localDaemon: DaemonStatus["localDaemon"] = state.running ? "running" : "stopped";
|
||||||
let connectedDaemon: DaemonStatus["connectedDaemon"] = "not_probed";
|
let connectedDaemon: DaemonStatus["connectedDaemon"] = "not_probed";
|
||||||
let runningAgents: number | null = null;
|
|
||||||
let idleAgents: number | null = null;
|
|
||||||
let daemonVersion: string | null = null;
|
let daemonVersion: string | null = null;
|
||||||
let daemonProviders: ProviderBinaryStatus[] | undefined;
|
let daemonProviders: ProviderBinaryStatus[] | undefined;
|
||||||
let agentsUnavailableReason: string | undefined;
|
|
||||||
let note: string | undefined;
|
let note: string | undefined;
|
||||||
|
|
||||||
if (!state.running && state.stalePidFile && state.pidInfo) {
|
if (!state.running && state.stalePidFile && state.pidInfo) {
|
||||||
@@ -424,28 +368,16 @@ export async function runStatusCommand(
|
|||||||
|
|
||||||
if (host) {
|
if (host) {
|
||||||
const probe = await probeDaemonOverWebsocket({ host, state });
|
const probe = await probeDaemonOverWebsocket({ host, state });
|
||||||
({
|
({ connectedDaemon, localDaemon, daemonNode, daemonVersion, daemonProviders, note } =
|
||||||
connectedDaemon,
|
applyProbeToStatus({
|
||||||
localDaemon,
|
probe,
|
||||||
daemonNode,
|
connectedDaemon,
|
||||||
daemonVersion,
|
localDaemon,
|
||||||
runningAgents,
|
daemonNode,
|
||||||
idleAgents,
|
daemonVersion,
|
||||||
daemonProviders,
|
daemonProviders,
|
||||||
agentsUnavailableReason,
|
note,
|
||||||
note,
|
}));
|
||||||
} = applyProbeToStatus({
|
|
||||||
probe,
|
|
||||||
connectedDaemon,
|
|
||||||
localDaemon,
|
|
||||||
daemonNode,
|
|
||||||
daemonVersion,
|
|
||||||
runningAgents,
|
|
||||||
idleAgents,
|
|
||||||
daemonProviders,
|
|
||||||
agentsUnavailableReason,
|
|
||||||
note,
|
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
note = appendNote(note, "Daemon is configured for unix socket listen; API probe skipped");
|
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,
|
startedAt: state.pidInfo?.startedAt ?? null,
|
||||||
owner,
|
owner,
|
||||||
logPath: state.logPath,
|
logPath: state.logPath,
|
||||||
runningAgents,
|
|
||||||
idleAgents,
|
|
||||||
daemonNode,
|
daemonNode,
|
||||||
cliNode,
|
cliNode,
|
||||||
cliVersion,
|
cliVersion,
|
||||||
daemonVersion,
|
daemonVersion,
|
||||||
desktopManaged: state.pidInfo?.desktopManaged === true,
|
desktopManaged: state.pidInfo?.desktopManaged === true,
|
||||||
providers,
|
providers,
|
||||||
agentsUnavailableReason,
|
|
||||||
note,
|
note,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ try {
|
|||||||
|
|
||||||
assert.strictEqual(status.localDaemon, "running");
|
assert.strictEqual(status.localDaemon, "running");
|
||||||
assert.strictEqual(status.connectedDaemon, "auth_required");
|
assert.strictEqual(status.connectedDaemon, "auth_required");
|
||||||
assert.strictEqual(status.runningAgents, null);
|
assert(!("runningAgents" in status), "status should not fetch agent counts");
|
||||||
assert.strictEqual(status.idleAgents, null);
|
assert(!("idleAgents" in status), "status should not fetch agent counts");
|
||||||
assert.match(status.note, /requires a password/i);
|
assert.match(status.note, /requires a password/i);
|
||||||
assert.doesNotMatch(status.note, /not reachable/i);
|
assert.doesNotMatch(status.note, /not reachable/i);
|
||||||
console.log("✓ missing password reports auth_required\n");
|
console.log("✓ missing password reports auth_required\n");
|
||||||
@@ -81,8 +81,8 @@ try {
|
|||||||
|
|
||||||
assert.strictEqual(status.localDaemon, "running");
|
assert.strictEqual(status.localDaemon, "running");
|
||||||
assert.strictEqual(status.connectedDaemon, "reachable");
|
assert.strictEqual(status.connectedDaemon, "reachable");
|
||||||
assert.strictEqual(status.runningAgents, 0);
|
assert(!("runningAgents" in status), "status should not fetch agent counts");
|
||||||
assert.strictEqual(status.idleAgents, 0);
|
assert(!("idleAgents" in status), "status should not fetch agent counts");
|
||||||
console.log("✓ password-authenticated status remains reachable\n");
|
console.log("✓ password-authenticated status remains reachable\n");
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user