diff --git a/scripts/src/platform-infra-observability/actions.ts b/scripts/src/platform-infra-observability/actions.ts index 6218c01c..a580f763 100644 --- a/scripts/src/platform-infra-observability/actions.ts +++ b/scripts/src/platform-infra-observability/actions.ts @@ -25,6 +25,7 @@ import { readObservabilityConfig } from "./config"; import { applyScript, statusScript } from "./search-script"; import { compactStatus, configSummary, manifestObjectSummary, policyChecks, statusSummary, targetSummary } from "./summary"; import { renderManifest } from "./trace-script"; +import { formatTable, shortenEnd, textValue } from "./manifest"; import { apiPathField, configLabel, kubernetesNameField, stringField } from "./types"; export function parseStatusEndpoint(record: Record, index: number): StatusEndpoint { @@ -109,12 +110,20 @@ export async function apply(config: UniDeskConfig, options: ApplyOptions): Promi }; } -export async function status(config: UniDeskConfig, options: CommonOptions): Promise> { +export async function status(config: UniDeskConfig, options: CommonOptions): Promise | RenderedCliResult> { const observability = readObservabilityConfig(); const target = resolveTarget(observability, options.targetId); const result = await capture(config, target.route, ["sh"], statusScript(observability, target, options.full)); const parsed = parseJsonOutput(result.stdout); const summary = parsed === null ? null : statusSummary(parsed); + if (!options.full && !options.raw) { + return renderStatusTable({ + ok: result.exitCode === 0 && summary?.ready === true, + target, + summary, + remote: parsed === null ? compactCapture(result, { full: false }) : null, + }); + } return { ok: result.exitCode === 0 && summary?.ready === true, action: "platform-infra-observability-status", @@ -131,6 +140,66 @@ export async function status(config: UniDeskConfig, options: CommonOptions): Pro }; } +function renderStatusTable(input: { + ok: boolean; + target: ObservabilityTarget; + summary: Record | null; + remote: Record | null; +}): RenderedCliResult { + const deployments = recordList(input.summary?.deployments).map((item) => [ + shortenEnd(textValue(item.name), 34), + textValue(item.ready), + `${textValue(item.availableReplicas)}/${textValue(item.replicas)}`, + ]); + const pods = recordList(input.summary?.pods).slice(0, 12).map((item) => [ + shortenEnd(textValue(item.name), 44), + textValue(item.phase), + textValue(item.ready), + shortenEnd(textValue(item.reason), 28), + ]); + const probes = recordList(input.summary?.probes).map((item) => [ + shortenEnd(textValue(item.name), 24), + textValue(item.ok), + shortenEnd(textValue(item.service), 26), + shortenEnd(textValue(item.path), 38), + ]); + const lines = [ + `platform-infra observability status (${input.ok ? "ok" : "not-ok"})`, + "", + `target=${input.target.id} namespace=${input.target.namespace} ready=${textValue(input.summary?.ready)} route=${input.target.route}`, + "", + "Deployments:", + formatTable(["NAME", "READY", "AVAILABLE"], deployments.length > 0 ? deployments : [["-", "-", "-"]]), + "", + "Pods:", + formatTable(["NAME", "PHASE", "READY", "REASON"], pods.length > 0 ? pods : [["-", "-", "-", "-"]]), + "", + "Probes:", + formatTable(["NAME", "OK", "SERVICE", "PATH"], probes.length > 0 ? probes : [["-", "-", "-", "-"]]), + ]; + if (input.remote !== null) { + lines.push("", "Remote:"); + lines.push(` ${shortenEnd(JSON.stringify(input.remote), 240)}`); + } + lines.push("", "Next:"); + lines.push(` bun scripts/cli.ts platform-infra observability validate --target ${input.target.id}`); + lines.push(` bun scripts/cli.ts platform-infra observability status --target ${input.target.id} --full`); + lines.push("", "Disclosure:"); + lines.push(" default view is a bounded table; use --full for compact JSON or --raw for backend/parser debugging."); + return { + ok: input.ok, + command: "platform-infra observability status", + contentType: "text/plain", + renderedText: lines.join("\n"), + }; +} + +function recordList(value: unknown): Record[] { + return Array.isArray(value) + ? value.filter((item): item is Record => typeof item === "object" && item !== null && !Array.isArray(item)) + : []; +} + export async function validate(config: UniDeskConfig, options: CommonOptions): Promise> { const observability = readObservabilityConfig(); const target = resolveTarget(observability, options.targetId); diff --git a/scripts/src/platform-infra-observability/apply-status-scripts.ts b/scripts/src/platform-infra-observability/apply-status-scripts.ts index 5883c5bf..3f612cd9 100644 --- a/scripts/src/platform-infra-observability/apply-status-scripts.ts +++ b/scripts/src/platform-infra-observability/apply-status-scripts.ts @@ -63,6 +63,7 @@ export function compactDiagnoseCodeAgentResult(value: unknown): Record { "bun scripts/cli.ts platform-infra observability search --target D518 --grep 'no rollout found' [--lookback-minutes 360] [--candidate-limit 80] [--limit 20] [--full|--raw]", "bun scripts/cli.ts platform-infra observability search --target D518 --path /v1/workbench/sessions --status 502 [--lookback-minutes 120] [--full|--raw]", "bun scripts/cli.ts platform-infra observability diagnose-code-agent --target D518 --business-trace-id [--full|--raw]", - "bun scripts/cli.ts platform-infra observability diagnose-code-agent --target D518 --run-id [--command-id ] [--runner-job-id ] [--full|--raw]", + "bun scripts/cli.ts platform-infra observability diagnose-code-agent --target D518 --run-id [--command-id ] [--session-id ] [--runner-job-id ] [--full|--raw]", ], boundary: "Prometheus remains the metrics source; this command owns only platform-infra OTel Collector, trace backend readiness, and trace lookup.", }; @@ -222,6 +222,7 @@ export function parseDiagnoseCodeAgentOptions(args: string[]): DiagnoseCodeAgent let traceId: string | null = null; let runId: string | null = null; let commandId: string | null = null; + let sessionId: string | null = null; let runnerJobId: string | null = null; let limit = 40; let candidateLimit = 300; @@ -252,6 +253,12 @@ export function parseDiagnoseCodeAgentOptions(args: string[]): DiagnoseCodeAgent if (!/^cmd_[A-Za-z0-9_-]+$/u.test(value)) throw new Error(`${arg} must look like cmd_`); commandId = value; index += 1; + } else if (arg === "--session-id" || arg === "--session") { + const value = args[index + 1]; + if (value === undefined || value.startsWith("--")) throw new Error(`${arg} requires a value`); + if (!/^ses_[A-Za-z0-9_-]+$/u.test(value)) throw new Error(`${arg} must look like ses_`); + sessionId = value; + index += 1; } else if (arg === "--runner-job-id" || arg === "--runner-job" || arg === "--runnerjob") { const value = args[index + 1]; if (value === undefined || value.startsWith("--")) throw new Error(`${arg} requires a value`); @@ -290,5 +297,5 @@ export function parseDiagnoseCodeAgentOptions(args: string[]): DiagnoseCodeAgent if (businessTraceId === null && traceId === null && runId === null && commandId === null && runnerJobId === null) { throw new Error("observability diagnose-code-agent requires --business-trace-id , --trace-id , --run-id , --command-id , or --runner-job-id "); } - return { ...parseCommonOptions(commonArgs), businessTraceId, traceId, runId, commandId, runnerJobId, limit, candidateLimit, lookbackMinutes }; + return { ...parseCommonOptions(commonArgs), businessTraceId, traceId, runId, commandId, sessionId, runnerJobId, limit, candidateLimit, lookbackMinutes }; } diff --git a/scripts/src/platform-infra-observability/render.ts b/scripts/src/platform-infra-observability/render.ts index 1bfb8efc..0a46060a 100644 --- a/scripts/src/platform-infra-observability/render.ts +++ b/scripts/src/platform-infra-observability/render.ts @@ -209,12 +209,13 @@ function diagnoseTraceSearchFilters(options: DiagnoseCodeAgentOptions): string[] if (options.businessTraceId !== null) filters.push(`.traceId = ${JSON.stringify(options.businessTraceId)}`); if (options.runId !== null) filters.push(`.runId = ${JSON.stringify(options.runId)}`); if (options.commandId !== null) filters.push(`.commandId = ${JSON.stringify(options.commandId)}`); + if (options.sessionId !== null) filters.push(`.sessionId = ${JSON.stringify(options.sessionId)}`); if (options.runnerJobId !== null) filters.push(`.runnerJobId = ${JSON.stringify(options.runnerJobId)}`); return filters; } function diagnoseTraceSearchMode(options: DiagnoseCodeAgentOptions): string { - if (options.businessTraceId !== null && options.runId === null && options.commandId === null && options.runnerJobId === null) return "business-trace-id"; + if (options.businessTraceId !== null && options.runId === null && options.commandId === null && options.sessionId === null && options.runnerJobId === null) return "business-trace-id"; return "trace-attribute-query"; } @@ -245,9 +246,11 @@ export async function diagnoseCodeAgent(config: UniDeskConfig, options: Diagnose traceId: options.traceId, runId: options.runId, commandId: options.commandId, + sessionId: options.sessionId, runnerJobId: options.runnerJobId, mode: diagnoseTraceSearchMode(options), tempoQuery, + queryClauses: searchFilters, path: searchPath, lookbackMinutes: options.lookbackMinutes, candidateLimit: options.candidateLimit, @@ -630,6 +633,15 @@ export function renderDiagnoseCodeAgentTable(input: { shortenEnd(textValue(candidate.summary ?? candidate.label), 80), ]); const httpRows = httpTableRows(http); + const queryClauses = asArray(input.query.queryClauses).map((item) => textValue(item)).filter((item) => item !== "-"); + const requestedRunId = textValue(input.query.runId); + const requestedCommandId = textValue(input.query.commandId); + const requestedSessionId = textValue(input.query.sessionId); + const requestedRunnerJobId = textValue(input.query.runnerJobId); + const observedRunId = textValue(identity?.runId); + const observedCommandId = textValue(identity?.commandId); + const observedSessionId = textValue(identity?.sessionId); + const observedRunnerJobId = textValue(identity?.runnerJobId); const lines = [ `platform-infra observability diagnose-code-agent (${input.ok ? "ok" : "not-ok"})`, "", @@ -637,8 +649,10 @@ export function renderDiagnoseCodeAgentTable(input: { "", "Identity:", ` businessTraceId=${textValue(mapping?.businessTraceId ?? input.query.businessTraceId)} otelTraceId=${traceId}`, - ` queryMode=${textValue(mapping?.mode ?? input.query.mode)} tempoQuery=${shortenMiddle(textValue(mapping?.searchQuery ?? input.query.tempoQuery), 80)}`, - ` runId=${textValue(identity?.runId)} commandId=${textValue(identity?.commandId)} runnerJobId=${textValue(identity?.runnerJobId)} runnerId=${textValue(identity?.runnerId)}`, + ` queryMode=${textValue(mapping?.mode ?? input.query.mode)} tempoQuery=${shortenEnd(textValue(mapping?.searchQuery ?? input.query.tempoQuery), 180)}`, + ` queryClauses=${queryClauses.length > 0 ? queryClauses.join(" ; ") : "-"}`, + ` requested runId=${requestedRunId} commandId=${requestedCommandId} sessionId=${requestedSessionId} runnerJobId=${requestedRunnerJobId}`, + ` observed runId=${observedRunId} commandId=${observedCommandId} sessionId=${observedSessionId} runnerJobId=${observedRunnerJobId} runnerId=${textValue(identity?.runnerId)}`, ` backendProfile=${textValue(identity?.backendProfile)} sourceCommit=${shortenMiddle(textValue(identity?.sourceCommit), 20)}`, "", "Root causes:", diff --git a/scripts/src/platform-infra-observability/types.ts b/scripts/src/platform-infra-observability/types.ts index 06c9cc56..3fc94b97 100644 --- a/scripts/src/platform-infra-observability/types.ts +++ b/scripts/src/platform-infra-observability/types.ts @@ -159,6 +159,7 @@ export interface DiagnoseCodeAgentOptions extends CommonOptions { traceId: string | null; runId: string | null; commandId: string | null; + sessionId: string | null; runnerJobId: string | null; limit: number; candidateLimit: number;