fix: tighten web probe in-place refresh evidence

This commit is contained in:
Codex
2026-06-28 00:54:30 +00:00
parent e7ea14da5f
commit 0b926a3332
2 changed files with 41 additions and 2 deletions
@@ -2467,11 +2467,15 @@ function detectWorkbenchTerminalApiDomLag(samples, network) {
for (const event of terminalEvents) {
const pageSamples = rowsByPage.get(event.pageKey) || [];
if (pageSamples.length === 0 || event.tsMs < pageSamples[0].tsMs) continue;
const alreadyVisible = lastWorkbenchSampleAtOrBefore(pageSamples, event.tsMs, event, (row) => workbenchSampleHasTerminalProjection(row.sample, event));
if (alreadyVisible) continue;
const firstAfter = firstWorkbenchSampleAfter(pageSamples, event.tsMs, event.tsMs + windowMs, event);
const firstMiss = firstWorkbenchSampleAfter(pageSamples, event.tsMs, event.tsMs + budgetMs, event, (row) => !workbenchSampleHasTerminalProjection(row.sample, event));
const resolved = firstWorkbenchSampleAfter(pageSamples, event.tsMs, event.tsMs + windowMs, event, (row) => workbenchSampleHasTerminalProjection(row.sample, event));
const deltaMs = resolved ? Math.max(0, Math.round(resolved.tsMs - event.tsMs)) : null;
const unresolved = !resolved;
const exceedsBudget = unresolved || (Number.isFinite(deltaMs) && deltaMs > budgetMs);
if (!firstMiss) continue;
if (!exceedsBudget) continue;
overBudget.push({
ts: event.ts,
@@ -2490,6 +2494,7 @@ function detectWorkbenchTerminalApiDomLag(samples, network) {
resolvedDeltaMs: deltaMs,
unresolvedWithinWindow: unresolved,
firstAfterSample: compactWorkbenchProjectionSample(firstAfter?.sample, event),
firstMissSample: compactWorkbenchProjectionSample(firstMiss?.sample, event),
resolvedSample: compactWorkbenchProjectionSample(resolved?.sample, event),
valuesRedacted: true
});
@@ -2547,6 +2552,17 @@ function isReliableWorkbenchTerminalApiEvent(summary, routeKind) {
return Number(summary.runningStatusCount ?? 0) <= 0;
}
function lastWorkbenchSampleAtOrBefore(rows, tsMs, event, predicate = null) {
let result = null;
for (const row of rows || []) {
if (row.tsMs > tsMs) break;
if (!workbenchSampleMatchesTerminalEvent(row.sample, event)) continue;
if (typeof predicate === "function" && !predicate(row)) continue;
result = row;
}
return result;
}
function firstWorkbenchSampleAfter(rows, startMs, endMs, event, predicate = null) {
for (const row of rows || []) {
if (row.tsMs < startMs) continue;
@@ -324,9 +324,16 @@ async function drainOneCommand() {
function startCommandActiveSampler(command) {
const intervalMs = Math.max(1000, Number(sampleIntervalMs) || 5000);
const heartbeatIntervalMs = Math.min(5000, intervalMs);
let stopped = false;
let timer = null;
let heartbeatTimer = null;
let inFlight = false;
const heartbeat = () => {
if (stopped) return;
void writeHeartbeat({ status: terminalStatus, activeCommandId: command.id, activeCommandType: command.type, commandActive: true })
.catch((error) => appendJsonl(files.errors, eventRecord("command-active-heartbeat-error", { commandId: command.id, commandType: command.type, error: errorSummary(error) })));
};
const schedule = () => {
if (stopped) return;
timer = setTimeout(tick, intervalMs);
@@ -346,10 +353,13 @@ function startCommandActiveSampler(command) {
schedule();
});
};
heartbeatTimer = setInterval(heartbeat, heartbeatIntervalMs);
if (heartbeatTimer && typeof heartbeatTimer.unref === "function") heartbeatTimer.unref();
schedule();
return () => {
stopped = true;
if (timer) clearTimeout(timer);
if (heartbeatTimer) clearInterval(heartbeatTimer);
};
}
@@ -1605,7 +1615,7 @@ async function sendPrompt(text, options = {}) {
const editor = await primaryEditor.isVisible().catch(() => false)
? primaryEditor
: page.locator('textarea, [role="textbox"], [contenteditable="true"], input[type="text"]').last();
await editor.waitFor({ state: "visible", timeout: 15000 });
await withHardTimeout(editor.waitFor({ state: "visible", timeout: 15000 }), 20000, "sendPrompt composer editor did not become visible within 20s");
await fillComposerEditor(editor, text);
const primarySubmitSelector = '#command-send, #command-submit, [data-testid="command-submit"], [data-testid="composer-submit"], [data-testid="send-command"]';
const primarySubmit = page.locator(primarySubmitSelector).last();
@@ -1619,7 +1629,7 @@ async function sendPrompt(text, options = {}) {
'[aria-label*="send" i]',
'[aria-label*="发送"]'
].join(", ")).last();
await submit.waitFor({ state: "visible", timeout: 15000 });
await withHardTimeout(submit.waitFor({ state: "visible", timeout: 15000 }), 20000, "sendPrompt submit button did not become visible within 20s");
if (options.expectedAction) {
const configuredActionWaitMs = options.expectedActionWaitMs === null || options.expectedActionWaitMs === undefined || options.expectedActionWaitMs === ""
? null
@@ -4405,5 +4415,18 @@ function errorSummary(error) {
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
}
function withHardTimeout(promise, timeoutMs, message) {
const guarded = Promise.resolve(promise);
let timer = null;
const timeout = new Promise((_, reject) => {
timer = setTimeout(() => reject(new Error(message)), Math.max(1, Number(timeoutMs) || 1));
if (timer && typeof timer.unref === "function") timer.unref();
});
return Promise.race([guarded, timeout]).finally(() => {
if (timer) clearTimeout(timer);
guarded.catch(() => {});
});
}
`;
}