Files
pikasTech-unidesk/scripts/src/hwlab-node/web-probe-observe.ts
T
2026-06-26 12:48:32 +00:00

2128 lines
159 KiB
TypeScript

// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. web-probe-observe module for scripts/src/hwlab-node-impl.ts.
// Moved mechanically from scripts/src/hwlab-node-impl.ts:7427-9000 for #903.
// SPEC: PJ2026-01060505 Workbench Performance draft-2026-06-17-p0.
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-25-p0-web-probe-sentinel.
// SPEC: PJ2026-01060508 Web哨兵 draft-2026-06-26-p9-multi-web-probe-sentinel.
// Responsibility: YAML-first node/lane operations, including Workbench observability control commands.
import { createHash, randomBytes } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { repoRoot, rootPath, type Config } from "../config";
import { runCommand, type CommandResult } from "../command";
import { startJob } from "../jobs";
import { classifySshTcpPoolFailure } from "../ssh";
import { HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, hwlabNodeControlPlaneInfraHelp, runHwlabNodeControlPlaneInfra } from "../hwlab-node-control-plane";
import { hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, hwlabRuntimeLaneSpecForNode, hwlabRuntimeNodeIds, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec, type HwlabRuntimeObservabilityRecordingRuleSpec, type HwlabRuntimeObservabilitySpec, type HwlabRuntimeObservabilityWarningAlertSpec, type HwlabRuntimePublicExposureSpec, type HwlabRuntimeWebProbeAlertThresholdsSpec, type HwlabRuntimeWebProbeProjectManagementSpec } from "../hwlab-node-lanes";
import { nodeWebProbeScriptRunnerSource } from "../hwlab-node-web-probe-runner-source";
import { nodeWebObserveAnalyzerSource } from "../hwlab-node-web-observe-analyzer-source";
import { nodeWebObserveRunnerSource } from "../hwlab-node-web-observe-runner-source";
import { nodeWebObserveCollectViewNodeScript, parseNodeWebProbeObserveCollectView, type NodeWebProbeObserveCollectView } from "../hwlab-node-web-observe-collect";
import { withWebObserveCollectRendered, withWebObserveCommandRendered, withWebObserveStatusRendered } from "../hwlab-node-web-observe-render";
import { buildWebObserveWrapperForObserveOptions, webObserveWrapperStateDirFromStatus } from "../hwlab-node-web-observe-wrapper";
import { renderWebObserveWrapperContract } from "../hwlab-node-web-observe-wrapper-render";
import { runWebProbeSentinelCommand, type WebProbeSentinelOptions, type WebProbeSentinelReportView } from "../hwlab-node-web-sentinel-cicd";
import { hwlabNodeHelp, hwlabNodeObservabilityHelp, hwlabNodeWebProbeHelp } from "../hwlab-node-help";
import { compactWebProbeResult, compactWebProbeScriptResult } from "../hwlab-node-web-probe-summary";
import { nodeObservabilityRecordingRuleExpression, nodeObservabilityRecordingRuleSummaries, nodeObservabilityWarningAlertExpression, nodeObservabilityWarningAlertSummaries } from "../hwlab-node-observability-promql";
import { runDelegatedHwlabNodeCommand, type DelegatedNodeDomain } from "../hwlab-node-transport";
import type { RenderedCliResult } from "../output";
import type { BootstrapAdminPasswordMaterial, NodeWebProbeObserveCommandType, NodeWebProbeObserveOptions, NodeWebProbeOptions, NodeWebProbeRunOptions, NodeWebProbeScreenshotOptions, NodeWebProbeSentinelOptions, RuntimeSecretSpec, WebObserveIndexEntry, WebProbeBrowserProxyMode } from "./entry";
import { runTransWorkspaceStdinScript, runtimeSecretSpec } from "./public-exposure";
import { transPath } from "./runtime-common";
import { assertLane, assertNodeId, compactCommandResult, compactCommandResultRedacted, compactCommandResultWithStdoutTail, nullableRecord, optionValue, parseJsonObject, positiveIntegerOption, record, requiredOption, shellQuote } from "./utils";
import { nodeWebObserveResolveStateDirShell, recoverWebObserveAnalyzeTurnDetails, renderWebObserveStartResult, renderWebProbeRunResult, upsertWebObserveIndexEntry, webObserveCommandLabel, webObserveIdFromOptions, webObserveIdFromStatus, webObserveIndexEntryFromOptions, webObserveNextCommands, withWebObserveAnalyzeRendered, withWebObserveShortcuts } from "./web-observe-render";
import { commandSummaryForOutput, isSafeWebObserveArchivePrefix, isSafeWebObserveCollectFile, isSafeWebObserveFindingId, isSafeWebObserveJobId, isSafeWebObserveStateDir, isSafeWebObserveTraceId, nodeWebObserveCollectNodeScript, nodeWebObserveForceStopNodeScript, nodeWebObserveStatusNodeScript, nodeWebObserveWaitCommandShell, runNodeWebProbeScript, safeWebObserveSegment, safeWebObserveTargetSegment } from "./web-observe-scripts";
import { displayRepoPath, readBootstrapAdminPasswordMaterial, sleepSync } from "./web-probe";
export function parseNodeWebProbeSentinelOptions(args: string[]): NodeWebProbeSentinelOptions {
const [sentinelActionRaw] = args;
if (
sentinelActionRaw !== "plan"
&& sentinelActionRaw !== "status"
&& sentinelActionRaw !== "image"
&& sentinelActionRaw !== "control-plane"
&& sentinelActionRaw !== "validate"
&& sentinelActionRaw !== "maintenance"
&& sentinelActionRaw !== "report"
) {
throw new Error("web-probe sentinel usage: sentinel plan|status|image|control-plane|validate|maintenance|report --node NODE --lane vNN [--dry-run|--confirm]");
}
assertKnownOptions(args, new Set([
"--node",
"--lane",
"--timeout-seconds",
"--release-id",
"--reason",
"--view",
"--run",
"--run-id",
"--trace-id",
"--sample-seq",
"--sentinel",
"--sentinel-id",
]), new Set(["--dry-run", "--confirm", "--wait", "--quick-verify", "--raw", "--latest"]));
const node = requiredOption(args, "--node");
assertNodeId(node);
const lane = requiredOption(args, "--lane");
assertLane(lane);
const sentinelId = optionValue(args, "--sentinel") ?? optionValue(args, "--sentinel-id") ?? null;
if (sentinelId !== null && !/^[a-z0-9][a-z0-9-]{1,80}$/u.test(sentinelId)) throw new Error(`--sentinel must be a stable lowercase sentinel id, got ${sentinelId}`);
if (!isHwlabRuntimeLane(lane)) throw new Error(`web-probe only supports HWLAB runtime lanes, got ${lane}`);
const confirm = args.includes("--confirm");
const dryRun = args.includes("--dry-run");
if (confirm && dryRun) throw new Error("web-probe sentinel accepts only one of --confirm or --dry-run");
const timeoutSeconds = positiveIntegerOption(args, "--timeout-seconds", 900, 3600);
let sentinel: WebProbeSentinelOptions;
if (sentinelActionRaw === "plan" || sentinelActionRaw === "status") {
sentinel = { kind: "config", action: sentinelActionRaw, node, lane, sentinelId, dryRun };
} else if (sentinelActionRaw === "image") {
const imageAction = args[1];
if (imageAction !== "status" && imageAction !== "build") throw new Error("web-probe sentinel image usage: image status|build --node NODE --lane vNN [--dry-run|--confirm]");
sentinel = { kind: "image", action: imageAction, node, lane, sentinelId, dryRun: imageAction === "build" ? dryRun || !confirm : dryRun, confirm, wait: args.includes("--wait"), timeoutSeconds };
} else if (sentinelActionRaw === "control-plane") {
const controlPlaneAction = args[1];
if (controlPlaneAction !== "plan" && controlPlaneAction !== "apply" && controlPlaneAction !== "status" && controlPlaneAction !== "trigger-current") {
throw new Error("web-probe sentinel control-plane usage: control-plane plan|apply|status|trigger-current --node NODE --lane vNN [--dry-run|--confirm]");
}
sentinel = { kind: "control-plane", action: controlPlaneAction, node, lane, sentinelId, dryRun: controlPlaneAction === "apply" || controlPlaneAction === "trigger-current" ? dryRun || !confirm : dryRun, confirm, wait: args.includes("--wait"), timeoutSeconds };
} else if (sentinelActionRaw === "maintenance") {
const maintenanceAction = args[1];
if (maintenanceAction !== "status" && maintenanceAction !== "start" && maintenanceAction !== "stop") {
throw new Error("web-probe sentinel maintenance usage: maintenance status|start|stop --node NODE --lane vNN [--dry-run|--confirm]");
}
sentinel = {
kind: "maintenance",
action: maintenanceAction,
node,
lane,
sentinelId,
dryRun: maintenanceAction === "status" ? dryRun : dryRun || !confirm,
confirm,
wait: args.includes("--wait"),
timeoutSeconds,
releaseId: optionValue(args, "--release-id") ?? null,
reason: optionValue(args, "--reason") ?? null,
quickVerify: maintenanceAction === "stop" || args.includes("--quick-verify"),
};
} else if (sentinelActionRaw === "validate") {
sentinel = { kind: "validate", action: "validate", node, lane, sentinelId, dryRun, confirm, wait: args.includes("--wait"), timeoutSeconds, quickVerify: args.includes("--quick-verify") };
} else {
const view = parseWebProbeSentinelReportView(optionValue(args, "--view") ?? "summary");
const latest = args.includes("--latest");
const runId = optionValue(args, "--run") ?? optionValue(args, "--run-id") ?? null;
if (latest && runId !== null) throw new Error("web-probe sentinel report accepts --latest or --run/--run-id, not both");
const sampleSeqRaw = optionValue(args, "--sample-seq") ?? null;
const sampleSeq = sampleSeqRaw === null ? null : Number(sampleSeqRaw);
if (sampleSeq !== null && (!Number.isInteger(sampleSeq) || sampleSeq < 1)) throw new Error("web-probe sentinel report --sample-seq must be a positive integer");
sentinel = {
kind: "report",
action: "report",
node,
lane,
sentinelId,
view,
runId,
latest,
traceId: optionValue(args, "--trace-id") ?? null,
sampleSeq,
raw: args.includes("--raw"),
timeoutSeconds,
};
}
return {
action: "sentinel",
sentinel,
node,
lane,
};
}
function parseWebProbeSentinelReportView(value: string): WebProbeSentinelReportView {
if (value === "summary" || value === "turn-summary" || value === "findings" || value === "trace-frame" || value === "auth-session-switch-summary") return value;
throw new Error(`web-probe sentinel report --view must be summary, turn-summary, findings, trace-frame, or auth-session-switch-summary; got ${value}`);
}
export function normalizeNodeWebProbeObserveArgs(args: string[]): { args: string[]; id: string | null } {
const [observeActionRaw, maybeId, ...rest] = args;
if (observeActionRaw !== "start" && maybeId !== undefined && !maybeId.startsWith("--")) {
if (!isSafeWebObserveJobId(maybeId)) throw new Error(`unsafe web-probe observe id: ${maybeId}`);
return { args: [observeActionRaw, ...rest], id: maybeId };
}
return { args, id: null };
}
export function parseNodeWebProbeObserveOptions(
args: string[],
node: string,
lane: string,
spec: HwlabRuntimeLaneSpec,
observeId: string | null,
indexed: WebObserveIndexEntry | null,
): NodeWebProbeObserveOptions {
const [observeActionRaw] = args;
if (
observeActionRaw !== "start"
&& observeActionRaw !== "status"
&& observeActionRaw !== "command"
&& observeActionRaw !== "stop"
&& observeActionRaw !== "collect"
&& observeActionRaw !== "analyze"
) {
throw new Error("web-probe observe usage: observe start --node NODE --lane vNN [...]; observe status|command|stop|collect|analyze <id> [...]");
}
assertKnownOptions(args.slice(1), new Set([
"--node",
"--lane",
"--url",
"--target-path",
"--viewport",
"--browser-proxy-mode",
"--sample-interval-ms",
"--screenshot-interval-ms",
"--observer-refresh-interval-ms",
"--max-samples",
"--command-timeout-seconds",
"--wait-ms",
"--tail-lines",
"--max-files",
"--view",
"--file",
"--finding",
"--grep",
"--trace-id",
"--sample-seq",
"--timestamp",
"--turn",
"--command-id",
"--window-ms",
"--compact-raw",
"--archive-prefix",
"--tail-samples",
"--state-dir",
"--job-id",
"--type",
"--text",
"--path",
"--label",
"--session-id",
"--provider",
"--account-id",
"--account",
"--from-account-id",
"--to-account-id",
"--after-round",
"--severity",
"--alternate-session-strategy",
"--expected-sentinel-range",
"--finding-id",
"--source-id",
"--file-ref",
"--filename",
"--task-ref",
"--task",
"--task-id",
"--field",
"--link",
"--title",
"--body",
"--status",
"--hwpod-id",
"--node-id",
"--workspace-root",
"--workspace-root-ref",
"--root",
]), new Set(["--force", "--full", "--raw", "--text-stdin", "--require-composer-ready", "--blocking", "--non-blocking"]));
const commandTypeRaw = optionValue(args, "--type") ?? null;
const commandType = commandTypeRaw === null ? null : parseNodeWebProbeObserveCommandType(commandTypeRaw);
const stateDir = optionValue(args, "--state-dir") ?? indexed?.stateDir ?? null;
const jobId = optionValue(args, "--job-id") ?? observeId ?? indexed?.id ?? null;
if (stateDir !== null && !isSafeWebObserveStateDir(stateDir)) throw new Error(`unsafe web-probe observe --state-dir: ${stateDir}`);
if (jobId !== null && !isSafeWebObserveJobId(jobId)) throw new Error(`unsafe web-probe observe --job-id: ${jobId}`);
const collectView = parseNodeWebProbeObserveCollectView(optionValue(args, "--view") ?? "files");
const collectFile = optionValue(args, "--file") ?? null;
if (collectFile !== null && !isSafeWebObserveCollectFile(collectFile)) throw new Error(`unsafe web-probe observe --file: ${collectFile}`);
const collectFinding = optionValue(args, "--finding") ?? null;
if (collectFinding !== null && !isSafeWebObserveFindingId(collectFinding)) throw new Error(`unsafe web-probe observe --finding: ${collectFinding}`);
const collectGrep = optionValue(args, "--grep") ?? null;
if (collectGrep !== null && (collectGrep.includes("\0") || collectGrep.length > 200)) throw new Error("unsafe web-probe observe --grep: expected 1-200 non-NUL chars");
const collectTraceId = optionValue(args, "--trace-id") ?? null;
if (collectTraceId !== null && !isSafeWebObserveTraceId(collectTraceId)) throw new Error(`unsafe web-probe observe --trace-id: ${collectTraceId}`);
const collectSampleSeqRaw = optionValue(args, "--sample-seq") ?? null;
const collectSampleSeq = collectSampleSeqRaw === null ? null : Number(collectSampleSeqRaw);
if (collectSampleSeq !== null && (!Number.isInteger(collectSampleSeq) || collectSampleSeq < 1)) throw new Error("unsafe web-probe observe --sample-seq: expected positive integer");
const collectTimestamp = optionValue(args, "--timestamp") ?? null;
if (collectTimestamp !== null && (collectTimestamp.includes("\0") || collectTimestamp.length > 80 || !Number.isFinite(Date.parse(collectTimestamp)))) throw new Error("unsafe web-probe observe --timestamp: expected parseable timestamp");
const collectTurnRaw = optionValue(args, "--turn") ?? null;
const collectTurn = collectTurnRaw === null ? null : Number(collectTurnRaw);
if (collectTurn !== null && (!Number.isInteger(collectTurn) || collectTurn < 1)) throw new Error("unsafe web-probe observe --turn: expected positive integer");
const collectCommandId = optionValue(args, "--command-id") ?? null;
if (collectCommandId !== null && (!/^[A-Za-z0-9_.:-]+$/u.test(collectCommandId) || collectCommandId.length > 120)) throw new Error("unsafe web-probe observe --command-id: expected 1-120 safe command id chars");
const collectWindowMsRaw = optionValue(args, "--window-ms") ?? null;
const collectWindowMs = collectWindowMsRaw === null ? null : Number(collectWindowMsRaw);
if (collectWindowMs !== null && (!Number.isInteger(collectWindowMs) || collectWindowMs < 1000 || collectWindowMs > 86_400_000)) throw new Error("unsafe web-probe observe --window-ms: expected integer 1000-86400000");
const analyzeArchivePrefix = optionValue(args, "--archive-prefix") ?? null;
if (analyzeArchivePrefix !== null && !isSafeWebObserveArchivePrefix(analyzeArchivePrefix)) throw new Error(`unsafe web-probe observe --archive-prefix: ${analyzeArchivePrefix}`);
const analyzeTailSamplesRaw = optionValue(args, "--tail-samples") ?? null;
const analyzeTailSamples = analyzeTailSamplesRaw === null ? null : Number(analyzeTailSamplesRaw);
if (analyzeTailSamples !== null && (!Number.isInteger(analyzeTailSamples) || analyzeTailSamples < 0)) {
throw new Error("unsafe web-probe observe --tail-samples: expected a non-negative integer; use 0 for all samples");
}
if (observeActionRaw !== "start" && stateDir === null && jobId === null) {
throw new Error("web-probe observe status|command|stop|collect|analyze requires --state-dir or --job-id");
}
const commandTextOption = optionValue(args, "--text") ?? null;
const commandTextFromStdin = args.includes("--text-stdin");
if (commandTextFromStdin && observeActionRaw !== "command") {
throw new Error("web-probe observe --text-stdin is only supported for observe command");
}
if (commandTextFromStdin && commandTextOption !== null) {
throw new Error("web-probe observe command accepts either --text or --text-stdin, not both");
}
const commandText = commandTextFromStdin ? readFileSync(0, "utf8") : commandTextOption;
const commandSourceId = optionValue(args, "--source-id") ?? null;
const commandAccountId = optionValue(args, "--account-id") ?? optionValue(args, "--account") ?? null;
const commandFromAccountId = optionValue(args, "--from-account-id") ?? null;
const commandToAccountId = optionValue(args, "--to-account-id") ?? null;
const commandFileRef = optionValue(args, "--file-ref") ?? null;
const commandFilename = optionValue(args, "--filename") ?? null;
const commandTaskRef = optionValue(args, "--task-ref") ?? null;
const commandTaskId = optionValue(args, "--task-id") ?? optionValue(args, "--task") ?? null;
const commandField = optionValue(args, "--field") ?? null;
const commandLink = optionValue(args, "--link") ?? null;
const commandTitle = optionValue(args, "--title") ?? null;
const commandBody = optionValue(args, "--body") ?? null;
const commandStatus = optionValue(args, "--status") ?? null;
const commandHwpodId = optionValue(args, "--hwpod-id") ?? null;
const commandNodeId = optionValue(args, "--node-id") ?? null;
const commandWorkspaceRoot = optionValue(args, "--workspace-root") ?? optionValue(args, "--workspace-root-ref") ?? null;
const commandRoot = optionValue(args, "--root") ?? null;
const commandAfterRoundRaw = optionValue(args, "--after-round") ?? null;
const commandAfterRound = commandAfterRoundRaw === null ? null : Number(commandAfterRoundRaw);
if (commandAfterRound !== null && (!Number.isInteger(commandAfterRound) || commandAfterRound < 0 || commandAfterRound > 1000)) {
throw new Error("unsafe web-probe observe --after-round: expected integer 0-1000");
}
const commandSeverity = optionValue(args, "--severity") ?? null;
const commandAlternateSessionStrategy = optionValue(args, "--alternate-session-strategy") ?? null;
const commandExpectedSentinelRange = optionValue(args, "--expected-sentinel-range") ?? null;
const commandFindingId = optionValue(args, "--finding-id") ?? null;
const commandBlocking = args.includes("--blocking") ? true : args.includes("--non-blocking") ? false : null;
for (const [label, value] of [
["--severity", commandSeverity],
["--alternate-session-strategy", commandAlternateSessionStrategy],
["--expected-sentinel-range", commandExpectedSentinelRange],
["--finding-id", commandFindingId],
["--source-id", commandSourceId],
["--account-id/--account", commandAccountId],
["--from-account-id", commandFromAccountId],
["--to-account-id", commandToAccountId],
["--file-ref", commandFileRef],
["--filename", commandFilename],
["--task-ref", commandTaskRef],
["--task/--task-id", commandTaskId],
["--field", commandField],
["--link", commandLink],
["--title", commandTitle],
["--body", commandBody],
["--status", commandStatus],
["--hwpod-id", commandHwpodId],
["--node-id", commandNodeId],
["--workspace-root/--workspace-root-ref", commandWorkspaceRoot],
["--root", commandRoot],
] as const) {
if (value !== null && (value.includes("\0") || value.length > 500)) throw new Error(`unsafe web-probe observe ${label}: expected 1-500 non-NUL chars`);
}
return {
action: "observe",
observeAction: observeActionRaw,
id: observeId ?? jobId,
node,
lane,
url: optionValue(args, "--url") ?? (observeActionRaw === "start" ? nodeWebProbeDefaultUrl(spec) : indexed?.url ?? spec.publicWebUrl),
targetPath: optionValue(args, "--target-path") ?? "/workbench",
viewport: optionValue(args, "--viewport") ?? "1440x900",
browserProxyMode: parseWebProbeBrowserProxyMode(optionValue(args, "--browser-proxy-mode") ?? spec.webProbe?.browserProxyMode),
sampleIntervalMs: positiveIntegerOption(args, "--sample-interval-ms", 5000, 600000),
screenshotIntervalMs: positiveIntegerOption(args, "--screenshot-interval-ms", 300000, 86_400_000),
observerRefreshIntervalMs: positiveIntegerOption(args, "--observer-refresh-interval-ms", 180000, 86_400_000),
maxSamples: positiveIntegerOption(args, "--max-samples", 0, 10_000_000),
commandTimeoutSeconds: positiveIntegerOption(args, "--command-timeout-seconds", 55, 3600),
waitMs: positiveIntegerOption(args, "--wait-ms", 0, 600000),
tailLines: positiveIntegerOption(args, "--tail-lines", 5, 200),
maxFiles: positiveIntegerOption(args, "--max-files", 80, 5000),
collectView,
collectFile,
collectFinding,
collectGrep,
collectTraceId,
collectSampleSeq,
collectTimestamp,
collectTurn,
collectCommandId,
collectWindowMs,
analyzeArchivePrefix,
analyzeTailSamples,
full: args.includes("--full"),
raw: args.includes("--raw"),
compactRaw: args.includes("--compact-raw"),
stateDir,
jobId,
force: args.includes("--force"),
commandType,
commandText,
commandPath: optionValue(args, "--path") ?? null,
commandLabel: optionValue(args, "--label") ?? null,
commandSessionId: optionValue(args, "--session-id") ?? null,
commandProvider: optionValue(args, "--provider") ?? null,
commandAfterRound,
commandSeverity,
commandAlternateSessionStrategy,
commandExpectedSentinelRange,
commandRequireComposerReady: args.includes("--require-composer-ready"),
commandFindingId,
commandBlocking,
commandAccountId,
commandFromAccountId,
commandToAccountId,
commandSourceId,
commandFileRef,
commandFilename,
commandTaskRef,
commandTaskId,
commandField,
commandLink,
commandTitle,
commandBody,
commandStatus,
commandHwpodId,
commandNodeId,
commandWorkspaceRoot,
commandRoot,
};
}
export function parseNodeWebProbeObserveCommandType(value: string): NodeWebProbeObserveCommandType {
if (
value === "login"
|| value === "loginAccount"
|| value === "logout"
|| value === "listSessions"
|| value === "switchSessions"
|| value === "preflight"
|| value === "goto"
|| value === "newSession"
|| value === "sendPrompt"
|| value === "steer"
|| value === "cancel"
|| value === "selectProvider"
|| value === "clickSession"
|| value === "refreshCurrentSession"
|| value === "switchAwayAndBack"
|| value === "assertSessionInvariant"
|| value === "gotoProjectMdtodo"
|| value === "selectProjectSource"
|| value === "selectMdtodoSource"
|| value === "selectMdtodoFile"
|| value === "selectMdtodoTask"
|| value === "expandMdtodoTask"
|| value === "openMdtodoReportPreview"
|| value === "toggleMdtodoReportFullscreen"
|| value === "openMdtodoSourceConfig"
|| value === "configureMdtodoHwpodSource"
|| value === "probeMdtodoSource"
|| value === "reindexMdtodoSource"
|| value === "editMdtodoTaskInline"
|| value === "editMdtodoTaskTitle"
|| value === "editMdtodoTaskBody"
|| value === "toggleMdtodoTaskStatus"
|| value === "addMdtodoRootTask"
|| value === "addMdtodoSubTask"
|| value === "continueMdtodoTask"
|| value === "deleteMdtodoTask"
|| value === "launchWorkbenchFromTask"
|| value === "launchWorkbenchFromMdtodo"
|| value === "screenshot"
|| value === "mark"
|| value === "stop"
) return value;
throw new Error(`web-probe observe command --type must be login, loginAccount, logout, listSessions, switchSessions, preflight, goto, gotoProjectMdtodo, newSession, sendPrompt, steer, cancel, selectProvider, clickSession, refreshCurrentSession, switchAwayAndBack, assertSessionInvariant, selectProjectSource, selectMdtodoSource, selectMdtodoFile, selectMdtodoTask, expandMdtodoTask, openMdtodoReportPreview, toggleMdtodoReportFullscreen, openMdtodoSourceConfig, configureMdtodoHwpodSource, probeMdtodoSource, reindexMdtodoSource, editMdtodoTaskInline, editMdtodoTaskTitle, editMdtodoTaskBody, toggleMdtodoTaskStatus, addMdtodoRootTask, addMdtodoSubTask, continueMdtodoTask, deleteMdtodoTask, launchWorkbenchFromTask, launchWorkbenchFromMdtodo, screenshot, mark, or stop; got ${value}`);
}
export function parseWebProbeBrowserProxyMode(value: string | undefined): WebProbeBrowserProxyMode {
if (value === undefined || value === "auto") return "auto";
if (value === "direct") return "direct";
throw new Error(`web-probe --browser-proxy-mode must be auto or direct, got ${value}`);
}
export function nodeWebProbeDefaultUrl(spec: HwlabRuntimeLaneSpec): string {
const origin = spec.webProbe?.defaultOrigin;
if (origin === undefined || origin.mode === "public") return origin?.baseUrl ?? spec.publicWebUrl;
const clusterIp = resolveKubernetesServiceClusterIp(spec, origin.namespace, origin.serviceName, new Map());
return `${origin.scheme}://${clusterIp}:${origin.port}`;
}
export function nodeWebProbeAutoCommandTimeoutSeconds(input: {
timeoutMs: number;
waitAfterSubmitMs: number;
waitMessagesMs: number;
waitAgentTerminalMs: number;
traceSampleCount: number;
traceSampleIntervalMs: number;
freshSession: boolean;
hasMessage: boolean;
}): number {
const traceWindowMs = input.traceSampleCount > 0
? Math.max(0, input.traceSampleCount - 1) * input.traceSampleIntervalMs
: 0;
const startupBudgetMs = input.timeoutMs + 30_000;
const freshnessBudgetMs = input.freshSession ? Math.min(input.timeoutMs, 30_000) : 0;
const submitBudgetMs = input.hasMessage ? input.waitAfterSubmitMs + input.waitMessagesMs + 15_000 : 0;
const terminalBudgetMs = input.waitAgentTerminalMs > 0 ? input.waitAgentTerminalMs : 0;
const totalMs = startupBudgetMs + freshnessBudgetMs + submitBudgetMs + terminalBudgetMs + traceWindowMs + 15_000;
return Math.min(3600, Math.max(60, Math.ceil(totalMs / 1000)));
}
export function assertKnownOptions(args: string[], valueOptions: Set<string>, flagOptions: Set<string>): void {
for (let index = 0; index < args.length; index += 1) {
const arg = args[index] ?? "";
if (!arg.startsWith("--")) continue;
if (flagOptions.has(arg)) continue;
if (valueOptions.has(arg)) {
index += 1;
continue;
}
throw new Error(`unknown option: ${arg}`);
}
}
export function runNodeWebProbe(options: NodeWebProbeOptions): Record<string, unknown> | RenderedCliResult {
const lane = options.lane;
if (!isHwlabRuntimeLane(lane)) throw new Error(`web-probe only supports HWLAB runtime lanes, got ${lane}`);
const spec = hwlabRuntimeLaneSpecForNode(lane, options.node);
if (options.action === "sentinel") return runWebProbeSentinelCommand(spec, options.sentinel);
if (options.action === "screenshot") return runNodeWebProbeScreenshot(options, spec);
if (options.action === "observe" && options.observeAction !== "start") return runNodeWebProbeObserve(options, spec, null, null, null);
const secretSpec = runtimeSecretSpec({ node: options.node, lane });
const material = readBootstrapAdminPasswordMaterial(secretSpec);
const credential = webProbeCredential(secretSpec, material);
if (!material.ok || material.password === null) {
return {
ok: false,
status: "blocked",
command: options.action === "observe"
? `web-probe observe ${options.observeAction} --node ${options.node} --lane ${options.lane}`
: `web-probe ${options.action} --node ${options.node} --lane ${options.lane}`,
node: options.node,
lane: options.lane,
workspace: spec.workspace,
url: options.url,
degradedReason: "web_login_secret_missing",
credential,
next: { secretStatus: `bun scripts/cli.ts hwlab nodes secret status --node ${options.node} --lane ${options.lane} --name ${secretSpec.bootstrapAdminSecret}` },
};
}
if (options.action === "observe") return runNodeWebProbeObserve(options, spec, secretSpec, material, credential);
if (options.action === "script") return runNodeWebProbeScript(options, spec, secretSpec, material, credential);
if (options.commandTimeoutSeconds > 55) return runNodeWebProbeAsync(options, spec, secretSpec, material, credential);
const probeArgs = nodeWebProbeRunArgs(options, "run");
const webProbeProxy = nodeWebProbeHostProxyEnv(spec);
const script = [
"set -eu",
`${[...webProbeProxy.envAssignments, `HWLAB_WEB_USER=${shellQuote(secretSpec.bootstrapAdminUsername)}`, `HWLAB_WEB_PASS=${shellQuote(material.password)}`].join(" ")} ${probeArgs.map(shellQuote).join(" ")}`,
].join("\n");
const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds);
const probe = compactWebProbeResult(parseJsonObject(result.stdout));
const passed = result.exitCode === 0 && probe?.status === "pass";
const summary = nullableRecord(probe?.summary);
const degradedReason = result.timedOut
? "web-probe-command-timeout"
: typeof probe?.degradedReason === "string"
? probe.degradedReason
: null;
return renderWebProbeRunResult({
ok: passed,
status: passed ? "pass" : "blocked",
command: `web-probe run --node ${options.node} --lane ${options.lane}`,
node: options.node,
lane: options.lane,
workspace: spec.workspace,
url: options.url,
network: webProbeProxy.summary,
credential,
commandTimeout: {
seconds: options.commandTimeoutSeconds,
autoSeconds: options.commandTimeoutAutoSeconds,
userProvided: options.commandTimeoutUserProvided,
timedOut: result.timedOut,
},
degradedReason,
failureKind: typeof summary?.failureKind === "string" ? summary.failureKind : null,
summary,
probe,
result: compactCommandResult(result),
valuesRedacted: true,
});
}
export function runNodeWebProbeScreenshot(options: NodeWebProbeScreenshotOptions, spec: HwlabRuntimeLaneSpec): Record<string, unknown> {
const route = `${options.node}:${spec.workspace}`;
const script = webProbeScreenshotRemoteScript(options);
const result = runCommand([
transPath(),
route,
"playwright",
"--local-dir",
options.localDir,
"--wait-timeout-ms",
String(options.waitTimeoutMs),
"--inactivity-timeout-ms",
"30000",
...(options.keepRemote ? ["--keep-remote"] : []),
], repoRoot, { input: script, timeoutMs: options.commandTimeoutSeconds * 1000 });
const transport = record(parseJsonObject(result.stdout));
const transportParsed = Object.keys(transport).length > 0;
const artifacts = Array.isArray(transport.artifacts) ? transport.artifacts.map(record) : [];
const screenshot = artifacts.find((artifact) => {
const remotePath = typeof artifact.remotePath === "string" ? artifact.remotePath : "";
const localPath = typeof artifact.localPath === "string" ? artifact.localPath : "";
return remotePath.endsWith(".png") || localPath.endsWith(".png");
}) ?? null;
const remoteSummary = parseWebProbeScreenshotSummary(record(transport.remote).stdoutTail);
const compactScreenshot = screenshot === null ? null : compactWebProbeScreenshotArtifact(screenshot);
const compactArtifacts = artifacts.map(compactWebProbeScreenshotArtifact);
const remoteRecord = record(transport.remote);
const ok = result.exitCode === 0 && transport.ok === true && screenshot !== null && screenshot.verified !== false;
const degradedReason = ok
? null
: result.timedOut
? "web-probe-screenshot-command-timeout"
: !transportParsed
? "web-probe-screenshot-transport-unparseable"
: transport.ok === false
? "web-probe-screenshot-remote-failed"
: screenshot === null
? "web-probe-screenshot-artifact-missing"
: "web-probe-screenshot-download-unverified";
return {
ok,
status: ok ? "pass" : "blocked",
command: `web-probe screenshot --node ${options.node} --lane ${options.lane}`,
node: options.node,
lane: options.lane,
workspace: spec.workspace,
route,
url: options.url,
viewport: options.viewport,
localDir: options.localDir,
screenshot: compactScreenshot,
artifacts: compactArtifacts,
artifactCount: artifacts.length,
remote: {
exitCode: remoteRecord.exitCode ?? null,
remoteDir: remoteRecord.remoteDir ?? null,
defaultScreenshot: remoteRecord.defaultScreenshot ?? null,
stdoutTail: ok ? "" : typeof remoteRecord.stdoutTail === "string" ? remoteRecord.stdoutTail.slice(-1200) : "",
stderrTail: ok ? "" : typeof remoteRecord.stderrTail === "string" ? remoteRecord.stderrTail.slice(-1200) : "",
},
page: compactWebProbeScreenshotPageSummary(remoteSummary),
transport: {
runId: transport.runId ?? null,
artifactCount: transport.artifactCount ?? null,
expectedArtifactCount: transport.expectedArtifactCount ?? null,
cleanup: compactWebProbeScreenshotCleanup(transport.cleanup),
downloadFailure: transport.downloadFailure ?? null,
},
result: compactCommandResult(result),
degradedReason,
valuesRedacted: true,
};
}
function compactWebProbeScreenshotArtifact(artifact: Record<string, unknown>): Record<string, unknown> {
const transfer = record(artifact.transfer);
return {
remotePath: typeof artifact.remotePath === "string" ? artifact.remotePath : null,
localPath: typeof artifact.localPath === "string" ? artifact.localPath : null,
bytes: Number.isFinite(Number(artifact.bytes)) ? Number(artifact.bytes) : null,
sha256: typeof artifact.sha256 === "string" ? artifact.sha256 : null,
verified: artifact.verified === true,
transfer: Object.keys(transfer).length === 0 ? null : {
strategy: transfer.strategy ?? null,
transport: transfer.transport ?? null,
chunks: transfer.chunks ?? null,
elapsedMs: transfer.elapsedMs ?? null,
throughputBytesPerSecond: transfer.throughputBytesPerSecond ?? null,
},
};
}
function compactWebProbeScreenshotPageSummary(value: Record<string, unknown> | null): Record<string, unknown> | null {
if (value === null) return null;
const layout = record(value.layout);
return {
ok: value.ok === true,
status: value.status ?? null,
title: value.title ?? null,
finalUrl: value.finalUrl ?? null,
executablePath: value.executablePath ?? null,
viewport: value.viewport ?? null,
fullPage: value.fullPage ?? null,
selector: value.selector ?? null,
layout: {
viewport: record(layout.viewport),
documentSize: record(layout.documentSize),
horizontalOverflow: layout.horizontalOverflow === true,
overflowCount: layout.overflowCount ?? null,
overflow: Array.isArray(layout.overflow) ? layout.overflow.slice(0, 5).map(record) : [],
},
consoleCount: value.consoleCount ?? null,
requestFailureCount: value.requestFailureCount ?? null,
};
}
function compactWebProbeScreenshotCleanup(value: unknown): Record<string, unknown> | null {
const cleanup = record(value);
if (Object.keys(cleanup).length === 0) return null;
return {
attempted: cleanup.attempted ?? null,
kept: cleanup.kept ?? null,
remoteDir: cleanup.remoteDir ?? null,
exitCode: cleanup.exitCode ?? null,
ok: cleanup.ok ?? null,
};
}
function webProbeScreenshotRemoteScript(options: NodeWebProbeScreenshotOptions): string {
const [widthRaw, heightRaw] = options.viewport.split("x");
return [
"set -eu",
`export UNIDESK_WEB_PROBE_SCREENSHOT_URL=${shellQuote(options.url)}`,
`export UNIDESK_WEB_PROBE_SCREENSHOT_PATH="$UNIDESK_PLAYWRIGHT_REMOTE_DIR"/${shellQuote(options.name)}`,
`export UNIDESK_WEB_PROBE_SCREENSHOT_WIDTH=${shellQuote(widthRaw ?? "1440")}`,
`export UNIDESK_WEB_PROBE_SCREENSHOT_HEIGHT=${shellQuote(heightRaw ?? "900")}`,
`export UNIDESK_WEB_PROBE_SCREENSHOT_TIMEOUT_MS=${shellQuote(String(options.timeoutMs))}`,
`export UNIDESK_WEB_PROBE_SCREENSHOT_WAIT_UNTIL=${shellQuote(options.waitUntil)}`,
`export UNIDESK_WEB_PROBE_SCREENSHOT_FULL_PAGE=${shellQuote(options.fullPage ? "1" : "0")}`,
`export UNIDESK_WEB_PROBE_SCREENSHOT_SELECTOR=${shellQuote(options.selector ?? "")}`,
"if command -v chromium >/dev/null 2>&1; then",
" export UNIDESK_WEB_PROBE_SCREENSHOT_EXECUTABLE_PATH=$(command -v chromium)",
"elif command -v chromium-browser >/dev/null 2>&1; then",
" export UNIDESK_WEB_PROBE_SCREENSHOT_EXECUTABLE_PATH=$(command -v chromium-browser)",
"elif command -v google-chrome >/dev/null 2>&1; then",
" export UNIDESK_WEB_PROBE_SCREENSHOT_EXECUTABLE_PATH=$(command -v google-chrome)",
"else",
" export UNIDESK_WEB_PROBE_SCREENSHOT_EXECUTABLE_PATH=",
"fi",
"cat > \"$UNIDESK_PLAYWRIGHT_REMOTE_DIR/web-probe-screenshot.mjs\" <<'WEB_PROBE_SCREENSHOT_JS'",
webProbeScreenshotRemoteModule(),
"WEB_PROBE_SCREENSHOT_JS",
"bun \"$UNIDESK_PLAYWRIGHT_REMOTE_DIR/web-probe-screenshot.mjs\"",
].join("\n");
}
function webProbeScreenshotRemoteModule(): string {
return String.raw`import { chromium } from "playwright";
const url = process.env.UNIDESK_WEB_PROBE_SCREENSHOT_URL;
const screenshotPath = process.env.UNIDESK_WEB_PROBE_SCREENSHOT_PATH;
const width = Number(process.env.UNIDESK_WEB_PROBE_SCREENSHOT_WIDTH || 1440);
const height = Number(process.env.UNIDESK_WEB_PROBE_SCREENSHOT_HEIGHT || 900);
const timeout = Number(process.env.UNIDESK_WEB_PROBE_SCREENSHOT_TIMEOUT_MS || 30000);
const waitUntil = process.env.UNIDESK_WEB_PROBE_SCREENSHOT_WAIT_UNTIL || "networkidle";
const fullPage = process.env.UNIDESK_WEB_PROBE_SCREENSHOT_FULL_PAGE !== "0";
const selector = process.env.UNIDESK_WEB_PROBE_SCREENSHOT_SELECTOR || "";
const executablePath = process.env.UNIDESK_WEB_PROBE_SCREENSHOT_EXECUTABLE_PATH || "";
if (!url || !screenshotPath) throw new Error("missing screenshot URL or path");
const consoleMessages = [];
const requestFailures = [];
const launchOptions = {
headless: true,
args: ["--disable-gpu", "--no-sandbox"],
...(executablePath ? { executablePath } : {}),
};
const browser = await chromium.launch(launchOptions);
const context = await browser.newContext({ viewport: { width, height }, deviceScaleFactor: 1, isMobile: width <= 560 });
const page = await context.newPage();
page.on("console", (message) => {
if (consoleMessages.length < 20) consoleMessages.push({ type: message.type(), text: message.text().slice(0, 240) });
});
page.on("requestfailed", (request) => {
if (requestFailures.length < 20) requestFailures.push({ url: request.url().slice(0, 240), method: request.method(), failure: request.failure()?.errorText || null });
});
let status = null;
try {
const response = await page.goto(url, { timeout, waitUntil });
status = response?.status() ?? null;
await page.waitForTimeout(350);
if (selector) await page.locator(selector).screenshot({ path: screenshotPath, timeout });
else await page.screenshot({ path: screenshotPath, fullPage, animations: "disabled" });
const layout = await page.evaluate(() => {
const doc = document.documentElement;
const body = document.body;
const viewport = { width: window.innerWidth, height: window.innerHeight };
const documentSize = {
width: Math.max(doc.scrollWidth, body?.scrollWidth || 0),
height: Math.max(doc.scrollHeight, body?.scrollHeight || 0),
};
const overflow = [];
let overflowCount = 0;
for (const element of Array.from(document.querySelectorAll("body *"))) {
const rect = element.getBoundingClientRect();
const right = rect.right;
const bottom = rect.bottom;
const overflowRight = right - viewport.width;
const overflowLeft = -rect.left;
if (overflowRight > 1 || overflowLeft > 1) {
overflowCount += 1;
if (overflow.length < 5) {
overflow.push({
tag: element.tagName.toLowerCase(),
className: String(element.className || "").slice(0, 80),
text: String(element.textContent || "").replace(/\s+/g, " ").trim().slice(0, 80),
x: Math.round(rect.x),
y: Math.round(rect.y),
width: Math.round(rect.width),
height: Math.round(rect.height),
overflowRight: Math.max(0, Math.round(overflowRight)),
overflowLeft: Math.max(0, Math.round(overflowLeft)),
bottom: Math.round(bottom),
});
}
}
}
return {
title: document.title,
finalUrl: window.location.href,
viewport,
documentSize,
horizontalOverflow: documentSize.width > viewport.width + 1,
overflowCount,
overflow,
};
});
console.log("__WEB_PROBE_SCREENSHOT_JSON__" + JSON.stringify({
ok: true,
url,
finalUrl: page.url(),
status,
title: await page.title(),
screenshotPath,
executablePath: executablePath || null,
viewport: { width, height },
fullPage,
selector: selector || null,
layout,
consoleCount: consoleMessages.length,
requestFailureCount: requestFailures.length,
consoleMessages: consoleMessages.slice(0, 5),
requestFailures: requestFailures.slice(0, 5),
valuesRedacted: true,
}));
} finally {
await context.close().catch(() => {});
await browser.close().catch(() => {});
}
`;
}
function parseWebProbeScreenshotSummary(value: unknown): Record<string, unknown> | null {
if (typeof value !== "string" || value.length === 0) return null;
const marker = "__WEB_PROBE_SCREENSHOT_JSON__";
const index = value.lastIndexOf(marker);
if (index < 0) return null;
try {
return record(JSON.parse(value.slice(index + marker.length).trim()));
} catch {
return null;
}
}
export function nodeWebProbeRunArgs(options: NodeWebProbeRunOptions, command: "run" | "start"): string[] {
const probeArgs = [
"node",
"scripts/web-live-dom-probe.mjs",
command,
"--url", options.url,
"--timeout-ms", String(options.timeoutMs),
"--wait-after-submit-ms", String(options.waitAfterSubmitMs),
"--wait-messages-ms", String(options.waitMessagesMs),
];
if (options.waitAgentTerminalMs > 0) probeArgs.push("--wait-agent-terminal-ms", String(options.waitAgentTerminalMs));
if (options.traceSampleCount > 0) probeArgs.push("--trace-sample-count", String(options.traceSampleCount), "--trace-sample-interval-ms", String(options.traceSampleIntervalMs));
if (options.freshSession) probeArgs.push("--fresh-session");
if (!options.cancelRunning) probeArgs.push("--no-cancel-running");
if (options.conversationId !== null) probeArgs.push("--conversation-id", options.conversationId);
if (options.message !== null) probeArgs.push("--message", options.message);
return probeArgs;
}
export function runNodeWebProbeAsync(
options: NodeWebProbeRunOptions,
spec: HwlabRuntimeLaneSpec,
secretSpec: RuntimeSecretSpec,
material: BootstrapAdminPasswordMaterial,
credential: Record<string, unknown>,
): Record<string, unknown> {
const startArgs = nodeWebProbeRunArgs(options, "start");
const webProbeProxy = nodeWebProbeHostProxyEnv(spec);
const startScript = [
"set -eu",
`${[...webProbeProxy.envAssignments, `HWLAB_WEB_USER=${shellQuote(secretSpec.bootstrapAdminUsername)}`, `HWLAB_WEB_PASS=${shellQuote(material.password ?? "")}`].join(" ")} ${startArgs.map(shellQuote).join(" ")}`,
].join("\n");
const startResult = runTransWorkspaceStdinScript(options.node, spec.workspace, startScript, 55);
const start = parseJsonObject(startResult.stdout);
const jobId = typeof start?.jobId === "string" ? start.jobId : null;
if (startResult.exitCode !== 0 || jobId === null) {
return renderWebProbeRunResult({
ok: false,
status: "blocked",
command: `web-probe run --node ${options.node} --lane ${options.lane}`,
node: options.node,
lane: options.lane,
workspace: spec.workspace,
url: options.url,
network: webProbeProxy.summary,
credential,
mode: "async-start",
commandTimeout: webProbeCommandTimeoutSummary(options, false),
degradedReason: startResult.timedOut ? "web-probe-command-timeout" : "web-probe-async-start-failed",
start: start ?? null,
result: compactCommandResultRedacted(startResult, [material.password ?? ""]),
valuesRedacted: true,
});
}
const poll = pollNodeWebProbeJob(options, spec, jobId);
const statusReport = record(record(poll.status).report);
const reportPath = typeof start.reportPath === "string" ? start.reportPath : null;
const reportLoad = Object.keys(statusReport).length > 0
? { source: "status", report: statusReport, result: null as CommandResult | null, degradedReason: null as string | null, path: null as string | null }
: readNodeWebProbeReport(options, spec, reportPath);
const report = reportLoad.report ?? {};
const reportRecovered = Object.keys(report).length > 0;
const probe = compactWebProbeResult(Object.keys(report).length > 0 ? report : null);
const passed = probe?.status === "pass";
const summary = nullableRecord(probe?.summary);
const degradedReason = poll.timedOut
? "web-probe-command-timeout"
: typeof probe?.degradedReason === "string"
? probe.degradedReason
: poll.status === null
? reportRecovered
? reportLoad.degradedReason
: reportLoad.degradedReason ?? "web-probe-async-status-failed"
: reportLoad.degradedReason;
return renderWebProbeRunResult({
ok: passed,
status: passed ? "pass" : "blocked",
command: `web-probe run --node ${options.node} --lane ${options.lane}`,
node: options.node,
lane: options.lane,
workspace: spec.workspace,
url: options.url,
network: webProbeProxy.summary,
credential,
mode: "async",
commandTimeout: webProbeCommandTimeoutSummary(options, poll.timedOut),
degradedReason,
failureKind: typeof summary?.failureKind === "string" ? summary.failureKind : null,
summary,
job: {
jobId,
startedAt: start.startedAt ?? null,
polls: poll.polls,
elapsedMs: poll.elapsedMs,
statusCommand: start.statusCommand ?? `node scripts/web-live-dom-probe.mjs status ${jobId}`,
},
probe,
start: {
ok: start.ok === true,
status: start.status ?? null,
jobId,
traceSampling: start.traceSampling ?? null,
reportPath: start.reportPath ?? null,
screenshotPath: start.screenshotPath ?? null,
},
statusResult: poll.result === null ? null : compactCommandResult(poll.result),
reportLoad: {
source: reportLoad.source,
path: reportLoad.path,
result: reportLoad.result === null ? null : compactCommandResult(reportLoad.result),
degradedReason: reportLoad.degradedReason,
},
valuesRedacted: true,
});
}
export function readNodeWebProbeReport(
options: NodeWebProbeRunOptions,
spec: HwlabRuntimeLaneSpec,
reportPath: string | null,
): { source: string; report: Record<string, unknown> | null; result: CommandResult | null; degradedReason: string | null; path: string | null } {
if (!reportPath) return { source: "missing", report: null, result: null, degradedReason: "web-probe-report-path-missing", path: null };
if (!isSafeWebProbeReportPath(reportPath)) return { source: "unsafe-path", report: null, result: null, degradedReason: "web-probe-report-path-invalid", path: reportPath };
const script = [
"set -eu",
`test -f ${shellQuote(reportPath)}`,
`node - ${shellQuote(reportPath)} <<'NODE'`,
"const fs = require('fs');",
"const reportPath = process.argv[2];",
"const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));",
"function rec(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; }",
"function compact(value, depth = 0) {",
" if (value === null || value === undefined) return value ?? null;",
" if (typeof value === 'string') return value.replace(/\\s+/gu, ' ').trim().slice(0, 600);",
" if (typeof value === 'number' || typeof value === 'boolean') return value;",
" if (depth >= 6) return '[max-depth]';",
" if (Array.isArray(value)) return value.slice(0, 16).map((item) => compact(item, depth + 1));",
" if (typeof value === 'object') {",
" const out = {};",
" for (const [key, nested] of Object.entries(value).slice(0, 32)) out[key] = compact(nested, depth + 1);",
" return out;",
" }",
" return String(value).slice(0, 600);",
"}",
"const artifacts = rec(report.artifacts);",
"const compactReport = {",
" ok: report.ok === true,",
" status: typeof report.status === 'string' ? report.status : null,",
" finalUrl: typeof report.finalUrl === 'string' ? report.finalUrl : null,",
" error: typeof report.error === 'string' ? report.error : null,",
" degradedReason: typeof report.degradedReason === 'string' ? report.degradedReason : null,",
" actions: compact(report.actions),",
" session: compact(report.session),",
" trace: compact(report.trace),",
" promptValidation: compact(report.promptValidation),",
" performance: compact(report.performance),",
" traceSamples: compact(report.traceSamples),",
" dom: compact(report.dom),",
" failureDom: compact(report.failureDom),",
" artifacts: {",
" ...compact(artifacts),",
" reportPath: typeof artifacts.reportPath === 'string' ? artifacts.reportPath : reportPath,",
" },",
" safety: compact(report.safety),",
"};",
"console.log(JSON.stringify(compactReport));",
"NODE",
].join("\n");
const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, 55);
if (result.exitCode !== 0 || result.timedOut) return { source: "report-file", report: null, result, degradedReason: result.timedOut ? "web-probe-command-timeout" : "web-probe-report-read-failed", path: reportPath };
const report = parseJsonObject(result.stdout);
return {
source: "report-file",
report,
result,
degradedReason: report === null ? "web-probe-report-parse-failed" : null,
path: reportPath,
};
}
export function isSafeWebProbeReportPath(reportPath: string): boolean {
return reportPath.includes("/.state/web-live-dom-probe/") && reportPath.endsWith(".result.json") && !reportPath.includes("\0");
}
export function pollNodeWebProbeJob(options: NodeWebProbeRunOptions, spec: HwlabRuntimeLaneSpec, jobId: string): {
status: Record<string, unknown> | null;
result: CommandResult | null;
polls: number;
elapsedMs: number;
timedOut: boolean;
} {
const startedAt = Date.now();
const deadline = startedAt + options.commandTimeoutSeconds * 1000;
let lastStatus: Record<string, unknown> | null = null;
let lastResult: CommandResult | null = null;
let polls = 0;
while (Date.now() < deadline) {
polls += 1;
const script = `node scripts/web-live-dom-probe.mjs status ${shellQuote(jobId)}`;
const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, 55);
lastResult = result;
lastStatus = parseJsonObject(result.stdout);
const status = typeof lastStatus?.status === "string" ? lastStatus.status : null;
if (result.exitCode === 0 && status !== "running") return { status: lastStatus, result, polls, elapsedMs: Date.now() - startedAt, timedOut: false };
if (result.exitCode !== 0 && status !== "running") return { status: lastStatus, result, polls, elapsedMs: Date.now() - startedAt, timedOut: false };
sleepSync(Math.min(5000, Math.max(500, deadline - Date.now())));
}
return { status: lastStatus, result: lastResult, polls, elapsedMs: Date.now() - startedAt, timedOut: true };
}
export function webProbeCommandTimeoutSummary(options: NodeWebProbeRunOptions, timedOut: boolean): Record<string, unknown> {
return {
seconds: options.commandTimeoutSeconds,
autoSeconds: options.commandTimeoutAutoSeconds,
userProvided: options.commandTimeoutUserProvided,
transportMode: options.commandTimeoutSeconds > 55 ? "async-start-status" : "direct",
timedOut,
};
}
export function webProbeCredential(secretSpec: RuntimeSecretSpec, material: BootstrapAdminPasswordMaterial): Record<string, unknown> {
return {
username: secretSpec.bootstrapAdminUsername,
sourceRef: material.sourceRef,
sourceKey: material.sourceKey,
sourcePath: material.sourcePath === null ? null : displayRepoPath(material.sourcePath),
sourcePresent: material.sourcePresent,
sourceFingerprint: material.sourceFingerprint,
injectedVia: material.ok ? "stdin-env" : null,
valuesRedacted: true,
error: material.error,
};
}
export function nodeWebProbeAlertThresholds(spec: HwlabRuntimeLaneSpec): HwlabRuntimeWebProbeAlertThresholdsSpec {
const thresholds = spec.webProbe?.alertThresholds;
if (thresholds === undefined) {
throw new Error(`${hwlabRuntimeLaneConfigPath()} node=${spec.nodeId} lane=${spec.lane} requires webProbe.alertThresholds for web-probe observe`);
}
return thresholds;
}
export function nodeWebProbeProjectManagementConfig(spec: HwlabRuntimeLaneSpec): HwlabRuntimeWebProbeProjectManagementSpec | null {
return spec.webProbe?.projectManagement ?? null;
}
export interface NodeWebProbeHostProxyEnv {
readonly envAssignments: string[];
readonly summary: Record<string, unknown>;
}
export function nodeWebProbeHostProxyEnv(spec: HwlabRuntimeLaneSpec, browserProxyMode: WebProbeBrowserProxyMode = "auto"): NodeWebProbeHostProxyEnv {
if (browserProxyMode === "direct") {
return {
envAssignments: [],
summary: {
source: "option",
mode: "direct",
networkProfileId: spec.networkProfileId,
proxy: { enabled: false },
valuesPrinted: false,
},
};
}
const proxy = spec.networkProfile.proxy;
const serviceCache = new Map<string, string>();
const http = resolveNodeWebProbeHostProxyUrl(spec, proxy.http, serviceCache);
const https = resolveNodeWebProbeHostProxyUrl(spec, proxy.https, serviceCache);
const all = resolveNodeWebProbeHostProxyUrl(spec, proxy.all, serviceCache);
const noProxy = proxy.noProxy.join(",");
return {
envAssignments: [
["HTTP_PROXY", http.url],
["HTTPS_PROXY", https.url],
["ALL_PROXY", all.url],
["http_proxy", http.url],
["https_proxy", https.url],
["all_proxy", all.url],
["NO_PROXY", noProxy],
["no_proxy", noProxy],
].map(([key, value]) => `${key}=${shellQuote(value)}`),
summary: {
source: "yaml",
mode: "host-env",
networkProfileId: spec.networkProfileId,
proxy: {
http: http.summary,
https: https.summary,
all: all.summary,
noProxyCount: proxy.noProxy.length,
},
valuesPrinted: false,
},
};
}
export function resolveNodeWebProbeHostProxyUrl(
spec: HwlabRuntimeLaneSpec,
rawUrl: string,
serviceCache: Map<string, string>,
): { url: string; summary: Record<string, unknown> } {
let parsed: URL;
try {
parsed = new URL(rawUrl);
} catch (error) {
throw new Error(`config/hwlab-node-lanes.yaml networkProfiles.${spec.networkProfileId}.proxy contains invalid proxy URL: ${error instanceof Error ? error.message : String(error)}`);
}
const service = parseKubernetesServiceDnsHost(parsed.hostname);
if (service === null) {
return {
url: rawUrl,
summary: {
mode: "host-url",
host: parsed.hostname,
port: parsed.port || null,
valuesPrinted: false,
},
};
}
const clusterIp = resolveKubernetesServiceClusterIp(spec, service.namespace, service.name, serviceCache);
const originalHost = parsed.hostname;
parsed.hostname = clusterIp;
const resolvedUrl = normalizedProxyUrl(parsed);
return {
url: resolvedUrl,
summary: {
mode: "k8s-service-cluster-ip",
service: service.name,
namespace: service.namespace,
originalHost,
resolvedHost: clusterIp,
port: parsed.port || null,
valuesPrinted: false,
},
};
}
export function parseKubernetesServiceDnsHost(hostname: string): { name: string; namespace: string } | null {
const match = hostname.toLowerCase().match(/^([a-z0-9]([-a-z0-9]*[a-z0-9])?)\.([a-z0-9]([-a-z0-9]*[a-z0-9])?)\.svc(?:\.cluster\.local)?$/u);
if (match === null) return null;
return { name: match[1] ?? "", namespace: match[3] ?? "" };
}
export function resolveKubernetesServiceClusterIp(
spec: HwlabRuntimeLaneSpec,
namespace: string,
serviceName: string,
serviceCache: Map<string, string>,
): string {
const cacheKey = `${namespace}/${serviceName}`;
const cached = serviceCache.get(cacheKey);
if (cached !== undefined) return cached;
const result = runCommand([transPath(), spec.nodeKubeRoute, "get", "svc", "-n", namespace, serviceName, "-o", "jsonpath={.spec.clusterIP}"], repoRoot, { timeoutMs: 20_000 });
const clusterIp = result.stdout.trim();
if (result.exitCode !== 0 || clusterIp.length === 0) {
const reason = result.stderr.trim().slice(-500) || result.stdout.trim().slice(-500) || `exitCode=${result.exitCode}`;
throw new Error(`web-probe proxy service resolution failed for ${spec.nodeId}/${spec.lane} ${namespace}/${serviceName}: ${reason}`);
}
serviceCache.set(cacheKey, clusterIp);
return clusterIp;
}
export function normalizedProxyUrl(parsed: URL): string {
const value = parsed.toString();
if (parsed.pathname === "/" && parsed.search === "" && parsed.hash === "") return value.replace(/\/$/u, "");
return value;
}
function webProbeAccountEnvAssignments(): string[] {
return Object.entries(process.env)
.filter(([key, value]) => value !== undefined && /^HWLAB_WEB_(?:ACCOUNT_)?[A-Z0-9_]+_(?:JSON|USER|PASS)$/u.test(key))
.map(([key, value]) => `${key}=${shellQuote(value ?? "")}`);
}
export function runNodeWebProbeObserve(
options: NodeWebProbeObserveOptions,
spec: HwlabRuntimeLaneSpec,
secretSpec: RuntimeSecretSpec | null,
material: BootstrapAdminPasswordMaterial | null,
credential: Record<string, unknown> | null,
): Record<string, unknown> {
if (options.observeAction === "start") {
if (secretSpec === null || material === null || credential === null || material.password === null) throw new Error("web-probe observe start requires bootstrap admin credential material");
return runNodeWebProbeObserveStart(options, spec, secretSpec, material, credential);
}
if (options.observeAction === "status") return runNodeWebProbeObserveStatus(options, spec);
if (options.observeAction === "command") return runNodeWebProbeObserveCommand(options, spec, false);
if (options.observeAction === "stop") return runNodeWebProbeObserveCommand({ ...options, commandType: "stop" }, spec, true);
if (options.observeAction === "collect") return runNodeWebProbeObserveCollect(options, spec);
return runNodeWebProbeObserveAnalyze(options, spec);
}
export function runNodeWebProbeObserveStart(
options: NodeWebProbeObserveOptions,
spec: HwlabRuntimeLaneSpec,
secretSpec: RuntimeSecretSpec,
material: BootstrapAdminPasswordMaterial,
credential: Record<string, unknown>,
): Record<string, unknown> | RenderedCliResult {
const jobId = `webobs-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
const timestamp = new Date().toISOString().replace(/[-:]/gu, "").replace(/[.]\d{3}Z$/u, "Z");
const day = timestamp.slice(0, 8);
const defaultStateDir = `.state/web-observe/${safeWebObserveSegment(options.node)}/${safeWebObserveSegment(options.lane)}/${day.slice(0, 4)}/${day.slice(4, 6)}/${day.slice(6, 8)}/${timestamp}_${safeWebObserveTargetSegment(options.targetPath)}_${jobId}`;
const stateDir = options.stateDir ?? defaultStateDir;
const runnerB64 = Buffer.from(nodeWebObserveRunnerSource(), "utf8").toString("base64");
const runnerB64Body = runnerB64.match(/.{1,76}/gu)?.join("\n") ?? runnerB64;
const webProbeProxy = nodeWebProbeHostProxyEnv(spec, options.browserProxyMode);
const alertThresholds = nodeWebProbeAlertThresholds(spec);
const projectManagement = nodeWebProbeProjectManagementConfig(spec);
const runnerEnvAssignments = [
...webProbeProxy.envAssignments,
...webProbeAccountEnvAssignments(),
`HWLAB_WEB_BASE_URL=${shellQuote(options.url)}`,
`HWLAB_WEB_USER=${shellQuote(secretSpec.bootstrapAdminUsername)}`,
`HWLAB_WEB_PASS=${shellQuote(material.password)}`,
`UNIDESK_WEB_OBSERVE_STATE_DIR=${shellQuote(stateDir)}`,
`UNIDESK_WEB_OBSERVE_JOB_ID=${shellQuote(jobId)}`,
`UNIDESK_WEB_OBSERVE_TARGET_PATH=${shellQuote(options.targetPath)}`,
`UNIDESK_WEB_OBSERVE_SAMPLE_INTERVAL_MS=${shellQuote(String(options.sampleIntervalMs))}`,
`UNIDESK_WEB_OBSERVE_SCREENSHOT_INTERVAL_MS=${shellQuote(String(options.screenshotIntervalMs))}`,
`UNIDESK_WEB_OBSERVE_OBSERVER_REFRESH_INTERVAL_MS=${shellQuote(String(options.observerRefreshIntervalMs))}`,
`UNIDESK_WEB_OBSERVE_MAX_SAMPLES=${shellQuote(String(options.maxSamples))}`,
`UNIDESK_WEB_OBSERVE_VIEWPORT=${shellQuote(options.viewport)}`,
`UNIDESK_WEB_OBSERVE_BROWSER_PROXY_MODE=${shellQuote(options.browserProxyMode)}`,
`UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON=${shellQuote(JSON.stringify(alertThresholds))}`,
`UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON=${shellQuote(JSON.stringify(projectManagement))}`,
].join(" ");
const script = [
"set -eu",
`state_dir=${shellQuote(stateDir)}`,
"mkdir -p \"$state_dir\"",
"chmod 700 \"$state_dir\"",
"runner=\"$state_dir/observer-runner.mjs\"",
"runner_b64=\"$state_dir/observer-runner.mjs.b64\"",
"cat >\"$runner_b64\" <<'UNIDESK_WEB_OBSERVE_RUNNER_B64'",
runnerB64Body,
"UNIDESK_WEB_OBSERVE_RUNNER_B64",
"node -e \"const fs=require('fs'); fs.writeFileSync(process.argv[1], Buffer.from(fs.readFileSync(process.argv[2], 'utf8').replace(/\\s+/g, ''), 'base64'))\" \"$runner\" \"$runner_b64\"",
"rm -f \"$runner_b64\"",
"chmod 700 \"$runner\"",
`if command -v setsid >/dev/null 2>&1; then setsid env ${runnerEnvAssignments} node "$runner" >"$state_dir/stdout.log" 2>"$state_dir/stderr.log" </dev/null & else nohup env ${runnerEnvAssignments} node "$runner" >"$state_dir/stdout.log" 2>"$state_dir/stderr.log" </dev/null & fi`,
"pid=$!",
"printf '%s\\n' \"$pid\" >\"$state_dir/pid\"",
"sleep 1",
`node -e ${shellQuote("const fs=require('fs'); const dir=process.argv[1]; const read=(n)=>{try{return JSON.parse(fs.readFileSync(dir+'/'+n,'utf8'))}catch{return null}}; const pid=fs.existsSync(dir+'/pid')?fs.readFileSync(dir+'/pid','utf8').trim():null; console.log(JSON.stringify({ok:true,command:'web-probe-observe start',jobId:process.argv[2],stateDir:dir,pid:Number(pid)||null,manifestPath:dir+'/manifest.json',heartbeat:read('heartbeat.json'),manifest:read('manifest.json'),statusCommand:'bun scripts/cli.ts web-probe observe status --node '+process.argv[3]+' --lane '+process.argv[4]+' --state-dir '+dir,stopCommand:'bun scripts/cli.ts web-probe observe stop --node '+process.argv[3]+' --lane '+process.argv[4]+' --state-dir '+dir,valuesRedacted:true},null,2))")} "$state_dir" ${shellQuote(jobId)} ${shellQuote(options.node)} ${shellQuote(options.lane)}`,
].join("\n");
const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds);
const started = parseJsonObject(result.stdout);
const observerId = typeof started?.jobId === "string" ? started.jobId : jobId;
const index = result.exitCode === 0 && started?.ok === true
? upsertWebObserveIndexEntry({
id: observerId,
node: options.node,
lane: options.lane,
workspace: spec.workspace,
stateDir,
url: options.url,
targetPath: options.targetPath,
status: "running",
pid: typeof started.pid === "number" ? started.pid : null,
startedAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
})
: null;
return renderWebObserveStartResult({
ok: result.exitCode === 0 && started?.ok === true,
status: result.exitCode === 0 && started?.ok === true ? "started" : "blocked",
command: `web-probe observe start --node ${options.node} --lane ${options.lane}`,
node: options.node,
lane: options.lane,
workspace: spec.workspace,
url: options.url,
network: webProbeProxy.summary,
alertThresholds,
projectManagement,
targetPath: options.targetPath,
id: observerId,
credential,
observer: withWebObserveShortcuts(started, observerId),
wrapper: buildWebObserveWrapperForObserveOptions("start", options, spec.workspace, { id: observerId, jobId: observerId, stateDir }),
index,
next: webObserveNextCommands(observerId),
result: compactCommandResultRedacted(result, [material.password ?? ""]),
valuesRedacted: true,
});
}
export function runNodeWebProbeObserveStatus(options: NodeWebProbeObserveOptions, spec: HwlabRuntimeLaneSpec): Record<string, unknown> | RenderedCliResult {
const { result, status } = readNodeWebProbeObserveRemoteStatus(options, spec, options.tailLines, options.commandTimeoutSeconds);
const observerId = webObserveIdFromStatus(status, options);
const statusReadable = status !== null;
const ok = result.exitCode === 0 && statusReadable && status.ok !== false;
const degradedReason = result.timedOut
? "web-probe-command-timeout"
: result.exitCode !== 0
? "web-probe-observe-status-failed"
: !statusReadable
? "web-probe-observe-status-unreadable"
: typeof status.degradedReason === "string"
? status.degradedReason
: null;
const index = ok && observerId !== null && options.stateDir !== null
? upsertWebObserveIndexEntry(webObserveIndexEntryFromOptions(options, spec, observerId, status))
: null;
return withWebObserveStatusRendered({
ok,
status: ok ? "observed" : "blocked",
command: webObserveCommandLabel("status", options),
id: observerId,
node: options.node,
lane: options.lane,
workspace: spec.workspace,
degradedReason,
observer: withWebObserveShortcuts(status, observerId),
wrapper: buildWebObserveWrapperForObserveOptions("status", options, spec.workspace, { id: observerId, stateDir: webObserveWrapperStateDirFromStatus(status, options.stateDir) }),
index,
next: observerId === null ? null : webObserveNextCommands(observerId),
result: compactCommandResultWithStdoutTail(result),
valuesRedacted: true,
});
}
export function readNodeWebProbeObserveRemoteStatus(
options: NodeWebProbeObserveOptions,
spec: HwlabRuntimeLaneSpec,
tailLines: number,
timeoutSeconds: number,
): { result: ReturnType<typeof runTransWorkspaceStdinScript>; status: Record<string, unknown> | null } {
const script = [
"set -eu",
nodeWebObserveResolveStateDirShell(options),
nodeWebObserveStatusNodeScript(tailLines, options.node, options.lane),
].join("\n");
const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, timeoutSeconds);
return { result, status: parseJsonObject(result.stdout) };
}
export function webObserveText(value: unknown): string {
if (value === null || value === undefined || value === "") return "-";
if (typeof value === "string") return value;
if (typeof value === "number" || typeof value === "boolean") return String(value);
return JSON.stringify(value);
}
export function webObserveShort(value: string, maxLength: number): string {
if (value.length <= maxLength) return value;
if (maxLength <= 1) return value.slice(0, maxLength);
return `${value.slice(0, maxLength - 1)}~`;
}
export function runNodeWebProbeObserveCommand(options: NodeWebProbeObserveOptions, spec: HwlabRuntimeLaneSpec, stopCommand: boolean): Record<string, unknown> | RenderedCliResult {
const type = options.commandType ?? (stopCommand ? "stop" : null);
if (type === null) throw new Error("web-probe observe command requires --type");
const commandId = `cmd-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
const payload = {
id: commandId,
type,
createdAt: new Date().toISOString(),
source: "cli",
path: options.commandPath,
text: options.commandText,
label: options.commandLabel,
sessionId: options.commandSessionId,
provider: options.commandProvider,
afterRound: options.commandAfterRound,
severity: options.commandSeverity,
alternateSessionStrategy: options.commandAlternateSessionStrategy,
expectedSentinelRange: options.commandExpectedSentinelRange,
requireComposerReady: options.commandRequireComposerReady,
findingId: options.commandFindingId,
blocking: options.commandBlocking,
accountId: options.commandAccountId,
fromAccountId: options.commandFromAccountId,
toAccountId: options.commandToAccountId,
sourceId: options.commandSourceId,
fileRef: options.commandFileRef,
filename: options.commandFilename,
taskRef: options.commandTaskRef,
taskId: options.commandTaskId,
field: options.commandField,
link: options.commandLink,
title: options.commandTitle,
body: options.commandBody,
status: options.commandStatus,
hwpodId: options.commandHwpodId,
nodeId: options.commandNodeId,
workspaceRoot: options.commandWorkspaceRoot,
root: options.commandRoot,
};
const preStopStatus = options.force && stopCommand
? readNodeWebProbeObserveRemoteStatus(options, spec, 1, Math.min(options.commandTimeoutSeconds, 30))
: null;
const preStopDiagnostics = record(preStopStatus?.status?.diagnostics);
const preStopCommands = record(preStopStatus?.status?.commands);
const preStopPending = Number(preStopCommands?.pendingCount ?? 0);
const preStopProcessing = Number(preStopCommands?.processingCount ?? 0);
const forceBeforeQueueReason = options.force && stopCommand
? preStopDiagnostics?.heartbeatStale === true
? "heartbeat-stale"
: preStopPending > 0 || preStopProcessing > 0
? "command-backlog"
: null
: null;
if (forceBeforeQueueReason !== null) {
return runNodeWebProbeObserveForceStop(options, spec, payload, commandId, forceBeforeQueueReason, preStopStatus?.result ?? null, preStopStatus?.status ?? null, null);
}
const payloadB64 = Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
const waitMs = options.force && stopCommand ? Math.max(options.waitMs, 5000) : options.waitMs;
const script = [
"set -eu",
nodeWebObserveResolveStateDirShell(options),
"mkdir -p \"$state_dir/commands/pending\"",
`node -e "const fs=require('fs'),path=require('path'); const dir=process.argv[1], id=process.argv[2], payload=Buffer.from(process.argv[3], 'base64').toString('utf8'); fs.writeFileSync(path.join(dir,'commands','pending',id+'.json'), payload+'\\n', {mode:0o600});" "$state_dir" ${shellQuote(commandId)} ${shellQuote(payloadB64)}`,
nodeWebObserveWaitCommandShell(commandId, waitMs),
].join("\n");
const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds);
const commandResult = parseJsonObject(result.stdout);
if (options.force && stopCommand && (result.exitCode !== 0 || commandResult?.waitTimedOut === true || commandResult?.queued === true)) {
const reason = result.exitCode !== 0
? "graceful-stop-failed"
: commandResult?.waitTimedOut === true
? "graceful-stop-not-consumed"
: "graceful-stop-queued";
return runNodeWebProbeObserveForceStop(options, spec, payload, commandId, reason, preStopStatus?.result ?? null, preStopStatus?.status ?? null, result);
}
return withWebObserveCommandRendered({
ok: result.exitCode === 0 && commandResult?.ok !== false,
status: result.exitCode === 0 ? (waitMs > 0 ? "completed-or-queued" : "queued") : "blocked",
command: webObserveCommandLabel(stopCommand ? "stop" : "command", options),
id: webObserveIdFromOptions(options),
node: options.node,
lane: options.lane,
workspace: spec.workspace,
commandId,
observerCommand: commandSummaryForOutput(payload),
observer: commandResult,
wrapper: buildWebObserveWrapperForObserveOptions(stopCommand ? "stop" : "command", options, spec.workspace, { commandType: type }),
result: compactCommandResult(result),
full: options.full,
valuesRedacted: true,
});
}
export function runNodeWebProbeObserveForceStop(
options: NodeWebProbeObserveOptions,
spec: HwlabRuntimeLaneSpec,
payload: Record<string, unknown>,
commandId: string,
reason: string,
preflightResult: ReturnType<typeof runTransWorkspaceStdinScript> | null,
preflightStatus: Record<string, unknown> | null,
gracefulResult: ReturnType<typeof runTransWorkspaceStdinScript> | null,
): Record<string, unknown> | RenderedCliResult {
const killResult = runTransWorkspaceStdinScript(options.node, spec.workspace, [
"set -eu",
nodeWebObserveResolveStateDirShell(options),
nodeWebObserveForceStopNodeScript(reason, commandId),
].join("\n"), 55);
const forcePayload = parseJsonObject(killResult.stdout);
return withWebObserveCommandRendered({
ok: killResult.exitCode === 0 && forcePayload?.ok !== false,
status: killResult.exitCode === 0 && forcePayload?.ok !== false ? "forced-stopped" : "blocked",
command: webObserveCommandLabel("stop", options),
id: webObserveIdFromOptions(options),
node: options.node,
lane: options.lane,
workspace: spec.workspace,
commandId,
observerCommand: commandSummaryForOutput(payload),
observer: forcePayload,
wrapper: buildWebObserveWrapperForObserveOptions("stop", options, spec.workspace, { commandType: "stop" }),
forceReason: reason,
preflightObserver: preflightStatus,
preflightResult: preflightResult === null ? null : compactCommandResult(preflightResult),
gracefulResult: gracefulResult === null ? null : compactCommandResult(gracefulResult),
forceResult: compactCommandResultWithStdoutTail(killResult),
full: options.full,
valuesRedacted: true,
});
}
export function runNodeWebProbeObserveCollect(options: NodeWebProbeObserveOptions, spec: HwlabRuntimeLaneSpec): Record<string, unknown> | RenderedCliResult {
const collectScript = options.collectView === "files"
? nodeWebObserveCollectNodeScript(options.maxFiles, options.collectFile, options.collectFinding, options.collectGrep)
: nodeWebObserveCollectViewNodeScript({
maxFiles: options.maxFiles,
view: options.collectView,
traceId: options.collectTraceId,
sampleSeq: options.collectSampleSeq,
timestamp: options.collectTimestamp,
turn: options.collectTurn,
commandId: options.collectCommandId,
windowMs: options.collectWindowMs,
});
const script = [
"set -eu",
nodeWebObserveResolveStateDirShell(options),
collectScript,
].join("\n");
const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds);
const collect = parseJsonObject(result.stdout);
const compactRaw = options.raw && options.compactRaw;
const payload = {
ok: result.exitCode === 0 && collect !== null && collect.ok !== false,
status: result.exitCode === 0 && collect !== null ? "collected" : "blocked",
command: webObserveCommandLabel("collect", options),
id: webObserveIdFromOptions(options),
node: options.node,
lane: options.lane,
workspace: spec.workspace,
view: options.collectView,
requestedFile: options.collectFile,
requestedGrep: options.collectGrep,
requestedCommandId: options.collectCommandId,
requestedWindowMs: options.collectWindowMs,
degradedReason: collect === null ? "collect-json-parse-failed" : null,
collect: compactRaw ? compactObserveCollectForRaw(collect) : collect,
wrapper: compactRaw
? { mode: "wrapper-only", action: "collect", node: options.node, lane: options.lane, id: webObserveIdFromOptions(options), stateDir: options.stateDir, valuesRedacted: true }
: buildWebObserveWrapperForObserveOptions("collect", options, spec.workspace),
result: compactRaw ? { exitCode: result.exitCode, timedOut: result.timedOut, stdoutBytes: Buffer.byteLength(result.stdout), stderrBytes: Buffer.byteLength(result.stderr) } : collect === null ? compactCommandResultWithStdoutTail(result) : compactCommandResult(result),
valuesRedacted: true,
};
return options.raw ? payload : withWebObserveCollectRendered(payload);
}
function compactObserveCollectForRaw(collect: Record<string, unknown> | null): Record<string, unknown> | null {
if (collect === null) return null;
const rows = Array.isArray(collect.rows) ? collect.rows.map((item) => {
const row = observeRecord(item);
const finalResponse = observeRecord(row.finalResponse);
return {
round: row.round ?? null,
commandId: row.commandId ?? null,
userHash: row.userHash ?? null,
userBytes: row.userBytes ?? null,
traceId: row.traceId ?? null,
status: row.status ?? null,
elapsedSeconds: row.elapsedSeconds ?? null,
recentUpdateSeconds: row.recentUpdateSeconds ?? null,
marks: row.marks ?? null,
firstSeq: row.firstSeq ?? null,
lastSeq: row.lastSeq ?? null,
lastTs: row.lastTs ?? null,
finalResponse: {
preview: finalResponse.preview ?? null,
textHash: finalResponse.textHash ?? null,
textBytes: finalResponse.textBytes ?? null,
empty: finalResponse.empty === true,
},
valuesRedacted: true,
};
}) : undefined;
const timelineRows = Array.isArray(collect.timelineRows) ? collect.timelineRows.slice(0, 12).map((item) => {
const row = observeRecord(item);
return {
ts: row.ts ?? null,
seq: row.seq ?? null,
kind: row.kind ?? null,
phase: row.phase ?? null,
type: row.type ?? null,
commandId: row.commandId ?? null,
sessionId: row.sessionId ?? null,
traceId: row.traceId ?? null,
summary: row.summary ?? null,
valuesRedacted: true,
};
}) : undefined;
return {
ok: collect.ok !== false,
command: collect.command,
view: collect.view,
stateDir: collect.stateDir,
turnCount: collect.turnCount,
anchor: observeRecord(collect.anchor),
window: observeRecord(collect.window),
counts: observeRecord(collect.counts),
...(rows === undefined ? {} : { rows }),
...(timelineRows === undefined ? {} : { timelineRows }),
renderedText: collect.view === "timeline" ? undefined : typeof collect.renderedText === "string" ? collect.renderedText : undefined,
sourceFiles: Array.isArray(collect.sourceFiles) ? collect.sourceFiles : undefined,
blocker: collect.blocker,
sampleSeq: collect.sampleSeq,
traceId: collect.traceId,
finalResponse: collect.finalResponse,
valuesRedacted: true,
};
}
function observeRecord(value: unknown): Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {};
}
export function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec: HwlabRuntimeLaneSpec): Record<string, unknown> | RenderedCliResult {
const analyzerB64 = Buffer.from(nodeWebObserveAnalyzerSource(), "utf8").toString("base64");
const alertThresholds = nodeWebProbeAlertThresholds(spec);
const projectManagement = nodeWebProbeProjectManagementConfig(spec);
const script = [
"set -eu",
nodeWebObserveResolveStateDirShell(options),
"analyzer=\"$state_dir/observer-analyzer.mjs\"",
"analyzer_b64=\"$state_dir/observer-analyzer.mjs.b64\"",
"cat >\"$analyzer_b64\" <<'UNIDESK_WEB_OBSERVE_ANALYZER_B64'",
analyzerB64,
"UNIDESK_WEB_OBSERVE_ANALYZER_B64",
"node -e \"const fs=require('fs'); fs.writeFileSync(process.argv[1], Buffer.from(fs.readFileSync(process.argv[2], 'utf8').replace(/\\s+/g, ''), 'base64'))\" \"$analyzer\" \"$analyzer_b64\"",
"rm -f \"$analyzer_b64\"",
"chmod 700 \"$analyzer\"",
"mkdir -p \"$state_dir/analysis\"",
"analysis_stdout=\"$state_dir/analysis/analyzer-stdout.json\"",
"analysis_stderr=\"$state_dir/analysis/analyzer-stderr.log\"",
"set +e",
`UNIDESK_WEB_OBSERVE_ANALYZE_ARCHIVE_PREFIX=${shellQuote(options.analyzeArchivePrefix ?? "")}`,
`UNIDESK_WEB_OBSERVE_ANALYZE_TAIL_SAMPLES=${shellQuote(options.analyzeTailSamples === null ? "" : String(options.analyzeTailSamples))}`,
`UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON=${shellQuote(JSON.stringify(alertThresholds))}`,
`UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON=${shellQuote(JSON.stringify(projectManagement))}`,
"UNIDESK_WEB_OBSERVE_STATE_DIR=\"$state_dir\" UNIDESK_WEB_OBSERVE_ANALYZE_ARCHIVE_PREFIX=\"$UNIDESK_WEB_OBSERVE_ANALYZE_ARCHIVE_PREFIX\" UNIDESK_WEB_OBSERVE_ANALYZE_TAIL_SAMPLES=\"$UNIDESK_WEB_OBSERVE_ANALYZE_TAIL_SAMPLES\" UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON=\"$UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON\" UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON=\"$UNIDESK_WEB_OBSERVE_PROJECT_MANAGEMENT_JSON\" node \"$analyzer\" >\"$analysis_stdout\" 2>\"$analysis_stderr\"",
"analyzer_exit=$?",
"set -e",
"report_json=\"$state_dir/analysis/report.json\"",
"report_md=\"$state_dir/analysis/report.md\"",
"node - \"$analysis_stdout\" \"$analysis_stderr\" \"$report_json\" \"$report_md\" \"$analyzer_exit\" <<'UNIDESK_WEB_OBSERVE_ANALYZE_COMPACT'",
"const fs = require('fs');",
"const crypto = require('crypto');",
"const [stdoutPath, stderrPath, reportJsonPath, reportMdPath, analyzerExitRaw] = process.argv.slice(2);",
"const analyzerExit = Number(analyzerExitRaw);",
"const readText = (path) => { try { return fs.readFileSync(path, 'utf8'); } catch { return ''; } };",
"const readJson = (path) => { const text = readText(path); if (!text.trim()) return null; try { return JSON.parse(text); } catch { return null; } };",
"const sha256 = (path) => { const text = readText(path); return text ? 'sha256:' + crypto.createHash('sha256').update(text).digest('hex') : null; };",
"const statSize = (path) => { try { return fs.statSync(path).size; } catch { return 0; } };",
"const tail = (text, limit = 1200) => String(text || '').slice(-limit);",
"const takeHead = (value, limit) => Array.isArray(value) ? value.slice(0, limit) : [];",
"const takeTail = (value, limit) => Array.isArray(value) ? value.slice(-limit) : [];",
"const firstArray = (...values) => { for (const value of values) if (Array.isArray(value)) return value; return []; };",
"const firstNonEmptyArray = (...values) => { for (const value of values) if (Array.isArray(value) && value.length > 0) return value; return firstArray(...values); };",
"const readJsonlTail = (path, limit) => readText(path).split(/\\r?\\n/).filter(Boolean).slice(-limit).map((line) => { try { return JSON.parse(line); } catch { return null; } }).filter(Boolean);",
"const mergeArrays = (...values) => { const out = []; const seen = new Set(); for (const value of values) { if (!Array.isArray(value)) continue; for (const item of value) { const key = JSON.stringify([item?.id ?? item?.kind ?? item?.code ?? item?.columnLabel ?? item?.traceId ?? null, item?.path ?? item?.urlPath ?? null, item?.method ?? null, item?.status ?? null, item?.summary ?? item?.message ?? item?.fromSeq ?? item?.firstAt ?? null, item?.toSeq ?? item?.lastAt ?? null]); if (seen.has(key)) continue; seen.add(key); out.push(item); } } return out; };",
"const clip = (value, limit = 160) => value === null || value === undefined ? null : String(value).slice(0, limit);",
"const findingRank = (item) => { const id = String(item?.id ?? item?.kind ?? item?.code ?? ''); if (id.startsWith('project-management-') || id.startsWith('mdtodo-') || id === 'workbench-launch-button-unavailable') return 0; if (id === 'observer-command-failed') return 0.2; if (id === 'page-performance-slow-same-origin-api') return 1; if (id === 'session-rail-title-fallback-majority') return 2; if (id.startsWith('turn-timing-total-elapsed')) return 3; if (id.startsWith('turn-timing-recent-update')) return 4; if (id.includes('runtime-execution') || id.includes('prompt-chat-submit-failed')) return 5; return 10; };",
"const severityRank = (item) => { const severity = String(item?.severity ?? item?.level ?? '').toLowerCase(); if (severity === 'red') return 0; if (severity === 'amber' || severity === 'warning') return 1; if (severity === 'info') return 3; return 2; };",
"const sortFindings = (items) => (Array.isArray(items) ? items : []).slice().sort((a, b) => findingRank(a) - findingRank(b) || severityRank(a) - severityRank(b));",
"const stdoutJson = readJson(stdoutPath);",
"const reportJson = readJson(reportJsonPath);",
"const source = (stdoutJson && stdoutJson.ok !== false ? stdoutJson : null) || reportJson || stdoutJson || null;",
"const fullSource = reportJson || source;",
"const objectOrNull = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : null;",
"const slimRound = (item) => { const v = objectOrNull(item) || {}; return { promptIndex: v.promptIndex ?? null, sampleCount: v.sampleCount ?? null, loadingSamples: v.loadingSamples ?? null, maxLoadingCount: v.maxLoadingCount ?? null, loadingOwnerCount: v.loadingOwnerCount ?? null, lastTotalElapsedSeconds: v.lastTotalElapsedSeconds ?? null, lastRecentUpdateSeconds: v.lastRecentUpdateSeconds ?? null, diagnosticSamples: v.diagnosticSamples ?? null, terminalSamples: v.terminalSamples ?? null, turnTimingTotalElapsedForwardJumpCount: v.turnTimingTotalElapsedForwardJumpCount ?? null, turnTimingTotalElapsedForwardJumpMaxSeconds: v.turnTimingTotalElapsedForwardJumpMaxSeconds ?? null, promptTextHash: clip(v.promptTextHash, 80) }; };",
"const slimTurnColumn = (item) => { const v = objectOrNull(item) || {}; return { label: clip(v.label, 24), pageRole: clip(v.pageRole, 24), pageId: clip(v.pageId, 32), pageEpoch: v.pageEpoch ?? null, promptIndex: v.promptIndex ?? null, lastPromptIndex: v.lastPromptIndex ?? null, traceId: clip(v.traceId, 48), firstSeq: v.firstSeq ?? null, lastSeq: v.lastSeq ?? null, source: clip(v.source, 48) }; };",
"const slimSlowSample = (item) => { const v = objectOrNull(item) || {}; return { ts: v.ts ?? null, seq: v.seq ?? null, path: clip(v.path ?? v.rawPath, 96), initiatorType: clip(v.initiatorType, 24), durationMs: v.durationMs ?? null, requestToResponseStartMs: v.requestToResponseStartMs ?? v.streamOpenMs ?? null, responseTransferMs: v.responseTransferMs ?? null, nextHopProtocol: clip(v.nextHopProtocol, 24), timingStatus: clip(v.timingStatus, 16), serverTimingNames: Array.isArray(v.serverTimingNames) ? v.serverTimingNames.slice(0, 4).map((x) => clip(x, 32)) : [], otelTraceId: clip(v.otelTraceId, 32) }; };",
"const slimSlowApi = (item) => { const v = objectOrNull(item) || {}; return { path: clip(v.path ?? v.route, 96), route: clip(v.route ?? v.path, 96), sampleCount: v.sampleCount ?? null, p95Ms: v.p95Ms ?? v.p95 ?? null, maxMs: v.maxMs ?? v.max ?? null, budgetMs: v.budgetMs ?? null, overBudgetCount: v.overBudgetCount ?? null, overFiveSecondCount: v.overFiveSecondCount ?? null, slowSamples: Array.isArray(v.slowSamples) ? v.slowSamples.slice(0, 3).map(slimSlowSample) : [] }; };",
"const slimFinding = (item) => { const v = objectOrNull(item) || {}; return { kind: clip(v.kind ?? v.id ?? v.code, 48), code: clip(v.code ?? v.id ?? v.kind, 48), severity: clip(v.severity ?? v.level, 24), level: clip(v.level ?? v.severity, 24), count: v.count ?? v.sampleCount ?? null, sampleCount: v.sampleCount ?? v.count ?? null, summary: clip(v.summary ?? v.message, 180), message: clip(v.message ?? v.summary, 180) }; };",
"const slimProjectManagement = (value) => { const v = objectOrNull(value); if (!v) return null; const s = objectOrNull(v.summary) || v; return { summary: { enabled: s.enabled === true, projectSampleCount: s.projectSampleCount ?? null, mdtodoSampleCount: s.mdtodoSampleCount ?? null, latestPageKind: clip(s.latestPageKind, 48), latestPath: clip(s.latestPath, 96), latestSourceCount: s.latestSourceCount ?? null, latestFileCount: s.latestFileCount ?? null, latestTaskCount: s.latestTaskCount ?? null, latestSelectedTaskRefHash: clip(s.latestSelectedTaskRefHash, 80), launchCommandCount: s.launchCommandCount ?? null, launchSuccessCount: s.launchSuccessCount ?? null, launchFailureCount: s.launchFailureCount ?? null, launchWithOtelTraceHeaderCount: s.launchWithOtelTraceHeaderCount ?? null, projectApiResponseCount: s.projectApiResponseCount ?? null, projectApiFailureCount: s.projectApiFailureCount ?? null, projectApiSlowPathCount: s.projectApiSlowPathCount ?? null, slowApiBudgetMs: s.slowApiBudgetMs ?? null }, commands: takeTail(v.commands, 8).map((item) => { const row = objectOrNull(item) || {}; return { ts: row.ts ?? null, phase: clip(row.phase, 16), type: clip(row.type, 32), commandId: clip(row.commandId, 80), launchStatus: row.launchStatus ?? null, sessionId: clip(row.sessionId, 80), workbenchUrl: clip(row.workbenchUrl, 120), otelTraceId: clip(row.otelTraceId, 32), selectedTaskRefHash: clip(row.selectedTaskRefHash, 80) }; }), samples: takeTail(v.samples, 8).map((item) => { const row = objectOrNull(item) || {}; return { seq: row.seq ?? null, ts: row.ts ?? null, pageRole: clip(row.pageRole, 24), path: clip(row.path, 96), pageKind: clip(row.pageKind, 48), sourceCount: row.sourceCount ?? null, fileCount: row.fileCount ?? null, taskCount: row.taskCount ?? null, selectedTaskRefHash: clip(row.selectedTaskRefHash, 80), launchButtonEnabled: row.launchButtonEnabled === true, workbenchLinkCount: row.workbenchLinkCount ?? null }; }), projectApiByPath: takeHead(v.projectApiByPath, 8).map(slimNetworkGroup), valuesRedacted: true }; };",
"const slimDomGroup = (item) => { const v = objectOrNull(item) || {}; return { count: v.count ?? null, firstAt: v.firstAt ?? null, lastAt: v.lastAt ?? null, text: clip(v.text ?? v.preview, 180) }; };",
"const slimNetworkGroup = (item) => { const v = objectOrNull(item) || {}; return { count: v.count ?? null, method: clip(v.method, 12), status: v.status ?? null, path: clip(v.path ?? v.urlPath, 96), firstAt: v.firstAt ?? null, lastAt: v.lastAt ?? null, promptIndexes: Array.isArray(v.promptIndexes) ? v.promptIndexes.slice(0, 6) : [], failureKinds: Array.isArray(v.failureKinds) ? v.failureKinds.slice(0, 4).map((x) => clip(x, 48)) : [] }; };",
"const slimDomSample = (item) => { const v = objectOrNull(item) || {}; return { seq: v.seq ?? null, ts: v.ts ?? null, source: clip(v.source, 32), diagnosticCode: clip(v.diagnosticCode, 48), traceId: clip(v.traceId, 64), httpStatus: v.httpStatus ?? null, idleSeconds: v.idleSeconds ?? null, waitingFor: clip(v.waitingFor, 48), lastEventLabel: clip(v.lastEventLabel, 80), text: clip(v.text ?? v.preview, 180) }; };",
"const slimConsoleGroup = (item) => { const v = objectOrNull(item) || {}; return { count: v.count ?? null, type: clip(v.type, 24), status: v.status ?? null, path: clip(v.path ?? v.urlPath, 96), lastAt: v.lastAt ?? v.firstAt ?? null, firstAt: v.firstAt ?? null, traceIds: Array.isArray(v.traceIds) ? v.traceIds.slice(0, 3).map((x) => clip(x, 64)) : [] }; };",
"const slimConsoleSample = (item) => { const v = objectOrNull(item) || {}; return { ts: v.ts ?? null, type: clip(v.type, 24), status: v.status ?? null, path: clip(v.path ?? v.urlPath, 96), traceId: clip(v.traceId, 64), text: clip(v.text ?? v.preview, 180) }; };",
"const slimRunnerError = (item) => { const v = objectOrNull(item) || {}; const readiness = objectOrNull(v.lastReadiness); return { ts: v.ts ?? null, type: clip(v.type, 32), commandId: clip(v.commandId, 80), sampleSeq: v.sampleSeq ?? null, message: clip(v.message, 240), retry: clip(v.retry, 24), retryExhausted: v.retryExhausted === true, lastError: clip(v.lastError, 180), attemptCount: v.attemptCount ?? null, lastFailureKind: clip(v.lastFailureKind, 48), lastReadinessReason: clip(v.lastReadinessReason, 48), lastReadiness: readiness ? { reason: clip(readiness.reason, 48), path: clip(readiness.path, 96), readyState: clip(readiness.readyState, 24), workbenchShellVisible: readiness.workbenchShellVisible === true, sessionCreateVisible: readiness.sessionCreateVisible === true, commandInputPresent: readiness.commandInputPresent === true, activeTabPresent: readiness.activeTabPresent === true, warningPresent: readiness.warningPresent === true, loginVisible: readiness.loginVisible === true, bodyTextHash: clip(readiness.bodyTextHash, 80) } : null }; };",
"const slimRunnerErrorFromJsonl = (item) => { const v = objectOrNull(item) || {}; const error = objectOrNull(v.error) || {}; const attempts = Array.isArray(error.attempts) ? error.attempts : []; const lastAttempt = attempts.length > 0 ? objectOrNull(attempts[attempts.length - 1]) || {} : {}; const rawReadiness = objectOrNull(lastAttempt.readiness) || objectOrNull(error.navigationReadiness); const readiness = objectOrNull(rawReadiness?.snapshot) || rawReadiness; return { ts: v.ts ?? null, type: v.type ?? null, commandId: v.commandId ?? null, sampleSeq: v.sampleSeq ?? null, message: error.message ?? v.message ?? null, attemptCount: attempts.length, lastFailureKind: lastAttempt.failureKind ?? null, lastReadinessReason: rawReadiness?.reason ?? readiness?.reason ?? null, lastReadiness: readiness ?? null }; };",
"const slimCommandFailure = (item) => { const v = objectOrNull(item) || {}; return { ts: v.ts ?? null, commandId: clip(v.commandId, 80), type: clip(v.type, 32), source: clip(v.source, 24), durationMs: v.durationMs ?? null, beforePath: clip(v.beforePath, 80), afterPath: clip(v.afterPath, 80), name: clip(v.name, 48), failureKind: clip(v.failureKind, 48), sampleSeq: v.sampleSeq ?? null, failureSampleOk: v.failureSampleOk === true, message: clip(v.message, 240) }; };",
"const slimJump = (item) => { const v = objectOrNull(item) || {}; return { columnLabel: v.columnLabel ?? null, pageRole: clip(v.pageRole, 24), pageId: clip(v.pageId, 32), pageEpoch: v.pageEpoch ?? null, promptIndex: v.promptIndex ?? null, fromSeq: v.fromSeq ?? null, toSeq: v.toSeq ?? null, fromValue: v.fromValue ?? null, toValue: v.toValue ?? null, delta: v.delta ?? null, sampleDeltaSeconds: v.sampleDeltaSeconds ?? null, allowedIncreaseSeconds: v.allowedIncreaseSeconds ?? null, traceId: v.traceId ?? null }; };",
"const slimTraceOrderAnomaly = (item) => { const v = objectOrNull(item) || {}; return { sampleIndex: v.sampleIndex ?? v.seq ?? null, seq: v.seq ?? null, timestamp: v.timestamp ?? v.ts ?? null, pageRole: clip(v.pageRole, 24), traceId: clip(v.traceId, 64), previousRowIndex: v.previousRowIndex ?? null, currentRowIndex: v.currentRowIndex ?? null, reasons: Array.isArray(v.reasons) ? v.reasons.slice(0, 6).map((x) => clip(x, 48)) : [], previousTotalSeconds: v.previousTotalSeconds ?? null, currentTotalSeconds: v.currentTotalSeconds ?? null, previousClockSeconds: v.previousClockSeconds ?? null, currentClockSeconds: v.currentClockSeconds ?? null, previousPreview: clip(v.previousPreview, 180), currentPreview: clip(v.currentPreview, 180) }; };",
"const slimTraceOrderMetrics = (value) => { const v = objectOrNull(value); if (!v) return null; const s = objectOrNull(v.summary); return { summary: { sampleCount: v.sampleCount ?? s?.sampleCount ?? null, traceRowCount: v.traceRowCount ?? s?.traceRowCount ?? null, orderAnomalyCount: v.orderAnomalyCount ?? s?.orderAnomalyCount ?? (Array.isArray(v.orderAnomalies) ? v.orderAnomalies.length : null), completionNotLastCount: v.completionNotLastCount ?? s?.completionNotLastCount ?? (Array.isArray(v.completionNotLast) ? v.completionNotLast.length : null) }, orderAnomalies: takeHead(v.orderAnomalies, 8).map(slimTraceOrderAnomaly), valuesRedacted: true }; };",
"const slimLoadingOwner = (item) => { const v = objectOrNull(item) || {}; return { ownerKey: clip(v.ownerKey, 120), ownerKind: clip(v.ownerKind, 32), ownerLabel: clip(v.ownerLabel, 120), ownerTraceId: clip(v.ownerTraceId, 64), ownerMessageId: clip(v.ownerMessageId, 64), ownerSessionId: clip(v.ownerSessionId, 64), sampleCount: v.sampleCount ?? null, occurrenceCount: v.occurrenceCount ?? null, maxSimultaneousCount: v.maxSimultaneousCount ?? null, longestContinuousSeconds: v.longestContinuousSeconds ?? null, firstSeq: v.firstSeq ?? null, lastSeq: v.lastSeq ?? null, promptIndexes: Array.isArray(v.promptIndexes) ? v.promptIndexes.slice(0, 8) : [] }; };",
"const slimLoadingSegment = (item) => { const v = objectOrNull(item) || {}; return { firstAt: v.firstAt ?? null, lastAt: v.lastAt ?? null, endedAt: v.endedAt ?? null, firstSeq: v.firstSeq ?? null, lastSeq: v.lastSeq ?? null, durationSeconds: v.durationSeconds ?? null, upperBoundSeconds: v.upperBoundSeconds ?? null, endedGapSeconds: v.endedGapSeconds ?? null, sampleCount: v.sampleCount ?? null, maxCount: v.maxCount ?? null, ownerCount: v.ownerCount ?? null, ongoing: v.ongoing === true, owners: Array.isArray(v.owners) ? v.owners.slice(0, 6).map((owner) => ({ ownerKind: clip(owner?.ownerKind, 32), ownerLabel: clip(owner?.ownerLabel, 120), ownerTraceId: clip(owner?.ownerTraceId, 64), ownerMessageId: clip(owner?.ownerMessageId, 64), ownerSessionId: clip(owner?.ownerSessionId, 64), count: owner?.count ?? null })) : [] }; };",
"const slimLoadingMetrics = (value) => { const v = objectOrNull(value); if (!v) return null; return { summary: objectOrNull(v.summary), longestSegments: takeHead(v.longestSegments ?? v.segments, 8).map(slimLoadingSegment), owners: takeHead(v.owners, 8).map(slimLoadingOwner), timeline: takeTail(v.timeline, 12).map((item) => { const row = objectOrNull(item) || {}; return { seq: row.seq ?? null, ts: row.ts ?? null, promptIndex: row.promptIndex ?? null, loadingCount: row.loadingCount ?? null, ownerCount: row.ownerCount ?? null, owners: Array.isArray(row.owners) ? row.owners.slice(0, 6).map((owner) => ({ ownerKind: clip(owner?.ownerKind, 32), ownerLabel: clip(owner?.ownerLabel, 120), ownerTraceId: clip(owner?.ownerTraceId, 64), count: owner?.count ?? null })) : [] }; }), valuesRedacted: true }; };",
"const srcMetrics = objectOrNull(source?.sampleMetrics);",
"const fullRecentWindow = objectOrNull(fullSource?.windows?.recent);",
"const fullRecentMetrics = objectOrNull(fullRecentWindow?.sampleMetrics);",
"const fullArchiveMetrics = objectOrNull(fullSource?.sampleMetrics);",
"const metricHasTurnDetail = (value) => { const v = objectOrNull(value); const s = objectOrNull(v?.summary); return !!(v && ((Array.isArray(v.roundItems) && v.roundItems.length > 0) || (Array.isArray(v.rounds) && v.rounds.length > 0) || (Array.isArray(v.turnColumns) && v.turnColumns.length > 0) || (Array.isArray(v.turnTimingRows) && v.turnTimingRows.length > 0) || (Array.isArray(v.turnTimingTotalElapsedForwardJumps) && v.turnTimingTotalElapsedForwardJumps.length > 0) || (Array.isArray(v.turnTimingRecentUpdateSawtoothJumps) && v.turnTimingRecentUpdateSawtoothJumps.length > 0) || Number(v.turnTimingRows ?? s?.turnTimingRows ?? 0) > 0)); };",
"const selectedMetrics = metricHasTurnDetail(srcMetrics) ? srcMetrics : metricHasTurnDetail(fullRecentMetrics) ? fullRecentMetrics : metricHasTurnDetail(fullArchiveMetrics) ? fullArchiveMetrics : srcMetrics || fullRecentMetrics || fullArchiveMetrics;",
"const selectedSummary = objectOrNull(selectedMetrics?.summary);",
"const metrics = selectedMetrics ? {",
" sampleCount: selectedMetrics.sampleCount ?? selectedSummary?.sampleCount ?? null,",
" loadingSampleCount: selectedMetrics.loadingSampleCount ?? selectedSummary?.loadingSampleCount ?? null,",
" loadingMaxCount: selectedMetrics.loadingMaxCount ?? selectedSummary?.loadingMaxCount ?? null,",
" loadingMaxOwnerCount: selectedMetrics.loadingMaxOwnerCount ?? selectedSummary?.loadingMaxOwnerCount ?? null,",
" loadingOwnerCount: selectedMetrics.loadingOwnerCount ?? selectedSummary?.loadingOwnerCount ?? null,",
" loadingConcurrentSampleCount: selectedMetrics.loadingConcurrentSampleCount ?? selectedSummary?.loadingConcurrentSampleCount ?? null,",
" loadingLongestContinuousSeconds: selectedMetrics.loadingLongestContinuousSeconds ?? selectedSummary?.loadingLongestContinuousSeconds ?? null,",
" loadingCurrentContinuousSeconds: selectedMetrics.loadingCurrentContinuousSeconds ?? selectedSummary?.loadingCurrentContinuousSeconds ?? null,",
" loadingOverFiveSecondSegmentCount: selectedMetrics.loadingOverFiveSecondSegmentCount ?? selectedSummary?.loadingOverFiveSecondSegmentCount ?? null,",
" loading: slimLoadingMetrics(selectedMetrics.loading),",
" traceOrder: slimTraceOrderMetrics(selectedMetrics.traceOrder ?? fullRecentMetrics?.traceOrder ?? fullArchiveMetrics?.traceOrder),",
" roundItems: takeTail(firstNonEmptyArray(selectedMetrics.roundItems, selectedMetrics.rounds, fullRecentMetrics?.rounds, fullArchiveMetrics?.rounds), 8).map(slimRound),",
" rounds: takeTail(firstNonEmptyArray(selectedMetrics.roundItems, selectedMetrics.rounds, fullRecentMetrics?.rounds, fullArchiveMetrics?.rounds), 8).map(slimRound),",
" turnColumns: takeTail(firstNonEmptyArray(selectedMetrics.turnColumns, fullRecentMetrics?.turnColumns, fullArchiveMetrics?.turnColumns), 8).map(slimTurnColumn),",
" turnTimingRows: selectedMetrics.turnTimingRows ?? selectedSummary?.turnTimingRows ?? fullRecentMetrics?.summary?.turnTimingRows ?? fullRecentMetrics?.turnTimingRows?.length ?? fullArchiveMetrics?.summary?.turnTimingRows ?? fullArchiveMetrics?.turnTimingRows?.length ?? null,",
" turnTimingNonMonotonicCount: selectedMetrics.turnTimingNonMonotonicCount ?? selectedSummary?.turnTimingNonMonotonicCount ?? fullRecentMetrics?.summary?.turnTimingNonMonotonicCount ?? fullRecentMetrics?.turnTimingNonMonotonic?.length ?? fullArchiveMetrics?.summary?.turnTimingNonMonotonicCount ?? fullArchiveMetrics?.turnTimingNonMonotonic?.length ?? null,",
" turnTimingTotalElapsedDecreaseCount: selectedMetrics.turnTimingTotalElapsedDecreaseCount ?? selectedSummary?.turnTimingTotalElapsedDecreaseCount ?? fullRecentMetrics?.summary?.turnTimingTotalElapsedDecreaseCount ?? fullArchiveMetrics?.summary?.turnTimingTotalElapsedDecreaseCount ?? null,",
" turnTimingTotalElapsedZeroResetCount: selectedMetrics.turnTimingTotalElapsedZeroResetCount ?? selectedSummary?.turnTimingTotalElapsedZeroResetCount ?? fullRecentMetrics?.summary?.turnTimingTotalElapsedZeroResetCount ?? fullRecentMetrics?.turnTimingElapsedZeroResets?.length ?? fullArchiveMetrics?.summary?.turnTimingTotalElapsedZeroResetCount ?? fullArchiveMetrics?.turnTimingElapsedZeroResets?.length ?? null,",
" turnTimingTotalElapsedForwardJumpCount: selectedMetrics.turnTimingTotalElapsedForwardJumpCount ?? selectedSummary?.turnTimingTotalElapsedForwardJumpCount ?? fullRecentMetrics?.summary?.turnTimingTotalElapsedForwardJumpCount ?? fullRecentMetrics?.turnTimingTotalElapsedForwardJumps?.length ?? fullArchiveMetrics?.summary?.turnTimingTotalElapsedForwardJumpCount ?? fullArchiveMetrics?.turnTimingTotalElapsedForwardJumps?.length ?? null,",
" turnTimingTotalElapsedForwardJumpMaxSeconds: selectedMetrics.turnTimingTotalElapsedForwardJumpMaxSeconds ?? selectedSummary?.turnTimingTotalElapsedForwardJumpMaxSeconds ?? fullRecentMetrics?.summary?.turnTimingTotalElapsedForwardJumpMaxSeconds ?? fullArchiveMetrics?.summary?.turnTimingTotalElapsedForwardJumpMaxSeconds ?? null,",
" turnTimingTerminalElapsedGrowthCount: selectedMetrics.turnTimingTerminalElapsedGrowthCount ?? selectedSummary?.turnTimingTerminalElapsedGrowthCount ?? fullRecentMetrics?.summary?.turnTimingTerminalElapsedGrowthCount ?? fullRecentMetrics?.turnTimingTerminalElapsedGrowth?.length ?? fullArchiveMetrics?.summary?.turnTimingTerminalElapsedGrowthCount ?? fullArchiveMetrics?.turnTimingTerminalElapsedGrowth?.length ?? null,",
" turnTimingRecentUpdateJumpCount: selectedMetrics.turnTimingRecentUpdateJumpCount ?? selectedSummary?.turnTimingRecentUpdateJumpCount ?? selectedSummary?.turnTimingRecentUpdateSawtoothJumpCount ?? fullRecentMetrics?.summary?.turnTimingRecentUpdateJumpCount ?? fullRecentMetrics?.summary?.turnTimingRecentUpdateSawtoothJumpCount ?? fullArchiveMetrics?.summary?.turnTimingRecentUpdateJumpCount ?? fullArchiveMetrics?.summary?.turnTimingRecentUpdateSawtoothJumpCount ?? null,",
" turnTimingRecentUpdateSawtoothJumpCount: selectedMetrics.turnTimingRecentUpdateSawtoothJumpCount ?? selectedSummary?.turnTimingRecentUpdateSawtoothJumpCount ?? fullRecentMetrics?.summary?.turnTimingRecentUpdateSawtoothJumpCount ?? fullArchiveMetrics?.summary?.turnTimingRecentUpdateSawtoothJumpCount ?? null,",
" turnTimingRecentUpdateMaxIncreaseSeconds: selectedMetrics.turnTimingRecentUpdateMaxIncreaseSeconds ?? selectedSummary?.turnTimingRecentUpdateMaxIncreaseSeconds ?? fullRecentMetrics?.summary?.turnTimingRecentUpdateMaxIncreaseSeconds ?? fullArchiveMetrics?.summary?.turnTimingRecentUpdateMaxIncreaseSeconds ?? null,",
" promptSegments: selectedMetrics.promptSegments ?? selectedSummary?.promptSegments ?? null",
"} : null;",
"const srcRuntimeAlerts = objectOrNull(source?.runtimeAlerts);",
"const runtimeAlerts = srcRuntimeAlerts ? { httpErrorCount: srcRuntimeAlerts.httpErrorCount ?? null, requestFailedCount: srcRuntimeAlerts.requestFailedCount ?? null, domDiagnosticSampleCount: srcRuntimeAlerts.domDiagnosticSampleCount ?? null, consoleAlertCount: srcRuntimeAlerts.consoleAlertCount ?? null } : null;",
"const srcPagePerformance = objectOrNull(source?.pagePerformance);",
"const srcPagePerformanceSummary = objectOrNull(srcPagePerformance?.summary);",
"const pagePerformance = srcPagePerformance ? { sameOriginApiPaths: srcPagePerformance.sameOriginApiPaths ?? srcPagePerformanceSummary?.sameOriginApiPathCount ?? null, longLivedStreamPathCount: srcPagePerformance.longLivedStreamPathCount ?? srcPagePerformanceSummary?.longLivedStreamPathCount ?? null, longLivedStreamOpenOverFiveSecondSampleCount: srcPagePerformance.longLivedStreamOpenOverFiveSecondSampleCount ?? srcPagePerformanceSummary?.longLivedStreamOpenOverFiveSecondSampleCount ?? null, slowPathCount: srcPagePerformance.slowPathCount ?? srcPagePerformanceSummary?.slowPathCount ?? null, slowSampleCount: srcPagePerformance.slowSampleCount ?? srcPagePerformanceSummary?.slowSampleCount ?? null, worstP95Ms: srcPagePerformance.worstP95Ms ?? srcPagePerformanceSummary?.worstP95Ms ?? null } : null;",
"const fullPagePerformance = objectOrNull(fullRecentWindow?.pagePerformance) || objectOrNull(fullSource?.pagePerformance);",
"const fullArchivePagePerformance = objectOrNull(fullSource?.pagePerformance);",
"const isLongLivedApi = (item) => item?.isLongLivedStream === true || item?.routeKind === 'same-origin-api-stream' || item?.budgetMetric === 'streamOpenMs';",
"const sourceSlowApi = Array.isArray(source?.pagePerformanceSlowApi) ? source.pagePerformanceSlowApi.filter((item) => !isLongLivedApi(item) && Number(item?.overBudgetCount ?? item?.overFiveSecondCount ?? 0) > 0) : (Array.isArray(fullPagePerformance?.sameOriginApiByPath) ? fullPagePerformance.sameOriginApiByPath.filter((item) => !isLongLivedApi(item) && Number(item?.overBudgetCount ?? item?.overFiveSecondCount ?? 0) > 0) : []);",
"const archiveSlowApi = firstNonEmptyArray(source?.archivePagePerformanceSlowApi, Array.isArray(fullArchivePagePerformance?.sameOriginApiByPath) ? fullArchivePagePerformance.sameOriginApiByPath : []).filter((item) => !isLongLivedApi(item) && Number(item?.overBudgetCount ?? item?.overFiveSecondCount ?? 0) > 0);",
"const sourceSseStreams = Array.isArray(source?.pagePerformanceSseStreams) ? source.pagePerformanceSseStreams : (Array.isArray(fullPagePerformance?.sameOriginApiByPath) ? fullPagePerformance.sameOriginApiByPath.filter((item) => isLongLivedApi(item)) : []);",
"const combinedFindingSource = firstNonEmptyArray(source?.findings, fullSource?.findings, fullRecentWindow?.findings, source?.archiveSummary?.redFindings, fullSource?.archiveSummary?.redFindings);",
"const combinedFindings = sortFindings(combinedFindingSource);",
"const toolFindings = sortFindings([...firstArray(source?.toolFindings), ...firstArray(fullSource?.toolFindings), ...combinedFindingSource.filter((item) => String(item?.id ?? item?.kind ?? item?.code ?? '').startsWith('tool-'))]);",
"const archiveRedFindings = sortFindings(firstNonEmptyArray(source?.archiveSummary?.redFindings, fullSource?.archiveSummary?.redFindings, fullSource?.findings).filter((item) => String(item?.severity ?? item?.level ?? '').toLowerCase() === 'red'));",
"const allFindingsForCommands = [...firstArray(source?.findings), ...firstArray(fullSource?.findings), ...firstArray(fullRecentWindow?.findings), ...firstArray(source?.archiveSummary?.redFindings), ...firstArray(fullSource?.archiveSummary?.redFindings)];",
"const findingSamplesById = (id) => { for (const item of allFindingsForCommands) { if (String(item?.id ?? item?.kind ?? item?.code ?? '') !== id) continue; if (Array.isArray(item?.samples) && item.samples.length > 0) return item.samples; } return []; };",
"const commandFailuresFromFindings = [...allFindingsForCommands, ...archiveRedFindings].flatMap((item) => Array.isArray(item?.commands) ? item.commands : []);",
"const srcPromptNetwork = objectOrNull(source?.promptNetwork);",
"const promptNetwork = srcPromptNetwork ? { promptSegments: srcPromptNetwork.promptSegments ?? null } : null;",
"const projectManagement = slimProjectManagement(source?.projectManagement || fullSource?.projectManagement);",
"const runnerErrorsFromJsonl = readJsonlTail(reportJsonPath.replace(/\\/analysis\\/report\\.json$/u, '/errors.jsonl'), 8).filter((item) => item?.type === 'runner-error').map(slimRunnerErrorFromJsonl);",
"const compact = source ? {",
" ok: analyzerExit === 0,",
" counts: source.counts ?? null,",
" jsonlScope: objectOrNull(source.jsonlScope),",
" analysisWindow: objectOrNull(source.analysisWindow),",
" archiveSummary: objectOrNull(source.archiveSummary),",
" sampleMetrics: metrics,",
" runtimeAlerts,",
" pagePerformance,",
" projectManagement,",
" promptNetwork,",
" pagePerformanceSlowApi: takeHead(sourceSlowApi, 4).map(slimSlowApi),",
" archivePagePerformanceSlowApi: takeHead(archiveSlowApi, 8).map(slimSlowApi),",
" pagePerformanceSseStreams: takeHead(sourceSseStreams, 4).map((item) => ({ path: item?.path ?? item?.route ?? null, route: item?.route ?? null, sampleCount: item?.sampleCount ?? null, streamOpenSampleCount: item?.streamOpenSampleCount ?? null, streamOpenP95Ms: item?.streamOpenP95Ms ?? null, streamOpenMaxMs: item?.streamOpenMaxMs ?? null, streamOpenBudgetMs: item?.streamOpenBudgetMs ?? null, streamOpenOverBudgetCount: item?.streamOpenOverBudgetCount ?? null, streamOpenOverFiveSecondCount: item?.streamOpenOverFiveSecondCount ?? null, streamLifetimeOverFiveSecondCount: item?.streamLifetimeOverFiveSecondCount ?? null })),",
" toolFindings: takeHead(toolFindings, 8).map(slimFinding),",
" commandState: objectOrNull(source.commandState) || objectOrNull(fullSource?.commandState) || null,",
" findings: takeHead(combinedFindings, 8).map(slimFinding),",
" archiveRedFindings: takeHead(archiveRedFindings, 8).map(slimFinding),",
" httpErrorGroups: takeHead(firstArray(source.httpErrorGroups, fullRecentWindow?.runtimeAlerts?.networkHttpErrorsByPath, fullSource?.httpErrorGroups, fullSource?.runtimeAlerts?.networkHttpErrorsByPath), 4).map(slimNetworkGroup),",
" requestFailedGroups: takeHead(firstArray(source.requestFailedGroups, fullRecentWindow?.runtimeAlerts?.networkRequestFailedByPath, fullSource?.requestFailedGroups, fullSource?.runtimeAlerts?.networkRequestFailedByPath), 5).map(slimNetworkGroup),",
" domDiagnosticGroups: takeHead(firstArray(source.domDiagnosticGroups, fullRecentWindow?.runtimeAlerts?.domDiagnosticsByFingerprint, fullSource?.domDiagnosticGroups, fullSource?.runtimeAlerts?.domDiagnosticsByFingerprint), 3).map(slimDomGroup),",
" domDiagnosticSamples: takeHead(firstArray(source.domDiagnosticSamples, fullRecentWindow?.runtimeAlerts?.domDiagnostics, fullSource?.domDiagnosticSamples, fullSource?.runtimeAlerts?.domDiagnostics), 5).map(slimDomSample),",
" consoleAlertGroups: takeHead(firstArray(source.consoleAlertGroups, fullRecentWindow?.runtimeAlerts?.consoleAlertsByPath, fullSource?.consoleAlertGroups, fullSource?.runtimeAlerts?.consoleAlertsByPath), 5).map(slimConsoleGroup),",
" consoleAlertSamples: takeHead(firstArray(source.consoleAlertSamples, fullRecentWindow?.runtimeAlerts?.consoleAlerts, fullSource?.consoleAlertSamples, fullSource?.runtimeAlerts?.consoleAlerts), 5).map(slimConsoleSample),",
" runnerErrors: takeTail(firstNonEmptyArray(source.runnerErrors, fullSource?.runnerErrors, runnerErrorsFromJsonl), 8).map(slimRunnerError),",
" commandFailures: takeTail(firstNonEmptyArray(source.commandFailures, fullSource?.commandFailures, commandFailuresFromFindings), 8).map(slimCommandFailure),",
" turnTimingRecentUpdateJumps: takeHead(firstNonEmptyArray(source.turnTimingRecentUpdateJumps, selectedMetrics?.turnTimingRecentUpdateJumps, selectedMetrics?.turnTimingRecentUpdateSawtoothJumps, srcMetrics?.turnTimingRecentUpdateJumps, srcMetrics?.turnTimingRecentUpdateSawtoothJumps, fullRecentWindow?.sampleMetrics?.turnTimingRecentUpdateSawtoothJumps, fullRecentWindow?.turnTimingRecentUpdateJumps, fullSource?.sampleMetrics?.turnTimingRecentUpdateSawtoothJumps, fullSource?.turnTimingRecentUpdateJumps, findingSamplesById('turn-timing-recent-update-sawtooth-jump')), 5).map(slimJump),",
" turnTimingElapsedZeroResets: takeHead(firstNonEmptyArray(source.turnTimingElapsedZeroResets, selectedMetrics?.turnTimingElapsedZeroResets, srcMetrics?.turnTimingElapsedZeroResets, fullRecentWindow?.sampleMetrics?.turnTimingElapsedZeroResets, fullRecentWindow?.turnTimingElapsedZeroResets, fullSource?.sampleMetrics?.turnTimingElapsedZeroResets, fullSource?.turnTimingElapsedZeroResets, findingSamplesById('turn-timing-total-elapsed-zero-reset')), 5).map(slimJump),",
" turnTimingTotalElapsedForwardJumps: takeHead(firstNonEmptyArray(source.turnTimingTotalElapsedForwardJumps, selectedMetrics?.turnTimingTotalElapsedForwardJumps, srcMetrics?.turnTimingTotalElapsedForwardJumps, fullRecentWindow?.sampleMetrics?.turnTimingTotalElapsedForwardJumps, fullRecentWindow?.turnTimingTotalElapsedForwardJumps, fullSource?.sampleMetrics?.turnTimingTotalElapsedForwardJumps, fullSource?.turnTimingTotalElapsedForwardJumps, findingSamplesById('turn-timing-total-elapsed-forward-jump')), 5).map(slimJump),",
" reportJsonPath: source.reportJsonPath || reportJsonPath,",
" reportJsonSha256: source.reportJsonSha256 || sha256(reportJsonPath),",
" reportMdPath: source.reportMdPath || reportMdPath,",
" reportMdSha256: source.reportMdSha256 || sha256(reportMdPath),",
" analyzer: {",
" exitCode: Number.isFinite(analyzerExit) ? analyzerExit : null,",
" recoveredFrom: stdoutJson && stdoutJson.ok !== false ? 'stdout' : reportJson ? 'report-file' : stdoutJson ? 'stdout-error' : 'missing-output',",
" stdoutBytes: statSize(stdoutPath),",
" stderrBytes: statSize(stderrPath),",
" stderrTail: tail(readText(stderrPath))",
" }",
"} : {",
" ok: false,",
" error: 'web-probe-analyzer-output-missing',",
" reportJsonPath,",
" reportMdPath,",
" analyzer: {",
" exitCode: Number.isFinite(analyzerExit) ? analyzerExit : null,",
" recoveredFrom: 'missing-output',",
" stdoutBytes: statSize(stdoutPath),",
" stderrBytes: statSize(stderrPath),",
" stderrTail: tail(readText(stderrPath))",
" }",
"};",
"const compactStdoutLimitBytes = 8000;",
"const compactOutput = (value) => JSON.stringify(value);",
"let output = compactOutput(compact);",
"if (Buffer.byteLength(output, 'utf8') > compactStdoutLimitBytes) {",
" const minimalRounds = takeTail(firstNonEmptyArray(compact.sampleMetrics?.rounds, compact.sampleMetrics?.roundItems, source?.sampleMetrics?.rounds, fullRecentMetrics?.rounds, fullArchiveMetrics?.rounds), 8).map(slimRound);",
" const minimalTurnColumns = takeTail(firstNonEmptyArray(compact.sampleMetrics?.turnColumns, source?.sampleMetrics?.turnColumns, fullRecentMetrics?.turnColumns, fullArchiveMetrics?.turnColumns), 8).map(slimTurnColumn);",
" const minimal = {",
" ok: compact.ok,",
" counts: compact.counts ?? null,",
" jsonlScope: compact.jsonlScope ?? null,",
" analysisWindow: compact.analysisWindow ?? null,",
" archiveSummary: compact.archiveSummary ?? null,",
" sampleMetrics: compact.sampleMetrics ? {",
" roundItems: minimalRounds,",
" rounds: minimalRounds,",
" sampleCount: compact.sampleMetrics.sampleCount ?? null,",
" loadingSampleCount: compact.sampleMetrics.loadingSampleCount ?? null,",
" loadingMaxCount: compact.sampleMetrics.loadingMaxCount ?? null,",
" loadingMaxOwnerCount: compact.sampleMetrics.loadingMaxOwnerCount ?? null,",
" loadingOwnerCount: compact.sampleMetrics.loadingOwnerCount ?? null,",
" loadingConcurrentSampleCount: compact.sampleMetrics.loadingConcurrentSampleCount ?? null,",
" loadingLongestContinuousSeconds: compact.sampleMetrics.loadingLongestContinuousSeconds ?? null,",
" loadingCurrentContinuousSeconds: compact.sampleMetrics.loadingCurrentContinuousSeconds ?? null,",
" loadingOverFiveSecondSegmentCount: compact.sampleMetrics.loadingOverFiveSecondSegmentCount ?? null,",
" sessionRailSampleCount: compact.sampleMetrics.sessionRailSampleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.sampleCount ?? null,",
" sessionRailVisibleSampleCount: compact.sampleMetrics.sessionRailVisibleSampleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.visibleSampleCount ?? null,",
" sessionRailFallbackMajoritySampleCount: compact.sampleMetrics.sessionRailFallbackMajoritySampleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.majorityFallbackSampleCount ?? null,",
" sessionRailFallbackMaxRatio: compact.sampleMetrics.sessionRailFallbackMaxRatio ?? compact.sampleMetrics.sessionRailTitles?.summary?.maxFallbackRatio ?? null,",
" sessionRailFallbackMaxVisibleCount: compact.sampleMetrics.sessionRailFallbackMaxVisibleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.maxVisibleCount ?? null,",
" sessionRailFallbackMaxCount: compact.sampleMetrics.sessionRailFallbackMaxCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.maxFallbackTitleCount ?? null,",
" loading: compact.sampleMetrics.loading ?? null,",
" sessionRailTitles: compact.sampleMetrics.sessionRailTitles ? { summary: compact.sampleMetrics.sessionRailTitles.summary ?? null, samples: Array.isArray(compact.sampleMetrics.sessionRailTitles.samples) ? compact.sampleMetrics.sessionRailTitles.samples.slice(0, 4) : [], examples: Array.isArray(compact.sampleMetrics.sessionRailTitles.examples) ? compact.sampleMetrics.sessionRailTitles.examples.slice(0, 4) : [] } : null,",
" turnTimingRows: compact.sampleMetrics.turnTimingRows ?? null,",
" turnTimingNonMonotonicCount: compact.sampleMetrics.turnTimingNonMonotonicCount ?? null,",
" turnTimingTotalElapsedDecreaseCount: compact.sampleMetrics.turnTimingTotalElapsedDecreaseCount ?? null,",
" turnTimingTotalElapsedForwardJumpCount: compact.sampleMetrics.turnTimingTotalElapsedForwardJumpCount ?? null,",
" turnTimingTotalElapsedForwardJumpMaxSeconds: compact.sampleMetrics.turnTimingTotalElapsedForwardJumpMaxSeconds ?? null,",
" turnTimingTerminalElapsedGrowthCount: compact.sampleMetrics.turnTimingTerminalElapsedGrowthCount ?? null,",
" turnTimingRecentUpdateJumpCount: compact.sampleMetrics.turnTimingRecentUpdateJumpCount ?? null,",
" turnTimingRecentUpdateSawtoothJumpCount: compact.sampleMetrics.turnTimingRecentUpdateSawtoothJumpCount ?? null,",
" turnTimingRecentUpdateMaxIncreaseSeconds: compact.sampleMetrics.turnTimingRecentUpdateMaxIncreaseSeconds ?? null,",
" turnColumns: minimalTurnColumns",
" } : null,",
" runtimeAlerts: compact.runtimeAlerts ?? null,",
" pagePerformance: compact.pagePerformance ?? null,",
" projectManagement: compact.projectManagement ?? null,",
" promptNetwork: compact.promptNetwork ?? null,",
" toolFindings: Array.isArray(compact.toolFindings) ? compact.toolFindings.slice(0, 8) : [],",
" commandState: compact.commandState ?? null,",
" findings: Array.isArray(compact.findings) ? compact.findings.slice(0, 4) : [],",
" archiveRedFindings: Array.isArray(compact.archiveRedFindings) ? compact.archiveRedFindings.slice(0, 4) : [],",
" turnTimingRecentUpdateJumps: Array.isArray(compact.turnTimingRecentUpdateJumps) ? compact.turnTimingRecentUpdateJumps.slice(0, 5) : [],",
" turnTimingElapsedZeroResets: Array.isArray(compact.turnTimingElapsedZeroResets) ? compact.turnTimingElapsedZeroResets.slice(0, 5) : [],",
" turnTimingTotalElapsedForwardJumps: Array.isArray(compact.turnTimingTotalElapsedForwardJumps) ? compact.turnTimingTotalElapsedForwardJumps.slice(0, 5) : [],",
" pagePerformanceSlowApi: Array.isArray(compact.pagePerformanceSlowApi) ? compact.pagePerformanceSlowApi.slice(0, 2).map((item) => ({ ...item, slowSamples: Array.isArray(item.slowSamples) ? item.slowSamples.slice(0, 1) : [] })) : [],",
" archivePagePerformanceSlowApi: Array.isArray(compact.archivePagePerformanceSlowApi) ? compact.archivePagePerformanceSlowApi.slice(0, 4).map((item) => ({ ...item, slowSamples: Array.isArray(item.slowSamples) ? item.slowSamples.slice(0, 1) : [] })) : [],",
" pagePerformanceSseStreams: Array.isArray(compact.pagePerformanceSseStreams) ? compact.pagePerformanceSseStreams.slice(0, 2).map((item) => ({ path: item.path ?? item.route ?? null, route: item.route ?? item.path ?? null, sampleCount: item.sampleCount ?? null, streamOpenP95Ms: item.streamOpenP95Ms ?? null, streamOpenMaxMs: item.streamOpenMaxMs ?? null, streamOpenBudgetMs: item.streamOpenBudgetMs ?? null, streamOpenOverBudgetCount: item.streamOpenOverBudgetCount ?? null, streamOpenOverFiveSecondCount: item.streamOpenOverFiveSecondCount ?? null, streamLifetimeOverFiveSecondCount: item.streamLifetimeOverFiveSecondCount ?? null })) : [],",
" httpErrorGroups: Array.isArray(compact.httpErrorGroups) ? compact.httpErrorGroups.slice(0, 3) : [],",
" requestFailedGroups: Array.isArray(compact.requestFailedGroups) ? compact.requestFailedGroups.slice(0, 3) : [],",
" domDiagnosticGroups: Array.isArray(compact.domDiagnosticGroups) ? compact.domDiagnosticGroups.slice(0, 2) : [],",
" domDiagnosticSamples: Array.isArray(compact.domDiagnosticSamples) ? compact.domDiagnosticSamples.slice(0, 2) : [],",
" consoleAlertGroups: Array.isArray(compact.consoleAlertGroups) ? compact.consoleAlertGroups.slice(0, 3) : [],",
" consoleAlertSamples: Array.isArray(compact.consoleAlertSamples) ? compact.consoleAlertSamples.slice(0, 2) : [],",
" runnerErrors: Array.isArray(compact.runnerErrors) ? compact.runnerErrors.slice(-4) : [],",
" commandFailures: Array.isArray(compact.commandFailures) ? compact.commandFailures.slice(-4) : [],",
" turnTimingRecentUpdateJumps: Array.isArray(compact.turnTimingRecentUpdateJumps) ? compact.turnTimingRecentUpdateJumps.slice(0, 4) : [],",
" turnTimingElapsedZeroResets: Array.isArray(compact.turnTimingElapsedZeroResets) ? compact.turnTimingElapsedZeroResets.slice(0, 4) : [],",
" turnTimingTotalElapsedForwardJumps: Array.isArray(compact.turnTimingTotalElapsedForwardJumps) ? compact.turnTimingTotalElapsedForwardJumps.slice(0, 4) : [],",
" reportJsonPath: compact.reportJsonPath ?? reportJsonPath,",
" reportJsonSha256: compact.reportJsonSha256 ?? sha256(reportJsonPath),",
" reportMdPath: compact.reportMdPath ?? reportMdPath,",
" reportMdSha256: compact.reportMdSha256 ?? sha256(reportMdPath),",
" analyzer: { ...(compact.analyzer ?? {}), compactStdoutLimited: true, compactStdoutLimitBytes, originalCompactBytes: Buffer.byteLength(output, 'utf8') },",
" valuesRedacted: true",
" };",
" output = compactOutput(minimal);",
" if (Buffer.byteLength(output, 'utf8') > compactStdoutLimitBytes) {",
" const tiny = {",
" ok: compact.ok,",
" counts: compact.counts ?? null,",
" jsonlScope: compact.jsonlScope ?? null,",
" analysisWindow: compact.analysisWindow ?? null,",
" archiveSummary: compact.archiveSummary ?? null,",
" sampleMetrics: compact.sampleMetrics ? {",
" sampleCount: compact.sampleMetrics.sampleCount ?? null,",
" loadingSampleCount: compact.sampleMetrics.loadingSampleCount ?? null,",
" loadingMaxCount: compact.sampleMetrics.loadingMaxCount ?? null,",
" loadingMaxOwnerCount: compact.sampleMetrics.loadingMaxOwnerCount ?? null,",
" loadingOwnerCount: compact.sampleMetrics.loadingOwnerCount ?? null,",
" loadingLongestContinuousSeconds: compact.sampleMetrics.loadingLongestContinuousSeconds ?? null,",
" loadingCurrentContinuousSeconds: compact.sampleMetrics.loadingCurrentContinuousSeconds ?? null,",
" loadingOverFiveSecondSegmentCount: compact.sampleMetrics.loadingOverFiveSecondSegmentCount ?? null,",
" loading: compact.sampleMetrics.loading ? { summary: compact.sampleMetrics.loading.summary ?? null, longestSegments: Array.isArray(compact.sampleMetrics.loading.longestSegments) ? compact.sampleMetrics.loading.longestSegments.slice(0, 4) : [], owners: Array.isArray(compact.sampleMetrics.loading.owners) ? compact.sampleMetrics.loading.owners.slice(0, 4) : [] } : null,",
" sessionRailSampleCount: compact.sampleMetrics.sessionRailSampleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.sampleCount ?? null,",
" sessionRailVisibleSampleCount: compact.sampleMetrics.sessionRailVisibleSampleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.visibleSampleCount ?? null,",
" sessionRailFallbackMajoritySampleCount: compact.sampleMetrics.sessionRailFallbackMajoritySampleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.majorityFallbackSampleCount ?? null,",
" sessionRailFallbackMaxRatio: compact.sampleMetrics.sessionRailFallbackMaxRatio ?? compact.sampleMetrics.sessionRailTitles?.summary?.maxFallbackRatio ?? null,",
" sessionRailFallbackMaxVisibleCount: compact.sampleMetrics.sessionRailFallbackMaxVisibleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.maxVisibleCount ?? null,",
" sessionRailFallbackMaxCount: compact.sampleMetrics.sessionRailFallbackMaxCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.maxFallbackTitleCount ?? null,",
" sessionRailTitles: compact.sampleMetrics.sessionRailTitles ? { summary: compact.sampleMetrics.sessionRailTitles.summary ?? null, samples: Array.isArray(compact.sampleMetrics.sessionRailTitles.samples) ? compact.sampleMetrics.sessionRailTitles.samples.slice(0, 4) : [], examples: Array.isArray(compact.sampleMetrics.sessionRailTitles.examples) ? compact.sampleMetrics.sessionRailTitles.examples.slice(0, 4) : [] } : null,",
" turnTimingRows: compact.sampleMetrics.turnTimingRows ?? null,",
" turnTimingNonMonotonicCount: compact.sampleMetrics.turnTimingNonMonotonicCount ?? null,",
" turnTimingTotalElapsedDecreaseCount: compact.sampleMetrics.turnTimingTotalElapsedDecreaseCount ?? null,",
" turnTimingTotalElapsedForwardJumpCount: compact.sampleMetrics.turnTimingTotalElapsedForwardJumpCount ?? null,",
" turnTimingTerminalElapsedGrowthCount: compact.sampleMetrics.turnTimingTerminalElapsedGrowthCount ?? null,",
" turnTimingRecentUpdateJumpCount: compact.sampleMetrics.turnTimingRecentUpdateJumpCount ?? compact.sampleMetrics.turnTimingRecentUpdateSawtoothJumpCount ?? null",
" } : null,",
" runtimeAlerts: compact.runtimeAlerts ?? null,",
" pagePerformance: compact.pagePerformance ?? null,",
" projectManagement: compact.projectManagement ? { summary: compact.projectManagement.summary ?? compact.projectManagement, samples: Array.isArray(compact.projectManagement.samples) ? compact.projectManagement.samples.slice(-4) : [], commands: Array.isArray(compact.projectManagement.commands) ? compact.projectManagement.commands.slice(-4) : [], launchCommands: Array.isArray(compact.projectManagement.launchCommands) ? compact.projectManagement.launchCommands.slice(-4) : [], projectApiByPath: Array.isArray(compact.projectManagement.projectApiByPath) ? compact.projectManagement.projectApiByPath.slice(0, 4) : [], slowProjectApiPerformance: Array.isArray(compact.projectManagement.slowProjectApiPerformance) ? compact.projectManagement.slowProjectApiPerformance.slice(0, 4) : [], valuesRedacted: true } : null,",
" toolFindings: Array.isArray(compact.toolFindings) ? compact.toolFindings.slice(0, 8) : [],",
" commandState: compact.commandState ?? null,",
" findings: Array.isArray(compact.findings) ? compact.findings.slice(0, 4) : [],",
" archiveRedFindings: Array.isArray(compact.archiveRedFindings) ? compact.archiveRedFindings.slice(0, 4) : [],",
" pagePerformanceSlowApi: Array.isArray(compact.pagePerformanceSlowApi) ? compact.pagePerformanceSlowApi.slice(0, 2).map((item) => ({ path: item.path ?? item.route ?? null, route: item.route ?? item.path ?? null, sampleCount: item.sampleCount ?? null, p95Ms: item.p95Ms ?? item.p95 ?? null, maxMs: item.maxMs ?? item.max ?? null, budgetMs: item.budgetMs ?? null, overBudgetCount: item.overBudgetCount ?? null, overFiveSecondCount: item.overFiveSecondCount ?? null, slowSamples: Array.isArray(item.slowSamples) ? item.slowSamples.slice(0, 1) : [] })) : [],",
" archivePagePerformanceSlowApi: Array.isArray(compact.archivePagePerformanceSlowApi) ? compact.archivePagePerformanceSlowApi.slice(0, 4).map((item) => ({ path: item.path ?? item.route ?? null, route: item.route ?? item.path ?? null, sampleCount: item.sampleCount ?? null, p95Ms: item.p95Ms ?? item.p95 ?? null, maxMs: item.maxMs ?? item.max ?? null, budgetMs: item.budgetMs ?? null, overBudgetCount: item.overBudgetCount ?? null, overFiveSecondCount: item.overFiveSecondCount ?? null, slowSamples: Array.isArray(item.slowSamples) ? item.slowSamples.slice(0, 1) : [] })) : [],",
" pagePerformanceSseStreams: Array.isArray(compact.pagePerformanceSseStreams) ? compact.pagePerformanceSseStreams.slice(0, 2).map((item) => ({ path: item.path ?? item.route ?? null, route: item.route ?? item.path ?? null, sampleCount: item.sampleCount ?? null, streamOpenP95Ms: item.streamOpenP95Ms ?? null, streamOpenMaxMs: item.streamOpenMaxMs ?? null, streamOpenOverFiveSecondCount: item.streamOpenOverFiveSecondCount ?? null, streamLifetimeOverFiveSecondCount: item.streamLifetimeOverFiveSecondCount ?? null })) : [],",
" httpErrorGroups: Array.isArray(compact.httpErrorGroups) ? compact.httpErrorGroups.slice(0, 2) : [],",
" requestFailedGroups: Array.isArray(compact.requestFailedGroups) ? compact.requestFailedGroups.slice(0, 2) : [],",
" consoleAlertGroups: Array.isArray(compact.consoleAlertGroups) ? compact.consoleAlertGroups.slice(0, 2) : [],",
" commandFailures: Array.isArray(compact.commandFailures) ? compact.commandFailures.slice(-3) : [],",
" turnTimingRecentUpdateJumps: Array.isArray(compact.turnTimingRecentUpdateJumps) ? compact.turnTimingRecentUpdateJumps.slice(0, 3) : [],",
" turnTimingTotalElapsedForwardJumps: Array.isArray(compact.turnTimingTotalElapsedForwardJumps) ? compact.turnTimingTotalElapsedForwardJumps.slice(0, 3) : [],",
" reportJsonPath: compact.reportJsonPath ?? reportJsonPath,",
" reportJsonSha256: compact.reportJsonSha256 ?? sha256(reportJsonPath),",
" reportMdPath: compact.reportMdPath ?? reportMdPath,",
" reportMdSha256: compact.reportMdSha256 ?? sha256(reportMdPath),",
" analyzer: { ...(compact.analyzer ?? {}), compactStdoutLimited: true, compactStdoutLimitBytes, originalCompactBytes: Buffer.byteLength(compactOutput(compact), 'utf8'), originalMinimalBytes: Buffer.byteLength(output, 'utf8') },",
" valuesRedacted: true",
" };",
" output = compactOutput(tiny);",
" if (Buffer.byteLength(output, 'utf8') > compactStdoutLimitBytes) {",
" const ultratiny = {",
" ok: compact.ok,",
" counts: compact.counts ?? null,",
" analysisWindow: compact.analysisWindow ?? null,",
" archiveSummary: compact.archiveSummary ?? null,",
" sampleMetrics: compact.sampleMetrics ? {",
" sampleCount: compact.sampleMetrics.sampleCount ?? null,",
" loadingSampleCount: compact.sampleMetrics.loadingSampleCount ?? null,",
" loadingMaxCount: compact.sampleMetrics.loadingMaxCount ?? null,",
" loadingOverFiveSecondSegmentCount: compact.sampleMetrics.loadingOverFiveSecondSegmentCount ?? null,",
" loading: compact.sampleMetrics.loading ? { summary: compact.sampleMetrics.loading.summary ?? null, longestSegments: Array.isArray(compact.sampleMetrics.loading.longestSegments) ? compact.sampleMetrics.loading.longestSegments.slice(0, 4) : [], owners: Array.isArray(compact.sampleMetrics.loading.owners) ? compact.sampleMetrics.loading.owners.slice(0, 4) : [] } : null,",
" sessionRailFallbackMajoritySampleCount: compact.sampleMetrics.sessionRailFallbackMajoritySampleCount ?? compact.sampleMetrics.sessionRailTitles?.summary?.majorityFallbackSampleCount ?? null,",
" turnTimingRows: compact.sampleMetrics.turnTimingRows ?? null,",
" turnTimingTotalElapsedForwardJumpCount: compact.sampleMetrics.turnTimingTotalElapsedForwardJumpCount ?? null,",
" turnTimingRecentUpdateJumpCount: compact.sampleMetrics.turnTimingRecentUpdateJumpCount ?? compact.sampleMetrics.turnTimingRecentUpdateSawtoothJumpCount ?? null",
" } : null,",
" runtimeAlerts: compact.runtimeAlerts ?? null,",
" pagePerformance: compact.pagePerformance ?? null,",
" projectManagement: compact.projectManagement ? { summary: compact.projectManagement.summary ?? compact.projectManagement, samples: Array.isArray(compact.projectManagement.samples) ? compact.projectManagement.samples.slice(-3) : [], commands: Array.isArray(compact.projectManagement.commands) ? compact.projectManagement.commands.slice(-3) : [], launchCommands: Array.isArray(compact.projectManagement.launchCommands) ? compact.projectManagement.launchCommands.slice(-3) : [], projectApiByPath: Array.isArray(compact.projectManagement.projectApiByPath) ? compact.projectManagement.projectApiByPath.slice(0, 3) : [], slowProjectApiPerformance: Array.isArray(compact.projectManagement.slowProjectApiPerformance) ? compact.projectManagement.slowProjectApiPerformance.slice(0, 2) : [], valuesRedacted: true } : null,",
" toolFindings: Array.isArray(compact.toolFindings) ? compact.toolFindings.slice(0, 8) : [],",
" commandState: compact.commandState ?? null,",
" findings: Array.isArray(compact.findings) ? compact.findings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, summary: clip(item.summary ?? item.message, 120) })) : [],",
" archiveRedFindings: Array.isArray(compact.archiveRedFindings) ? compact.archiveRedFindings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, summary: clip(item.summary ?? item.message, 120) })) : [],",
" httpErrorGroups: Array.isArray(compact.httpErrorGroups) ? compact.httpErrorGroups.slice(0, 2).map((item) => ({ count: item.count ?? null, method: item.method ?? null, status: item.status ?? null, path: item.path ?? null, lastAt: item.lastAt ?? null })) : [],",
" requestFailedGroups: Array.isArray(compact.requestFailedGroups) ? compact.requestFailedGroups.slice(0, 2).map((item) => ({ count: item.count ?? null, method: item.method ?? null, path: item.path ?? null, failureKinds: item.failureKinds ?? null })) : [],",
" commandFailures: Array.isArray(compact.commandFailures) ? compact.commandFailures.slice(-3).map((item) => ({ ts: item.ts ?? null, type: item.type ?? null, commandId: item.commandId ?? null, durationMs: item.durationMs ?? null, sampleSeq: item.sampleSeq ?? null, beforePath: item.beforePath ?? null, afterPath: item.afterPath ?? null, message: clip(item.message ?? item.failureKind ?? item.name, 120) })) : [],",
" turnTimingRecentUpdateJumps: Array.isArray(compact.turnTimingRecentUpdateJumps) ? compact.turnTimingRecentUpdateJumps.slice(0, 3) : [],",
" turnTimingElapsedZeroResets: Array.isArray(compact.turnTimingElapsedZeroResets) ? compact.turnTimingElapsedZeroResets.slice(0, 3) : [],",
" turnTimingTotalElapsedForwardJumps: Array.isArray(compact.turnTimingTotalElapsedForwardJumps) ? compact.turnTimingTotalElapsedForwardJumps.slice(0, 3) : [],",
" pagePerformanceSlowApi: Array.isArray(compact.pagePerformanceSlowApi) ? compact.pagePerformanceSlowApi.slice(0, 2).map((item) => ({ path: item.path ?? item.route ?? null, sampleCount: item.sampleCount ?? null, p95Ms: item.p95Ms ?? null, maxMs: item.maxMs ?? null, budgetMs: item.budgetMs ?? null, overBudgetCount: item.overBudgetCount ?? null, overFiveSecondCount: item.overFiveSecondCount ?? null })) : [],",
" archivePagePerformanceSlowApi: Array.isArray(compact.archivePagePerformanceSlowApi) ? compact.archivePagePerformanceSlowApi.slice(0, 4).map((item) => ({ path: item.path ?? item.route ?? null, sampleCount: item.sampleCount ?? null, p95Ms: item.p95Ms ?? null, maxMs: item.maxMs ?? null, budgetMs: item.budgetMs ?? null, overBudgetCount: item.overBudgetCount ?? null, overFiveSecondCount: item.overFiveSecondCount ?? null })) : [],",
" pagePerformanceSseStreams: Array.isArray(compact.pagePerformanceSseStreams) ? compact.pagePerformanceSseStreams.slice(0, 2).map((item) => ({ path: item.path ?? item.route ?? null, sampleCount: item.sampleCount ?? null, streamOpenP95Ms: item.streamOpenP95Ms ?? null, streamOpenMaxMs: item.streamOpenMaxMs ?? null, streamOpenBudgetMs: item.streamOpenBudgetMs ?? null, streamOpenOverBudgetCount: item.streamOpenOverBudgetCount ?? null, streamOpenOverFiveSecondCount: item.streamOpenOverFiveSecondCount ?? null, streamLifetimeOverFiveSecondCount: item.streamLifetimeOverFiveSecondCount ?? null })) : [],",
" reportJsonPath: compact.reportJsonPath ?? reportJsonPath,",
" reportJsonSha256: compact.reportJsonSha256 ?? sha256(reportJsonPath),",
" reportMdPath: compact.reportMdPath ?? reportMdPath,",
" reportMdSha256: compact.reportMdSha256 ?? sha256(reportMdPath),",
" analyzer: { ...(compact.analyzer ?? {}), compactStdoutLimited: true, ultratiny: true, compactStdoutLimitBytes, originalCompactBytes: Buffer.byteLength(compactOutput(compact), 'utf8'), originalTinyBytes: Buffer.byteLength(output, 'utf8') },",
" valuesRedacted: true",
" };",
" output = compactOutput(ultratiny);",
" }",
" if (Buffer.byteLength(output, 'utf8') > compactStdoutLimitBytes) {",
" output = compactOutput({ ok: compact.ok, counts: compact.counts ?? null, jsonlScope: compact.jsonlScope ?? null, analysisWindow: compact.analysisWindow ?? null, archiveSummary: compact.archiveSummary ? { redFindingCount: compact.archiveSummary.redFindingCount ?? null, findingCount: compact.archiveSummary.findingCount ?? null, sampleCount: compact.archiveSummary.sampleMetrics?.sampleCount ?? null, slowPathCount: compact.archiveSummary.pagePerformance?.slowPathCount ?? null } : null, projectManagement: compact.projectManagement ? { summary: compact.projectManagement.summary ?? compact.projectManagement, samples: Array.isArray(compact.projectManagement.samples) ? compact.projectManagement.samples.slice(-2) : [], commands: Array.isArray(compact.projectManagement.commands) ? compact.projectManagement.commands.slice(-2) : [], launchCommands: Array.isArray(compact.projectManagement.launchCommands) ? compact.projectManagement.launchCommands.slice(-2) : [], projectApiByPath: Array.isArray(compact.projectManagement.projectApiByPath) ? compact.projectManagement.projectApiByPath.slice(0, 2) : [], valuesRedacted: true } : null, toolFindings: Array.isArray(compact.toolFindings) ? compact.toolFindings.slice(0, 8) : [], commandState: compact.commandState ? { pendingCount: compact.commandState.pendingCount ?? null, processingCount: compact.commandState.processingCount ?? null, abandonedCount: compact.commandState.abandonedCount ?? null, failedCount: compact.commandState.failedCount ?? null } : null, findings: Array.isArray(compact.findings) ? compact.findings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, summary: clip(item.summary ?? item.message, 120) })) : [], archiveRedFindings: Array.isArray(compact.archiveRedFindings) ? compact.archiveRedFindings.slice(0, 4).map((item) => ({ kind: item.kind ?? item.code ?? null, severity: item.severity ?? item.level ?? null, count: item.count ?? item.sampleCount ?? null, summary: clip(item.summary ?? item.message, 120) })) : [], commandFailures: Array.isArray(compact.commandFailures) ? compact.commandFailures.slice(-3).map((item) => ({ ts: item.ts ?? null, type: item.type ?? null, commandId: item.commandId ?? null, durationMs: item.durationMs ?? null, sampleSeq: item.sampleSeq ?? null, beforePath: item.beforePath ?? null, afterPath: item.afterPath ?? null, message: clip(item.message ?? item.failureKind ?? item.name, 120) })) : [], archivePagePerformanceSlowApi: Array.isArray(compact.archivePagePerformanceSlowApi) ? compact.archivePagePerformanceSlowApi.slice(0, 4).map((item) => ({ path: item.path ?? item.route ?? null, sampleCount: item.sampleCount ?? null, p95Ms: item.p95Ms ?? null, maxMs: item.maxMs ?? null, overFiveSecondCount: item.overFiveSecondCount ?? null })) : [], reportJsonPath: compact.reportJsonPath ?? reportJsonPath, reportJsonSha256: compact.reportJsonSha256 ?? sha256(reportJsonPath), reportMdPath: compact.reportMdPath ?? reportMdPath, reportMdSha256: compact.reportMdSha256 ?? sha256(reportMdPath), analyzer: { ...(compact.analyzer ?? {}), compactStdoutLimited: true, ultratiny: true, hardFallback: true, compactStdoutLimitBytes }, valuesRedacted: true });",
" }",
" }",
"}",
"console.log(output);",
"UNIDESK_WEB_OBSERVE_ANALYZE_COMPACT",
"exit \"$analyzer_exit\"",
].join("\n");
const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds);
const primaryAnalysis = recoverWebObserveAnalyzeTurnDetails(options, spec, parseJsonObject(result.stdout));
const artifactAnalysis = (result.timedOut || result.exitCode !== 0 || primaryAnalysis === null)
? recoverWebObserveAnalyzeFromArtifacts(options, spec, result)
: null;
const analysis = recoverWebObserveAnalyzeTurnDetails(options, spec, artifactAnalysis ?? primaryAnalysis);
const analysisOk = analysis?.ok === true;
const analysisFailure = analysisOk
? null
: {
reason: analysis === null ? "analyzer-output-not-json" : "analyzer-reported-not-ok",
exitCode: result.exitCode,
timedOut: result.timedOut,
parsedJson: analysis !== null,
recoveredFromArtifacts: artifactAnalysis !== null,
stdoutBytes: result.stdout.length,
stderrBytes: result.stderr.length,
stdoutTail: result.stdout.trim().slice(-1200),
stderrTail: result.stderr.trim().slice(-1200),
workspace: spec.workspace,
observerId: webObserveIdFromOptions(options),
valuesRedacted: true,
};
const analysisArtifactDir = options.stateDir ? join(options.stateDir, "analysis") : "";
const failureAnalysis = analysis ?? (analysisFailure ? {
ok: false,
command: "web-probe-observe analyze",
stateDir: options.stateDir,
reportJsonPath: analysisArtifactDir ? join(analysisArtifactDir, "report.json") : null,
reportMdPath: analysisArtifactDir ? join(analysisArtifactDir, "report.md") : null,
analyzer: {
exitCode: result.exitCode,
timedOut: result.timedOut,
stdoutBytes: result.stdout.length,
stderrBytes: result.stderr.length,
stdoutPath: analysisArtifactDir ? join(analysisArtifactDir, "analyzer-stdout.json") : null,
stderrPath: analysisArtifactDir ? join(analysisArtifactDir, "analyzer-stderr.log") : null,
stdoutTail: result.stdout.trim().slice(-1200),
stderrTail: result.stderr.trim().slice(-1200),
recoveredFrom: "analyzer-timeout-failure-contract",
valuesRedacted: true,
},
findings: [{
kind: result.timedOut ? "web-probe-analyzer-timeout" : "web-probe-analyzer-failed",
severity: "red",
count: 1,
summary: result.timedOut
? "observe analyze timed out before producing a compact report; inspect analyzer stdout/stderr artifacts under the observer analysis directory"
: "observe analyze failed before producing a compact report; inspect analyzer stdout/stderr artifacts under the observer analysis directory",
}],
next: {
collectAnalyzerStdout: options.stateDir ? `bun scripts/cli.ts web-probe observe collect --node ${options.node} --lane ${options.lane} --state-dir ${options.stateDir} --file analysis/analyzer-stdout.json` : null,
collectAnalyzerStderr: options.stateDir ? `bun scripts/cli.ts web-probe observe collect --node ${options.node} --lane ${options.lane} --state-dir ${options.stateDir} --file analysis/analyzer-stderr.log` : null,
valuesRedacted: true,
},
valuesRedacted: true,
} : null);
return withWebObserveAnalyzeRendered({
ok: analysisOk,
status: analysisOk ? "analyzed" : "blocked",
command: webObserveCommandLabel("analyze", options),
id: webObserveIdFromOptions(options),
node: options.node,
lane: options.lane,
workspace: spec.workspace,
analysis: failureAnalysis,
failure: analysisFailure,
alertThresholds,
wrapper: buildWebObserveWrapperForObserveOptions("analyze", options, spec.workspace),
result: analysis === null ? compactCommandResultWithStdoutTail(result) : compactCommandResult(result),
valuesRedacted: true,
});
}
export function recoverWebObserveAnalyzeFromArtifacts(options: NodeWebProbeObserveOptions, spec: HwlabRuntimeLaneSpec, result: { exitCode: number; timedOut: boolean }): Record<string, unknown> | null {
const recoverScript = [
"set -eu",
nodeWebObserveResolveStateDirShell(options),
"analysis_dir=\"$state_dir/analysis\"",
"analysis_stdout=\"$analysis_dir/analyzer-stdout.json\"",
"analysis_stderr=\"$analysis_dir/analyzer-stderr.log\"",
"report_json=\"$analysis_dir/report.json\"",
"report_md=\"$analysis_dir/report.md\"",
`transport_exit=${shellQuote(String(result.exitCode))}`,
`transport_timed_out=${shellQuote(result.timedOut ? "true" : "false")}`,
"node - \"$state_dir\" \"$analysis_stdout\" \"$analysis_stderr\" \"$report_json\" \"$report_md\" \"$transport_exit\" \"$transport_timed_out\" <<'UNIDESK_WEB_OBSERVE_RECOVER_ANALYZE_ARTIFACT'",
"const fs = require('fs');",
"const crypto = require('crypto');",
"const [stateDir, stdoutPath, stderrPath, reportJsonPath, reportMdPath, transportExitRaw, transportTimedOutRaw] = process.argv.slice(2);",
"const readText = (path) => { try { return fs.readFileSync(path, 'utf8'); } catch { return ''; } };",
"const readJson = (path) => { const text = readText(path); if (!text.trim()) return null; try { return JSON.parse(text); } catch { return null; } };",
"const statSize = (path) => { try { return fs.statSync(path).size; } catch { return 0; } };",
"const sha256 = (path) => { const text = readText(path); return text ? 'sha256:' + crypto.createHash('sha256').update(text).digest('hex') : null; };",
"const objectOrNull = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : null;",
"const arr = (value) => Array.isArray(value) ? value : [];",
"const clip = (value, limit = 160) => value === null || value === undefined ? null : String(value).slice(0, limit);",
"const numberish = (...values) => { for (const value of values) { const n = Number(value); if (Number.isFinite(n)) return value; } return null; };",
"const slimFinding = (item) => { const v = objectOrNull(item) || {}; return { kind: clip(v.kind ?? v.id ?? v.code, 48), code: clip(v.code ?? v.id ?? v.kind, 48), severity: clip(v.severity ?? v.level, 24), level: clip(v.level ?? v.severity, 24), count: v.count ?? v.sampleCount ?? null, sampleCount: v.sampleCount ?? v.count ?? null, summary: clip(v.summary ?? v.message, 180), message: clip(v.message ?? v.summary, 180) }; };",
"const slimRound = (item) => { const v = objectOrNull(item) || {}; return { promptIndex: v.promptIndex ?? null, promptTextHash: clip(v.promptTextHash, 80), sampleCount: v.sampleCount ?? null, firstSeq: v.firstSeq ?? null, lastSeq: v.lastSeq ?? null, lastTotalElapsedSeconds: v.lastTotalElapsedSeconds ?? null, lastRecentUpdateSeconds: v.lastRecentUpdateSeconds ?? null, loadingSamples: v.loadingSamples ?? null, maxLoadingCount: v.maxLoadingCount ?? null, loadingOwnerCount: v.loadingOwnerCount ?? null, diagnosticSamples: v.diagnosticSamples ?? null, terminalSamples: v.terminalSamples ?? null, finalTextSamples: v.finalTextSamples ?? null, turnTimingTotalElapsedZeroResetCount: v.turnTimingTotalElapsedZeroResetCount ?? null, turnTimingTotalElapsedForwardJumpCount: v.turnTimingTotalElapsedForwardJumpCount ?? null, turnTimingTotalElapsedForwardJumpMaxSeconds: v.turnTimingTotalElapsedForwardJumpMaxSeconds ?? null, turnTimingRecentUpdateJumpCount: v.turnTimingRecentUpdateJumpCount ?? null, turnTimingRecentUpdateMaxIncreaseSeconds: v.turnTimingRecentUpdateMaxIncreaseSeconds ?? null }; };",
"const slimTurnColumn = (item) => { const v = objectOrNull(item) || {}; return { label: clip(v.label, 24), source: clip(v.source, 48), pageRole: clip(v.pageRole, 24), pageId: clip(v.pageId, 32), pageEpoch: v.pageEpoch ?? null, promptIndex: v.promptIndex ?? null, lastPromptIndex: v.lastPromptIndex ?? null, firstSeq: v.firstSeq ?? null, lastSeq: v.lastSeq ?? null, traceId: clip(v.traceId, 64), messageId: clip(v.messageId, 64) }; };",
"const slimGroup = (item) => { const v = objectOrNull(item) || {}; return { count: v.count ?? null, method: clip(v.method, 12), status: v.status ?? null, path: clip(v.path ?? v.urlPath ?? v.route, 96), firstAt: v.firstAt ?? null, lastAt: v.lastAt ?? null, text: clip(v.text ?? v.preview, 180), failureKinds: arr(v.failureKinds).slice(0, 4).map((x) => clip(x, 48)), traceIds: arr(v.traceIds).slice(0, 3).map((x) => clip(x, 64)) }; };",
"const slimSlowSample = (item) => { const v = objectOrNull(item) || {}; return { ts: v.ts ?? null, seq: v.seq ?? null, path: clip(v.path ?? v.rawPath, 96), initiatorType: clip(v.initiatorType, 24), durationMs: v.durationMs ?? null, requestToResponseStartMs: v.requestToResponseStartMs ?? v.streamOpenMs ?? null, responseTransferMs: v.responseTransferMs ?? null, timingStatus: clip(v.timingStatus, 16), nextHopProtocol: clip(v.nextHopProtocol, 24), serverTimingNames: arr(v.serverTimingNames).slice(0, 4).map((x) => clip(x, 32)), otelTraceId: clip(v.otelTraceId, 32) }; };",
"const slimSlowApi = (item) => { const v = objectOrNull(item) || {}; return { path: clip(v.path ?? v.route, 96), route: clip(v.route ?? v.path, 96), sampleCount: v.sampleCount ?? null, p95Ms: v.p95Ms ?? v.p95 ?? null, maxMs: v.maxMs ?? v.max ?? null, budgetMs: v.budgetMs ?? null, overBudgetCount: v.overBudgetCount ?? null, overFiveSecondCount: v.overFiveSecondCount ?? null, slowSamples: arr(v.slowSamples).slice(0, 3).map(slimSlowSample) }; };",
"const compactLoading = (value) => { const v = objectOrNull(value); if (!v) return null; const s = objectOrNull(v.summary) || {}; return { summary: s, longestSegments: arr(v.longestSegments ?? v.segments).slice(0, 8), owners: arr(v.owners).slice(0, 8), timeline: arr(v.timeline).slice(-12) }; };",
"const compactSessionRailTitles = (value) => { const v = objectOrNull(value); if (!v) return null; return { summary: objectOrNull(v.summary) || {}, samples: arr(v.samples).slice(0, 8), examples: arr(v.examples).slice(0, 8) }; };",
"const compactTraceOrder = (value) => { const v = objectOrNull(value); if (!v) return null; const s = objectOrNull(v.summary) || {}; return { summary: { sampleCount: v.sampleCount ?? s.sampleCount ?? null, traceRowCount: v.traceRowCount ?? s.traceRowCount ?? null, orderAnomalyCount: v.orderAnomalyCount ?? s.orderAnomalyCount ?? arr(v.orderAnomalies).length, completionNotLastCount: v.completionNotLastCount ?? s.completionNotLastCount ?? arr(v.completionNotLast).length }, orderAnomalies: arr(v.orderAnomalies).slice(0, 8) }; };",
"const compactMetrics = (value) => { const v = objectOrNull(value) || {}; const s = objectOrNull(v.summary) || {}; return { sampleCount: numberish(v.sampleCount, s.sampleCount), rounds: arr(v.rounds ?? v.roundItems).slice(-8).map(slimRound), roundItems: arr(v.roundItems ?? v.rounds).slice(-8).map(slimRound), turnColumns: arr(v.turnColumns).slice(-12).map(slimTurnColumn), turnTimingRows: numberish(v.turnTimingRows, s.turnTimingRows), turnTimingNonMonotonicCount: numberish(v.turnTimingNonMonotonicCount, s.turnTimingNonMonotonicCount), turnTimingTotalElapsedDecreaseCount: numberish(v.turnTimingTotalElapsedDecreaseCount, s.turnTimingTotalElapsedDecreaseCount), turnTimingTotalElapsedZeroResetCount: numberish(v.turnTimingTotalElapsedZeroResetCount, s.turnTimingTotalElapsedZeroResetCount), turnTimingTotalElapsedForwardJumpCount: numberish(v.turnTimingTotalElapsedForwardJumpCount, s.turnTimingTotalElapsedForwardJumpCount), turnTimingTerminalElapsedGrowthCount: numberish(v.turnTimingTerminalElapsedGrowthCount, s.turnTimingTerminalElapsedGrowthCount), turnTimingRecentUpdateJumpCount: numberish(v.turnTimingRecentUpdateJumpCount, v.turnTimingRecentUpdateSawtoothJumpCount, s.turnTimingRecentUpdateJumpCount, s.turnTimingRecentUpdateSawtoothJumpCount), turnTimingRecentUpdateMaxIncreaseSeconds: numberish(v.turnTimingRecentUpdateMaxIncreaseSeconds, s.turnTimingRecentUpdateMaxIncreaseSeconds), loading: compactLoading(v.loading), traceOrder: compactTraceOrder(v.traceOrder), sessionRailTitles: compactSessionRailTitles(v.sessionRailTitles), promptSegments: numberish(v.promptSegments, s.promptSegments), loadingSampleCount: numberish(v.loadingSampleCount, s.loadingSampleCount), loadingMaxCount: numberish(v.loadingMaxCount, s.loadingMaxCount), loadingMaxOwnerCount: numberish(v.loadingMaxOwnerCount, s.loadingMaxOwnerCount), loadingOwnerCount: numberish(v.loadingOwnerCount, s.loadingOwnerCount), loadingLongestContinuousSeconds: numberish(v.loadingLongestContinuousSeconds, s.loadingLongestContinuousSeconds), loadingCurrentContinuousSeconds: numberish(v.loadingCurrentContinuousSeconds, s.loadingCurrentContinuousSeconds), loadingOverFiveSecondSegmentCount: numberish(v.loadingOverFiveSecondSegmentCount, s.loadingOverFiveSecondSegmentCount) }; };",
"const stdoutJson = readJson(stdoutPath);",
"const reportJson = readJson(reportJsonPath);",
"const source = objectOrNull(stdoutJson) || objectOrNull(reportJson);",
"if (!source) { console.log(JSON.stringify({ ok: false, command: 'web-probe-observe analyze', stateDir, error: 'web-probe-analyzer-artifacts-missing', analyzer: { recoveredFrom: 'analysis-artifact-after-transport-timeout', stdoutPath, stderrPath, reportJsonPath, reportMdPath, stdoutBytes: statSize(stdoutPath), stderrBytes: statSize(stderrPath), reportJsonBytes: statSize(reportJsonPath), reportMdBytes: statSize(reportMdPath), transportExitCode: Number(transportExitRaw), transportTimedOut: transportTimedOutRaw === 'true', valuesRedacted: true }, valuesRedacted: true })); process.exit(0); }",
"const recent = objectOrNull(source.windows?.recent) || {};",
"const srcMetrics = objectOrNull(source.sampleMetrics) || objectOrNull(recent.sampleMetrics) || {};",
"const pagePerformance = objectOrNull(source.pagePerformance) || objectOrNull(recent.pagePerformance) || {};",
"const runtimeAlerts = objectOrNull(source.runtimeAlerts) || objectOrNull(recent.runtimeAlerts) || {};",
"const promptNetwork = objectOrNull(source.promptNetwork) || objectOrNull(recent.promptNetwork) || {};",
"const archiveSummary = objectOrNull(source.archiveSummary) || {};",
"const archiveSampleMetrics = objectOrNull(archiveSummary.sampleMetrics) || objectOrNull(source.sampleMetrics?.summary) || objectOrNull(srcMetrics.summary) || {};",
"const slowApis = arr(source.pagePerformanceSlowApi).length > 0 ? arr(source.pagePerformanceSlowApi) : arr(pagePerformance.sameOriginApiByPath).filter((item) => Number(item?.overBudgetCount ?? item?.overFiveSecondCount ?? 0) > 0);",
"const compact = { ok: source.ok === true, command: source.command ?? 'web-probe-observe analyze', stateDir: source.stateDir ?? stateDir, jsonlScope: source.jsonlScope ?? null, alertThresholds: source.alertThresholds ?? null, counts: source.counts ?? null, archiveSummary: { ...archiveSummary, sampleMetrics: archiveSampleMetrics, pagePerformance: objectOrNull(archiveSummary.pagePerformance) || objectOrNull(pagePerformance.summary) || {}, runtimeAlerts: objectOrNull(archiveSummary.runtimeAlerts) || objectOrNull(runtimeAlerts.summary) || {}, redFindings: arr(archiveSummary.redFindings).slice(0, 12).map(slimFinding) }, analysisWindow: source.analysisWindow ?? objectOrNull(recent.summary), sampleMetrics: compactMetrics(srcMetrics), pageProvenance: objectOrNull(source.pageProvenance?.summary) || source.pageProvenance ?? null, pagePerformance: objectOrNull(pagePerformance.summary) || pagePerformance, projectManagement: objectOrNull(source.projectManagement) || null, promptNetwork: objectOrNull(promptNetwork.summary) || promptNetwork, runtimeAlerts: objectOrNull(runtimeAlerts.summary) || runtimeAlerts, runnerErrors: arr(source.runnerErrors).slice(-8), commandFailures: arr(source.commandFailures).slice(-8), commandState: objectOrNull(source.commandState) || null, toolFindings: arr(source.toolFindings).slice(0, 8).map(slimFinding), httpErrorGroups: arr(source.httpErrorGroups ?? runtimeAlerts.networkHttpErrorsByPath).slice(0, 8).map(slimGroup), requestFailedGroups: arr(source.requestFailedGroups ?? runtimeAlerts.networkRequestFailedByPath).slice(0, 8).map(slimGroup), domDiagnosticGroups: arr(source.domDiagnosticGroups ?? runtimeAlerts.domDiagnosticsByText).slice(0, 5).map(slimGroup), domDiagnosticSamples: arr(source.domDiagnosticSamples ?? runtimeAlerts.domDiagnostics).slice(0, 8).map(slimGroup), consoleAlertGroups: arr(source.consoleAlertGroups ?? runtimeAlerts.consoleAlertsByPath).slice(0, 8).map(slimGroup), consoleAlertSamples: arr(source.consoleAlertSamples ?? runtimeAlerts.consoleAlerts).slice(0, 8).map(slimGroup), turnTimingRecentUpdateJumps: arr(source.turnTimingRecentUpdateJumps ?? srcMetrics.turnTimingRecentUpdateSawtoothJumps).slice(0, 8), turnTimingElapsedZeroResets: arr(source.turnTimingElapsedZeroResets ?? srcMetrics.turnTimingElapsedZeroResets).slice(0, 8), turnTimingTotalElapsedForwardJumps: arr(source.turnTimingTotalElapsedForwardJumps ?? srcMetrics.turnTimingTotalElapsedForwardJumps).slice(0, 8), pagePerformanceSlowApi: slowApis.slice(0, 8).map(slimSlowApi), archivePagePerformanceSlowApi: arr(source.archivePagePerformanceSlowApi).slice(0, 8).map(slimSlowApi), pagePerformancePartialApi: arr(source.pagePerformancePartialApi).slice(0, 8), pagePerformanceSseStreams: arr(source.pagePerformanceSseStreams).slice(0, 8), findings: arr(source.findings).slice(0, 12).map(slimFinding), archiveRedFindings: arr(source.archiveRedFindings ?? archiveSummary.redFindings).slice(0, 12).map(slimFinding), reportJsonPath: source.reportJsonPath ?? reportJsonPath, reportJsonSha256: source.reportJsonSha256 ?? sha256(reportJsonPath), reportMdPath: source.reportMdPath ?? reportMdPath, reportMdSha256: source.reportMdSha256 ?? sha256(reportMdPath), analyzer: { ...(objectOrNull(source.analyzer) || {}), recoveredFrom: 'analysis-artifact-after-transport-timeout', stdoutPath, stderrPath, stdoutBytes: statSize(stdoutPath), stderrBytes: statSize(stderrPath), reportJsonBytes: statSize(reportJsonPath), reportMdBytes: statSize(reportMdPath), transportExitCode: Number(transportExitRaw), transportTimedOut: transportTimedOutRaw === 'true', valuesRedacted: true }, valuesRedacted: true };",
"console.log(JSON.stringify(compact));",
"UNIDESK_WEB_OBSERVE_RECOVER_ANALYZE_ARTIFACT",
].join("\n");
const recovered = parseJsonObject(runTransWorkspaceStdinScript(options.node, spec.workspace, recoverScript, Math.min(options.commandTimeoutSeconds, 30)).stdout);
return recovered;
}