fix: extend hwlab web probe sampling timeout

This commit is contained in:
Codex
2026-06-17 15:48:34 +00:00
parent 38f14cdfd2
commit 542cf37935
2 changed files with 132 additions and 11 deletions
+131 -10
View File
@@ -32,6 +32,8 @@ interface NodeWebProbeRunOptions {
freshSession: boolean;
cancelRunning: boolean;
commandTimeoutSeconds: number;
commandTimeoutAutoSeconds: number;
commandTimeoutUserProvided: boolean;
}
interface NodeWebProbeScriptOptions {
@@ -4784,14 +4786,16 @@ function parseNodeWebProbeOptions(args: string[]): NodeWebProbeOptions {
}
const scriptText = scriptFile === undefined ? readFileSync(0, "utf8") : readFileSync(scriptFile, "utf8");
if (scriptText.trim().length === 0) throw new Error("web-probe script received an empty script");
const timeoutMs = positiveIntegerOption(args, "--timeout-ms", 30000, 60000);
const commandTimeoutSeconds = positiveIntegerOption(args, "--command-timeout-seconds", Math.max(60, Math.ceil(timeoutMs / 1000) + 30), 3600);
return {
action: "script",
node,
lane,
url: optionValue(args, "--url") ?? spec.publicWebUrl,
timeoutMs: positiveIntegerOption(args, "--timeout-ms", 30000, 60000),
timeoutMs,
viewport: optionValue(args, "--viewport") ?? "1440x900",
commandTimeoutSeconds: positiveIntegerOption(args, "--command-timeout-seconds", 60, 60),
commandTimeoutSeconds,
scriptText,
scriptSource: {
kind: scriptFile === undefined ? "stdin" : "file",
@@ -4818,25 +4822,69 @@ function parseNodeWebProbeOptions(args: string[]): NodeWebProbeOptions {
"--fresh-session",
"--no-cancel-running",
]));
const timeoutMs = positiveIntegerOption(args, "--timeout-ms", 30000, 60000);
const waitAfterSubmitMs = positiveIntegerOption(args, "--wait-after-submit-ms", 1500, 60000);
const waitMessagesMs = positiveIntegerOption(args, "--wait-messages-ms", 2500, 60000);
const waitAgentTerminalMs = positiveIntegerOption(args, "--wait-agent-terminal-ms", 0, 600000);
const traceSampleCount = positiveIntegerOption(args, "--trace-sample-count", 0, 200);
const traceSampleIntervalMs = positiveIntegerOption(args, "--trace-sample-interval-ms", 0, 60000);
const commandTimeoutAutoSeconds = nodeWebProbeAutoCommandTimeoutSeconds({
timeoutMs,
waitAfterSubmitMs,
waitMessagesMs,
waitAgentTerminalMs,
traceSampleCount,
traceSampleIntervalMs,
freshSession: args.includes("--fresh-session"),
hasMessage: optionValue(args, "--message") !== undefined,
});
const commandTimeoutRaw = optionValue(args, "--command-timeout-seconds");
const commandTimeoutUserProvided = commandTimeoutRaw !== undefined;
const commandTimeoutSeconds = commandTimeoutUserProvided
? Math.max(positiveIntegerOption(args, "--command-timeout-seconds", commandTimeoutAutoSeconds, 3600), commandTimeoutAutoSeconds)
: commandTimeoutAutoSeconds;
return {
action: "run",
node,
lane,
url: optionValue(args, "--url") ?? spec.publicWebUrl,
timeoutMs: positiveIntegerOption(args, "--timeout-ms", 30000, 60000),
waitAfterSubmitMs: positiveIntegerOption(args, "--wait-after-submit-ms", 1500, 60000),
waitMessagesMs: positiveIntegerOption(args, "--wait-messages-ms", 2500, 60000),
waitAgentTerminalMs: positiveIntegerOption(args, "--wait-agent-terminal-ms", 0, 600000),
traceSampleCount: positiveIntegerOption(args, "--trace-sample-count", 0, 200),
traceSampleIntervalMs: positiveIntegerOption(args, "--trace-sample-interval-ms", 0, 60000),
timeoutMs,
waitAfterSubmitMs,
waitMessagesMs,
waitAgentTerminalMs,
traceSampleCount,
traceSampleIntervalMs,
message: optionValue(args, "--message") ?? null,
conversationId: optionValue(args, "--conversation-id") ?? null,
freshSession: args.includes("--fresh-session"),
cancelRunning: !args.includes("--no-cancel-running"),
commandTimeoutSeconds: positiveIntegerOption(args, "--command-timeout-seconds", 60, 60),
commandTimeoutSeconds,
commandTimeoutAutoSeconds,
commandTimeoutUserProvided,
};
}
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)));
}
function assertKnownOptions(args: string[], valueOptions: Set<string>, flagOptions: Set<string>): void {
for (let index = 0; index < args.length; index += 1) {
const arg = args[index] ?? "";
@@ -4894,6 +4942,11 @@ function runNodeWebProbe(options: NodeWebProbeOptions): Record<string, unknown>
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 degradedReason = result.timedOut
? "web-probe-command-timeout"
: typeof probe?.degradedReason === "string"
? probe.degradedReason
: null;
return {
ok: passed,
status: passed ? "pass" : "blocked",
@@ -4903,6 +4956,13 @@ function runNodeWebProbe(options: NodeWebProbeOptions): Record<string, unknown>
workspace: spec.workspace,
url: options.url,
credential,
commandTimeout: {
seconds: options.commandTimeoutSeconds,
autoSeconds: options.commandTimeoutAutoSeconds,
userProvided: options.commandTimeoutUserProvided,
timedOut: result.timedOut,
},
degradedReason,
probe,
result: compactCommandResult(result),
valuesRedacted: true,
@@ -9212,16 +9272,25 @@ function parseJsonObject(text: string): Record<string, unknown> | null {
function compactWebProbeResult(report: Record<string, unknown> | null): Record<string, unknown> | null {
if (report === null) return null;
const dom = record(report.dom);
const performance = record(report.performance);
const trace = record(report.trace);
const session = record(report.session);
return {
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,
actions: Array.isArray(report.actions) ? report.actions : [],
degradedReason: typeof report.degradedReason === "string" ? report.degradedReason : null,
actions: compactWebProbeActions(report.actions),
session,
trace,
performance,
traceSamples: Array.isArray(report.traceSamples) ? report.traceSamples : [],
dom: {
authState: typeof dom.authState === "string" ? dom.authState : null,
requiredSelectors: record(dom.requiredSelectors),
messageCount: typeof dom.messageCount === "number" ? dom.messageCount : null,
sessionRail: record(dom.sessionRail),
},
failureDom: record(report.failureDom),
artifacts: record(report.artifacts),
@@ -9229,6 +9298,58 @@ function compactWebProbeResult(report: Record<string, unknown> | null): Record<s
};
}
function compactWebProbeActions(value: unknown): Record<string, unknown>[] {
if (!Array.isArray(value)) return [];
return value.map(record).map((action) => {
const name = typeof action.action === "string" ? action.action : "unknown";
if (name === "fresh-session") {
const alignment = record(action.alignment);
const check = record(alignment.check);
const repair = record(alignment.repair);
return {
action: name,
settled: action.settled === true,
aligned: action.aligned === true,
reason: alignment.reason ?? check.reason ?? null,
conversationId: check.conversationId ?? null,
routeConversationId: check.routeConversationId ?? null,
selectedConversationId: check.selectedConversationId ?? null,
repaired: Object.keys(repair).length > 0,
repairConversationId: repair.conversationId ?? null,
attempts: Array.isArray(alignment.attempts) ? alignment.attempts.length : null,
};
}
if (name === "trace-interval-samples") {
return {
action: name,
count: action.count ?? null,
intervalMs: action.intervalMs ?? null,
};
}
if (name === "submit-prompt") {
return {
action: name,
chars: action.chars ?? null,
submittedAt: action.submittedAt ?? null,
};
}
if (name === "wait-agent-terminal") {
return {
action: name,
terminal: action.terminal === true,
timeoutMs: action.timeoutMs ?? null,
};
}
return {
action: name,
skipped: action.skipped ?? null,
ready: action.ready ?? null,
found: action.found ?? null,
timeoutMs: action.timeoutMs ?? null,
};
});
}
function compactCommand(command: string[]): string[] {
const scriptIndex = command.indexOf("--");
if (scriptIndex >= 0 && scriptIndex + 1 < command.length) return [...command.slice(0, scriptIndex + 1), "<script omitted>"];