From 0873bba8cd400c63695804cb5745af7d92680c9f Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 18 Jun 2026 06:41:37 +0000 Subject: [PATCH] fix: compact hwlab web probe script output --- scripts/src/hwlab-node-impl.ts | 39 +++- .../src/hwlab-node-web-probe-runner-source.ts | 170 +++++++++++++++++- scripts/src/hwlab-node-web-probe-summary.ts | 12 +- 3 files changed, 210 insertions(+), 11 deletions(-) diff --git a/scripts/src/hwlab-node-impl.ts b/scripts/src/hwlab-node-impl.ts index b4a68073..b48ef1ff 100644 --- a/scripts/src/hwlab-node-impl.ts +++ b/scripts/src/hwlab-node-impl.ts @@ -5131,16 +5131,47 @@ function runNodeWebProbeScript( ): Record { const script = nodeWebProbeScriptRemoteShell(options, secretSpec, material.password ?? ""); const result = runTransWorkspaceStdinScript(options.node, spec.workspace, script, options.commandTimeoutSeconds); - const report = compactWebProbeScriptResult(parseJsonObject(result.stdout)); + const parsedReport = parseJsonObject(result.stdout); + const report = compactWebProbeScriptResult(parsedReport); const passed = result.exitCode === 0 && report?.ok === true; const summary = nullableRecord(report?.summary); + const stdoutBytes = Buffer.byteLength(result.stdout, "utf8"); + const outputFailureKind = parsedReport === null + ? stdoutBytes > 64 * 1024 + ? "web-probe-output-too-large" + : "web-probe-report-parse-failed" + : null; const degradedReason = typeof summary?.degradedReason === "string" ? summary.degradedReason : typeof report?.failureKind === "string" ? report.failureKind + : outputFailureKind + ? outputFailureKind : result.timedOut ? "web-probe-command-timeout" : null; + const failureKind = typeof summary?.failureKind === "string" + ? summary.failureKind + : typeof report?.failureKind === "string" + ? report.failureKind + : outputFailureKind; + const effectiveSummary = summary ?? (outputFailureKind === null ? null : { + ok: false, + status: "blocked", + degradedReason: outputFailureKind, + failureKind: outputFailureKind, + failedCondition: outputFailureKind === "web-probe-output-too-large" + ? "remote web-probe stdout exceeded the bounded summary parser input" + : "remote web-probe stdout did not contain a parseable JSON report", + stdoutBytes, + stderrTail: result.stderr.trim().slice(-2000), + valuesRedacted: true, + }); + const compactResult = compactCommandResultRedacted(result, [material.password ?? ""]); + if (outputFailureKind !== null) { + compactResult.stdoutTail = redactKnownSecrets(result.stdout.slice(-2000), [material.password ?? ""]); + compactResult.stderrTail = redactKnownSecrets(result.stderr.slice(-2000), [material.password ?? ""]); + } return { ok: passed, status: passed ? "pass" : "blocked", @@ -5152,10 +5183,10 @@ function runNodeWebProbeScript( credential, scriptSource: options.scriptSource, degradedReason, - failureKind: typeof summary?.failureKind === "string" ? summary.failureKind : typeof report?.failureKind === "string" ? report.failureKind : null, - summary, + failureKind, + summary: effectiveSummary, probe: report, - result: compactCommandResultRedacted(result, [material.password ?? ""]), + result: compactResult, valuesRedacted: true, }; } diff --git a/scripts/src/hwlab-node-web-probe-runner-source.ts b/scripts/src/hwlab-node-web-probe-runner-source.ts index a5267d00..341760ca 100644 --- a/scripts/src/hwlab-node-web-probe-runner-source.ts +++ b/scripts/src/hwlab-node-web-probe-runner-source.ts @@ -1499,7 +1499,175 @@ async function emit(payload) { artifacts: { runDir, items: artifactRecords }, }; finalPayload.summary = scriptIssueSummary(finalPayload); - process.stdout.write(JSON.stringify(sanitize(finalPayload), null, 2) + "\n"); + process.stdout.write(JSON.stringify(sanitize(compactStdoutPayload(finalPayload)), null, 2) + "\n"); +} + +function compactStdoutPayload(payload) { + return { + ok: payload?.ok === true, + status: payload?.status ?? null, + command: payload?.command ?? "web-probe-script", + generatedAt: payload?.generatedAt ?? null, + startedAt: payload?.startedAt ?? null, + baseUrl: payload?.baseUrl ?? null, + finalUrl: payload?.finalUrl ?? payload?.lastUrl ?? null, + lastUrl: payload?.lastUrl ?? null, + scriptSha256: payload?.scriptSha256 ?? null, + runDir, + reportPath: payload?.reportPath ?? null, + reportSha256: payload?.reportSha256 ?? null, + auth: payload?.auth ?? null, + script: compactScriptForStdout(payload?.script), + steps: compactStepsForStdout(publicSteps()), + failureKind: payload?.failureKind ?? null, + error: payload?.error ?? null, + errorMessage: typeof payload?.errorMessage === "string" ? compactText(payload.errorMessage, 1200) : payload?.errorMessage ?? null, + guidance: typeof payload?.guidance === "string" ? compactText(payload.guidance, 1200) : payload?.guidance ?? null, + lastScreenshot: compactArtifactForStdout(payload?.lastScreenshot), + readiness: compactJsonForStdout(payload?.readiness), + artifacts: compactArtifactsForStdout(payload?.artifacts), + summary: compactSummaryForStdout(payload?.summary), + safety: { + valuesRedacted: true, + secretValuesPrinted: false, + stdoutCompacted: true, + fullReportInArtifact: true, + }, + }; +} + +function compactScriptForStdout(script) { + const value = script && typeof script === "object" ? script : {}; + const steps = Array.isArray(value.steps) ? value.steps : []; + return { + ok: value.ok === true, + result: compactJsonForStdout(value.result), + stepCount: steps.length, + }; +} + +function compactStepsForStdout(steps) { + if (!Array.isArray(steps)) return []; + return steps.slice(-3).map((step) => compactStepForStdout(step)); +} + +function compactStepForStdout(step) { + const value = step && typeof step === "object" ? step : {}; + return { + index: Number.isFinite(value.index) ? value.index : null, + name: typeof value.name === "string" ? value.name : null, + ok: typeof value.ok === "boolean" ? value.ok : null, + atMs: Number.isFinite(value.atMs) ? value.atMs : null, + data: compactStepDataForStdout(value.data), + }; +} + +function compactStepDataForStdout(data) { + if (data && typeof data === "object" && !Array.isArray(data) && Array.isArray(data.items) && typeof data.failedCount === "number") { + return compactApiMatrixForStdout(data); + } + if (data && typeof data === "object" && !Array.isArray(data)) { + const out = {}; + for (const [key, value] of Object.entries(data).slice(0, 8)) { + if (key === "apiMatrix") out[key] = compactApiMatrixForStdout(value); + else if (/screenshot/iu.test(key)) out[key] = compactArtifactForStdout(value); + else out[key] = compactJsonForStdout(value); + } + return out; + } + return compactJsonForStdout(data); +} + +function compactSummaryForStdout(summary) { + const value = summary && typeof summary === "object" ? summary : {}; + return { + ok: value.ok === true, + status: value.status ?? null, + degradedReason: value.degradedReason ?? null, + failureKind: value.failureKind ?? null, + failedCondition: typeof value.failedCondition === "string" ? compactText(value.failedCondition, 1200) : value.failedCondition ?? null, + nextAction: typeof value.nextAction === "string" ? compactText(value.nextAction, 1200) : value.nextAction ?? null, + baseUrl: value.baseUrl ?? null, + finalUrl: value.finalUrl ?? null, + scriptSha256: value.scriptSha256 ?? null, + runDir: value.runDir ?? runDir, + reportPath: value.reportPath ?? null, + reportSha256: value.reportSha256 ?? null, + lastScreenshot: compactArtifactForStdout(value.lastScreenshot), + screenshots: Array.isArray(value.screenshots) ? value.screenshots.slice(-5).map((item) => compactArtifactForStdout(item)).filter(Boolean) : [], + apiMatrix: compactApiMatrixForStdout(value.apiMatrix), + stepCount: Number.isFinite(value.stepCount) ? value.stepCount : null, + lastStep: compactStepForStdout(value.lastStep), + valuesRedacted: true, + }; +} + +function compactArtifactsForStdout(artifacts) { + const items = Array.isArray(artifacts?.items) ? artifacts.items : artifactRecords; + return { + runDir, + count: items.length, + screenshots: items.filter((item) => item && typeof item === "object" && item.kind === "screenshot").slice(-5).map((item) => compactArtifactForStdout(item)).filter(Boolean), + items: items.slice(-12).map((item) => compactArtifactForStdout(item)).filter(Boolean), + }; +} + +function compactArtifactForStdout(item) { + if (!item || typeof item !== "object") return null; + return { + kind: item.kind ?? null, + path: item.path ?? null, + byteCount: Number.isFinite(item.byteCount) ? item.byteCount : null, + sha256: item.sha256 ?? null, + error: typeof item.error === "string" ? compactText(item.error, 600) : item.error ?? null, + }; +} + +function compactApiMatrixForStdout(matrix) { + if (!matrix || typeof matrix !== "object" || Array.isArray(matrix)) return null; + return { + ok: matrix.ok === true, + count: Number.isFinite(matrix.count) ? matrix.count : null, + okCount: Number.isFinite(matrix.okCount) ? matrix.okCount : null, + failedCount: Number.isFinite(matrix.failedCount) ? matrix.failedCount : null, + items: Array.isArray(matrix.items) + ? matrix.items.slice(0, 12).map((item) => { + const row = item && typeof item === "object" ? item : {}; + return { + name: typeof row.name === "string" ? row.name : null, + path: typeof row.path === "string" ? row.path : null, + method: typeof row.method === "string" ? row.method : null, + ok: row.ok === true, + status: Number.isFinite(row.status) ? row.status : null, + error: typeof row.error === "string" ? compactText(row.error, 300) : row.error ?? null, + failureKind: typeof row.failureKind === "string" ? row.failureKind : null, + bodyKeys: Array.isArray(row.bodyKeys) ? row.bodyKeys.filter((key) => typeof key === "string").slice(0, 12) : [], + }; + }) + : [], + }; +} + +function compactJsonForStdout(value, depth = 0) { + if (value === null || value === undefined) return value ?? null; + if (typeof value === "string") return compactText(value, 180); + if (typeof value === "number" || typeof value === "boolean") return value; + if (typeof value === "bigint") return String(value); + if (typeof value === "function" || typeof value === "symbol") return "[" + typeof value + "]"; + if (depth >= 3) return "[max-depth]"; + if (Array.isArray(value)) return value.slice(0, 4).map((item) => compactJsonForStdout(item, depth + 1)); + if (typeof value === "object") { + const out = {}; + for (const [key, nested] of Object.entries(value).slice(0, 8)) { + out[key] = compactJsonForStdout(nested, depth + 1); + } + return out; + } + return compactText(String(value), 180); +} + +function compactText(value, maxChars) { + return redactString(String(value)).replace(/\s+/gu, " ").trim().slice(0, maxChars); } function scriptIssueSummary(payload) { diff --git a/scripts/src/hwlab-node-web-probe-summary.ts b/scripts/src/hwlab-node-web-probe-summary.ts index 9ed2997b..c5f76c42 100644 --- a/scripts/src/hwlab-node-web-probe-summary.ts +++ b/scripts/src/hwlab-node-web-probe-summary.ts @@ -48,7 +48,7 @@ function compactWebProbeScriptBlock(value: unknown): Record { function compactWebProbeSteps(value: unknown): Record[] { if (!Array.isArray(value)) return []; - return value.slice(-30).map((item) => { + return value.slice(-5).map((item) => { const step = record(item); return { index: typeof step.index === "number" ? step.index : null, @@ -85,18 +85,18 @@ function compactIssueSummary(value: Record): Record= 5) return "[max-depth]"; - if (Array.isArray(value)) return value.slice(0, 30).map((item) => compactJsonForIssue(item, depth + 1)); + if (depth >= 4) return "[max-depth]"; + if (Array.isArray(value)) return value.slice(0, 8).map((item) => compactJsonForIssue(item, depth + 1)); if (typeof value === "object") { const out: Record = {}; - for (const [key, nested] of Object.entries(value as Record).slice(0, 40)) { + for (const [key, nested] of Object.entries(value as Record).slice(0, 16)) { out[key] = compactJsonForIssue(nested, depth + 1); } return out; } - return String(value).slice(0, 600); + return String(value).slice(0, 240); } function compactStepForIssue(value: unknown): Record | null {