Merge pull request #470 from pikasTech/fix/469-web-probe-compact

fix: 收敛 web-probe script stdout 摘要
This commit is contained in:
Lyon
2026-06-18 14:42:40 +08:00
committed by GitHub
3 changed files with 210 additions and 11 deletions
+35 -4
View File
@@ -5131,16 +5131,47 @@ function runNodeWebProbeScript(
): Record<string, unknown> {
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,
};
}
@@ -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) {
+6 -6
View File
@@ -48,7 +48,7 @@ function compactWebProbeScriptBlock(value: unknown): Record<string, unknown> {
function compactWebProbeSteps(value: unknown): Record<string, unknown>[] {
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<string, unknown>): Record<string, unk
function compactJsonForIssue(value: unknown, depth = 0): unknown {
if (value === null || value === undefined) return value ?? null;
if (typeof value === "string") return value.replace(/\s+/gu, " ").trim().slice(0, 600);
if (typeof value === "string") return value.replace(/\s+/gu, " ").trim().slice(0, 240);
if (typeof value === "number" || typeof value === "boolean") return value;
if (depth >= 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<string, unknown> = {};
for (const [key, nested] of Object.entries(value as Record<string, unknown>).slice(0, 40)) {
for (const [key, nested] of Object.entries(value as Record<string, unknown>).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<string, unknown> | null {