diff --git a/packages/extension/services/actService.ts b/packages/extension/services/actService.ts index fdbf22cd8..144d614fd 100644 --- a/packages/extension/services/actService.ts +++ b/packages/extension/services/actService.ts @@ -157,7 +157,17 @@ export async function act({ }, } : undefined; - await waitForDomNetworkQuiet(page.mainFrame(), logger, domSettleTimeoutMs); + // With Jev on, the intent request (which needs no page) runs while the DOM + // settles; everything that reads or touches the page still waits for it. + // performance.now(): tests script Date.now() for inference timing. + const actStartedAt = performance.now(); + const settled = waitForDomNetworkQuiet(page.mainFrame(), logger, domSettleTimeoutMs); + // The cache lookup keys on the page's tree and URL, so when a cache is in + // play the page must have settled before it; only cache-less acts overlap. + const cacheLookup = cache !== undefined && options?.cache !== false; + const overlapSettle = jevAct !== undefined && jevAct.enabled !== false && !cacheLookup; + if (overlapSettle) settled.catch(() => {}); + else await settled; ensureTimeRemaining(); let actPath: "llm" | "jev" | "jev+arg-llm" | "jev+llm" | "jev-tool" | "jev-tool+arg-llm" = "llm"; let usedArgumentLlm = false; @@ -175,9 +185,11 @@ export async function act({ bypass: cacheService.shouldBypassCacheForLocatorScope(options), context: cache, logger, - onHit: (value) => replayCachedActions(value, instruction, variables, context), + onHit: async (value) => { + await settled; + return await replayCachedActions(value, instruction, variables, context); + }, execute: async () => { - // performance.now(): tests script Date.now() for inference timing. const startedAt = performance.now(); const result = await runActPipeline(); // Whatever Jev already did changed the page, whether or not the act @@ -195,6 +207,8 @@ export async function act({ path: actPath, success: result.data.success, durationMs: Math.round(performance.now() - startedAt), + // From the start of act(), DOM settle included: what the caller waits for. + totalMs: Math.round(performance.now() - actStartedAt), llmInputTokens: result.metadata.usage.inputTokens, llmOutputTokens: result.metadata.usage.outputTokens, llmMs: result.metadata.usage.inferenceTimeMs, @@ -229,6 +243,7 @@ export async function act({ snapshotOptions, ensureTimeRemaining, openPageCount, + settled, ...(webmcp ? { webmcp } : {}), extractText: async (text) => { const response = await inference.actTextArgument({ @@ -285,6 +300,7 @@ export async function act({ } } + await settled; const { combinedTree, combinedXpathMap } = await page.captureSnapshot(snapshotOptions); const actPrompt = buildActPrompt( diff --git a/packages/extension/services/jevAct/README.md b/packages/extension/services/jevAct/README.md index 08f504a27..8fc20979b 100644 --- a/packages/extension/services/jevAct/README.md +++ b/packages/extension/services/jevAct/README.md @@ -24,6 +24,12 @@ deliberately not a field of the public create config. Evals build that variable | Checks | code | Fill read-back, native `