Add app diagnostic report (#1728)

* feat(diagnostics): add app diagnostic report

* fix(diagnostics): guard app diagnostic runs

* fix(diagnostics): include websocket runtime metrics

* fix(diagnostics): type websocket metric snapshot

* fix(diagnostics): include daemon shell env

* fix(loop): make stop cancellation test deterministic
This commit is contained in:
Mohamed Boudra
2026-06-25 23:20:52 +08:00
committed by GitHub
parent bdd9419189
commit 26f169866f
23 changed files with 1812 additions and 36 deletions
@@ -0,0 +1,104 @@
import { describe, expect, test } from "vitest";
import { formatHostRuntimeSection, redactAppDiagnosticReport } from "./app-diagnostic-report";
import type { HostRuntimeSnapshot } from "@/runtime/host-runtime";
import type { HostProfile } from "@/types/host-connection";
function makeHost(): HostProfile {
return {
serverId: "srv-secret",
label: "Secret host",
lifecycle: {},
preferredConnectionId: "direct:secret.example.test:6767",
createdAt: "2026-06-25T00:00:00.000Z",
updatedAt: "2026-06-25T00:00:00.000Z",
connections: [
{
id: "direct:secret.example.test:6767",
type: "directTcp",
endpoint: "secret.example.test:6767",
useTls: true,
password: "tcp-password",
},
{
id: "relay:relay.secret.test:443",
type: "relay",
relayEndpoint: "relay.secret.test:443",
useTls: true,
daemonPublicKeyB64: "daemon-public-key-secret",
},
{
id: "socket:/tmp/paseo-secret.sock",
type: "directSocket",
path: "/tmp/paseo-secret.sock",
},
{
id: "pipe:\\\\.\\pipe\\paseo-secret",
type: "directPipe",
path: "\\\\.\\pipe\\paseo-secret",
},
],
};
}
describe("app diagnostics report", () => {
test("formats connection rows without raw connection details", () => {
const host = makeHost();
const snapshot: HostRuntimeSnapshot = {
serverId: host.serverId,
activeConnectionId: "relay:relay.secret.test:443",
activeConnection: {
type: "relay",
endpoint: "relay.secret.test:443",
display: "relay",
},
connectionStatus: "online",
client: null,
lastError: null,
lastOnlineAt: "2026-06-25T00:00:00.000Z",
agentDirectoryStatus: "ready",
agentDirectoryError: null,
hasEverLoadedAgentDirectory: true,
probeByConnectionId: new Map([
["direct:secret.example.test:6767", { status: "available", latencyMs: 42 }],
["relay:relay.secret.test:443", { status: "available", latencyMs: 8 }],
]),
clientGeneration: 1,
};
const report = formatHostRuntimeSection({ host, snapshot });
expect(report).toContain("direct TCP");
expect(report).toContain("relay");
expect(report).toContain("local socket");
expect(report).toContain("local pipe");
expect(report).not.toContain("secret.example.test");
expect(report).not.toContain("relay.secret.test");
expect(report).not.toContain("daemon-public-key-secret");
expect(report).not.toContain("/tmp/paseo-secret.sock");
expect(report).not.toContain("tcp-password");
});
test("redacts saved connection secrets from collected daemon and desktop text", () => {
const host = makeHost();
const redacted = redactAppDiagnosticReport(
[
"secret.example.test:6767",
"relay.secret.test:443",
"daemon-public-key-secret",
"/tmp/paseo-secret.sock",
"\\\\.\\pipe\\paseo-secret",
"password=tcp-password",
"paseo://pairing-secret",
].join("\n"),
[host],
);
expect(redacted).not.toContain("secret.example.test");
expect(redacted).not.toContain("relay.secret.test");
expect(redacted).not.toContain("daemon-public-key-secret");
expect(redacted).not.toContain("/tmp/paseo-secret.sock");
expect(redacted).not.toContain("\\\\.\\pipe\\paseo-secret");
expect(redacted).not.toContain("tcp-password");
expect(redacted).not.toContain("pairing-secret");
});
});
@@ -0,0 +1,133 @@
import type { ServerInfoStatusPayload } from "@getpaseo/protocol/messages";
import type { HostRuntimeSnapshot } from "@/runtime/host-runtime";
import type { HostConnection, HostProfile } from "@/types/host-connection";
interface DiagnosticEntry {
label: string;
value: string;
}
export function formatDiagnosticSection(title: string, entries: DiagnosticEntry[]): string {
return [title, ...entries.map((entry) => ` ${entry.label}: ${entry.value}`)].join("\n");
}
export function formatAppDiagnosticHeader(input: {
appVersion: string | null;
platform: string;
isDesktopApp: boolean;
hostCount: number;
}): string {
return formatDiagnosticSection("Paseo app diagnostics", [
{ label: "Collected at", value: new Date().toISOString() },
{ label: "App version", value: input.appVersion ?? "unknown" },
{ label: "Platform", value: input.platform },
{ label: "Desktop app", value: String(input.isDesktopApp) },
{ label: "Saved hosts", value: String(input.hostCount) },
]);
}
export function formatHostRuntimeSection(input: {
host: HostProfile;
snapshot: HostRuntimeSnapshot | null;
}): string {
const { host, snapshot } = input;
const entries: DiagnosticEntry[] = [
{ label: "Server ID", value: host.serverId },
{ label: "Status", value: snapshot?.connectionStatus ?? "not started" },
{
label: "Active connection",
value: snapshot?.activeConnection
? describeConnectionKind(snapshot.activeConnection.type)
: "none",
},
{ label: "Last online", value: snapshot?.lastOnlineAt ?? "never" },
{ label: "Last error", value: snapshot?.lastError ?? "none" },
{
label: "Agent directory",
value: snapshot?.agentDirectoryStatus ?? "unknown",
},
];
const connectionRows = host.connections.map((connection, index) => {
const probe = snapshot?.probeByConnectionId.get(connection.id) ?? null;
const isActive = snapshot?.activeConnectionId === connection.id;
return {
label: `Connection ${index + 1}`,
value: [
describeConnectionKind(connection.type),
isActive ? "active" : "inactive",
probe ? `probe=${probe.status}` : "probe=unknown",
probe?.status === "available" ? `latency=${Math.round(probe.latencyMs)}ms` : null,
]
.filter(Boolean)
.join(", "),
};
});
return formatDiagnosticSection(`Host: ${host.label}`, [...entries, ...connectionRows]);
}
export function formatServerInfoSection(serverInfo: ServerInfoStatusPayload | null): string {
if (!serverInfo) {
return formatDiagnosticSection("Server info", [{ label: "Status", value: "not received" }]);
}
const features = serverInfo.features ? Object.keys(serverInfo.features).sort() : [];
return formatDiagnosticSection("Server info", [
{ label: "Server ID", value: serverInfo.serverId },
{ label: "Hostname", value: serverInfo.hostname ?? "unknown" },
{ label: "Version", value: serverInfo.version ?? "unknown" },
{ label: "Features", value: features.length > 0 ? features.join(", ") : "none" },
]);
}
export function describeConnectionKind(type: HostConnection["type"] | string): string {
switch (type) {
case "directTcp":
return "direct TCP";
case "directSocket":
return "local socket";
case "directPipe":
return "local pipe";
case "relay":
return "relay";
default:
return "unknown";
}
}
export function redactAppDiagnosticReport(report: string, hosts: HostProfile[]): string {
let redacted = report;
for (const value of collectSensitiveHostValues(hosts)) {
redacted = redacted.split(value).join("[redacted]");
}
return redacted
.replace(/paseo:\/\/\S+/gi, "paseo://[redacted]")
.replace(
/([?&](?:password|token|secret|key|publicKey|daemonPublicKeyB64)=)[^&\s"']+/gi,
"$1[redacted]",
)
.replace(
/((?:password|token|secret|authorization|api[_-]?key|daemonPublicKeyB64|relayKey)\s*[:=]\s*)("[^"]+"|'[^']+'|[^\s,}]+)/gi,
"$1[redacted]",
);
}
function collectSensitiveHostValues(hosts: HostProfile[]): string[] {
const values = new Set<string>();
for (const host of hosts) {
for (const connection of host.connections) {
values.add(connection.id);
if (connection.type === "directTcp") {
values.add(connection.endpoint);
if (connection.password) values.add(connection.password);
} else if (connection.type === "relay") {
values.add(connection.relayEndpoint);
values.add(connection.daemonPublicKeyB64);
} else if (connection.type === "directSocket" || connection.type === "directPipe") {
values.add(connection.path);
}
}
}
return [...values].filter((value) => value.trim().length > 0);
}