Background
#521 added a per-iteration watchdog: WebAgent exposes this.currentIterationSignal (the caller's abortSignal combined with the iteration's watchdog AbortController) and threads it into the directly-issued iteration LLM calls — the action streamText and the validation generateTextWithRetry. When the watchdog fires, those in-flight calls are aborted.
Gap
The agent's tools are created once, before the loop, in runMainLoop:
const webActionTools = createWebActionTools({
...
abortSignal: this.abortSignal, // external signal only, captured at creation time
...
});
So the extract tool (which runs its own generateTextWithRetry) uses the external abort signal, not the per-iteration watchdog signal. When the watchdog fires mid-extract, that in-flight LLM request is not cancelled.
Impact
Low — not a hang risk. The Promise.race in runWithIterationWatchdog still bounds the overall iteration, so a stuck extract can't freeze the task. The only downside is best-effort cleanup: an extract LLM call already abandoned by a timed-out iteration keeps running in the background until it completes on its own (one orphaned request, GC'd when the page/process ends).
Suggested approach
Give the tools access to the current per-iteration signal rather than a static one captured at creation. Options:
- Pass a
() => AbortSignal | undefined accessor into createWebActionTools that returns this.currentIterationSignal ?? this.abortSignal, and have the extract tool read it at call time; or
- Recreate (or rebind the signal on) the relevant tools per iteration.
Prefer the accessor — it's the smallest change and keeps tool creation outside the loop.
Context
Surfaced in Copilot's review of #521; the docstring on runWithIterationWatchdog was updated there to scope the watchdog signal accurately (action stream + validation only). This issue tracks closing the remaining gap for extract.
Background
#521 added a per-iteration watchdog:
WebAgentexposesthis.currentIterationSignal(the caller'sabortSignalcombined with the iteration's watchdogAbortController) and threads it into the directly-issued iteration LLM calls — the actionstreamTextand the validationgenerateTextWithRetry. When the watchdog fires, those in-flight calls are aborted.Gap
The agent's tools are created once, before the loop, in
runMainLoop:So the
extracttool (which runs its owngenerateTextWithRetry) uses the external abort signal, not the per-iteration watchdog signal. When the watchdog fires mid-extract, that in-flight LLM request is not cancelled.Impact
Low — not a hang risk. The
Promise.raceinrunWithIterationWatchdogstill bounds the overall iteration, so a stuck extract can't freeze the task. The only downside is best-effort cleanup: an extract LLM call already abandoned by a timed-out iteration keeps running in the background until it completes on its own (one orphaned request, GC'd when the page/process ends).Suggested approach
Give the tools access to the current per-iteration signal rather than a static one captured at creation. Options:
() => AbortSignal | undefinedaccessor intocreateWebActionToolsthat returnsthis.currentIterationSignal ?? this.abortSignal, and have the extract tool read it at call time; orPrefer the accessor — it's the smallest change and keeps tool creation outside the loop.
Context
Surfaced in Copilot's review of #521; the docstring on
runWithIterationWatchdogwas updated there to scope the watchdog signal accurately (action stream + validation only). This issue tracks closing the remaining gap forextract.