fix: tighten web probe in-place refresh evidence
This commit is contained in:
@@ -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(() => {});
|
||||
});
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user