fix: compact noisy cli health outputs
This commit is contained in:
@@ -240,6 +240,14 @@ function hasFlag(args: string[], name: string): boolean {
|
||||
return args.includes(name);
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
||||
}
|
||||
|
||||
function asArray(value: unknown): unknown[] {
|
||||
return Array.isArray(value) ? value : [];
|
||||
}
|
||||
|
||||
function parseJsonOption(raw: string, name: string): unknown {
|
||||
try {
|
||||
return JSON.parse(raw) as unknown;
|
||||
@@ -264,6 +272,14 @@ function requestBodyOption(args: string[]): unknown | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function assertKnownObservationOptions(args: string[], command: string): void {
|
||||
const flags = new Set(["--full", "--raw"]);
|
||||
for (const arg of args) {
|
||||
if (!arg.startsWith("--")) continue;
|
||||
if (!flags.has(arg)) throw new Error(`unsupported ${command} option: ${arg}`);
|
||||
}
|
||||
}
|
||||
|
||||
function methodOption(args: string[], hasBody = false): string {
|
||||
const method = (stringOption(args, "--method") ?? (hasBody ? "POST" : "GET")).toUpperCase();
|
||||
if (!["GET", "HEAD", "POST", "DELETE", "PUT", "PATCH"].includes(method)) throw new Error(`unsupported --method ${method}`);
|
||||
@@ -314,20 +330,148 @@ export function summarizeMicroserviceProxyResponse(response: unknown, args: stri
|
||||
};
|
||||
}
|
||||
|
||||
function compactStringList(value: unknown, limit = 8): Record<string, unknown> {
|
||||
const all = Array.from(new Set(asArray(value).map((item) => String(item ?? "")).filter(Boolean)));
|
||||
return {
|
||||
items: all.slice(0, limit),
|
||||
count: all.length,
|
||||
truncated: all.length > limit,
|
||||
omitted: Math.max(0, all.length - limit),
|
||||
};
|
||||
}
|
||||
|
||||
function compactRecordFields(record: Record<string, unknown> | null, keys: string[]): Record<string, unknown> | null {
|
||||
if (record === null) return null;
|
||||
const selected: Record<string, unknown> = {};
|
||||
for (const key of keys) {
|
||||
if (record[key] !== undefined) selected[key] = record[key];
|
||||
}
|
||||
return Object.keys(selected).length > 0 ? selected : null;
|
||||
}
|
||||
|
||||
function compactQueueHealth(value: unknown): Record<string, unknown> | null {
|
||||
const queue = asRecord(value);
|
||||
if (queue === null) return null;
|
||||
return {
|
||||
controlPlane: queue.controlPlane ?? null,
|
||||
defaultProviderId: queue.defaultProviderId ?? null,
|
||||
defaultModel: queue.defaultModel ?? null,
|
||||
counts: queue.counts ?? {},
|
||||
total: queue.total ?? null,
|
||||
queueCount: queue.queueCount ?? null,
|
||||
activeQueueIds: compactStringList(queue.activeQueueIds, 8),
|
||||
activeTaskIds: compactStringList(queue.activeTaskIds ?? queue.databaseActiveTaskIds, 8),
|
||||
queuedTaskIds: compactStringList(queue.queuedTaskIds, 8),
|
||||
databaseActiveTaskCount: queue.databaseActiveTaskCount ?? null,
|
||||
schedulerActiveRunSlotCount: queue.schedulerActiveRunSlotCount ?? queue.activeRunSlotCount ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function compactMicroserviceBody(body: unknown, serviceId: string): Record<string, unknown> | null {
|
||||
const record = asRecord(body);
|
||||
if (record === null) return null;
|
||||
const diagnostics = asRecord(record.executionDiagnostics) ?? asRecord(record.diagnostics);
|
||||
const devReady = asRecord(record.devReady);
|
||||
return {
|
||||
ok: record.ok ?? null,
|
||||
serviceId: record.serviceId ?? record.service ?? serviceId,
|
||||
service: record.service ?? null,
|
||||
role: record.role ?? null,
|
||||
status: record.status ?? null,
|
||||
healthy: record.healthy ?? null,
|
||||
mode: record.mode ?? null,
|
||||
environment: record.environment ?? null,
|
||||
version: record.version ?? record.gatewayVersion ?? null,
|
||||
target: record.target ?? record.k3sServiceId ?? null,
|
||||
updatedAt: record.updatedAt ?? record.observedAt ?? record.generatedAt ?? null,
|
||||
startedAt: record.startedAt ?? null,
|
||||
taskCount: record.taskCount ?? null,
|
||||
schemaReady: record.schemaReady ?? null,
|
||||
queue: compactQueueHealth(record.queue),
|
||||
resourceBudget: compactRecordFields(asRecord(record.resourceBudget), [
|
||||
"targetMemoryMb",
|
||||
"mgrPoolMax",
|
||||
"tracePoolMax",
|
||||
"noRunnerDependencies",
|
||||
"noDockerSocket",
|
||||
"noPlaywright",
|
||||
"rustRewrite",
|
||||
]),
|
||||
topLevelKeys: compactStringList(Object.keys(record), 16),
|
||||
devReady: devReady === null ? null : {
|
||||
ok: devReady.ok ?? null,
|
||||
missingTools: compactStringList(devReady.missingTools, 8),
|
||||
skills: devReady.skills ?? null,
|
||||
},
|
||||
executionDiagnostics: diagnostics === null ? null : {
|
||||
state: diagnostics.state ?? null,
|
||||
effectiveLiveness: diagnostics.effectiveLiveness ?? null,
|
||||
recommendedAction: diagnostics.recommendedAction ?? null,
|
||||
splitBrainLive: diagnostics.splitBrainLive ?? null,
|
||||
schedulerActiveRunSlotCount: diagnostics.schedulerActiveRunSlotCount ?? null,
|
||||
databaseActiveTaskCount: diagnostics.databaseActiveTaskCount ?? null,
|
||||
heartbeatFreshTaskIds: compactStringList(diagnostics.heartbeatFreshTaskIds, 8),
|
||||
heartbeatRiskTaskIds: compactStringList(diagnostics.heartbeatRiskTaskIds, 8),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeMicroserviceObservation(action: string, serviceId: string, response: unknown, args: string[]): unknown {
|
||||
if (hasFlag(args, "--full") || hasFlag(args, "--raw")) return response;
|
||||
const record = asRecord(response);
|
||||
if (record === null) return response;
|
||||
const body = "body" in record ? record.body : response;
|
||||
const bodyBytes = jsonByteLength(body);
|
||||
const summary = compactMicroserviceBody(body, serviceId);
|
||||
const includePreview = summary === null || bodyBytes <= 20_000;
|
||||
return {
|
||||
upstream: {
|
||||
ok: record.ok ?? null,
|
||||
status: record.status ?? null,
|
||||
exitCode: record.exitCode ?? null,
|
||||
},
|
||||
microservice: {
|
||||
action,
|
||||
id: serviceId,
|
||||
summary,
|
||||
bodyBytes,
|
||||
bodyOmitted: true,
|
||||
outputPolicy: {
|
||||
default: "compact-health-summary",
|
||||
full: `bun scripts/cli.ts microservice ${action} ${serviceId} --full`,
|
||||
raw: `bun scripts/cli.ts microservice ${action} ${serviceId} --raw`,
|
||||
proxyRaw: serviceId === "code-queue" && action === "health" ? "bun scripts/cli.ts microservice proxy code-queue /health --raw --full" : null,
|
||||
},
|
||||
...(includePreview
|
||||
? { bodyPreview: previewJson(body, { maxDepth: 2, maxArrayItems: 2, maxObjectKeys: 8, maxStringLength: 160 }) }
|
||||
: {
|
||||
bodyPreviewOmitted: true,
|
||||
bodyPreviewHint: "Large body preview omitted because compact summary is available; use --full or --raw for complete evidence.",
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function runMicroserviceCommand(_config: UniDeskConfig, args: string[]): Promise<unknown> {
|
||||
const [action = "list", idArg, pathArg] = args;
|
||||
if (action === "list") return coreInternalFetch("/api/microservices");
|
||||
if (action === "status") {
|
||||
const id = requireId(idArg, "microservice status");
|
||||
return coreInternalFetch(`/api/microservices/${encodeId(id)}/status`);
|
||||
const optionArgs = args.slice(2);
|
||||
assertKnownObservationOptions(optionArgs, "microservice status");
|
||||
return summarizeMicroserviceObservation(action, id, coreInternalFetch(`/api/microservices/${encodeId(id)}/status`), optionArgs);
|
||||
}
|
||||
if (action === "health") {
|
||||
const id = requireId(idArg, "microservice health");
|
||||
return coreInternalFetch(`/api/microservices/${encodeId(id)}/health`);
|
||||
const optionArgs = args.slice(2);
|
||||
assertKnownObservationOptions(optionArgs, "microservice health");
|
||||
return summarizeMicroserviceObservation(action, id, coreInternalFetch(`/api/microservices/${encodeId(id)}/health`), optionArgs);
|
||||
}
|
||||
if (action === "diagnostics") {
|
||||
const id = requireId(idArg, "microservice diagnostics");
|
||||
return coreInternalFetch(`/api/microservices/${encodeId(id)}/diagnostics`);
|
||||
const optionArgs = args.slice(2);
|
||||
assertKnownObservationOptions(optionArgs, "microservice diagnostics");
|
||||
return summarizeMicroserviceObservation(action, id, coreInternalFetch(`/api/microservices/${encodeId(id)}/diagnostics`), optionArgs);
|
||||
}
|
||||
if (action === "tunnel-self-test") {
|
||||
const id = requireId(idArg, "microservice tunnel-self-test");
|
||||
|
||||
Reference in New Issue
Block a user