fix: throw unhonored AbortError & rm detection code for it
This commit is contained in:
@@ -21,7 +21,7 @@ import type {
|
|||||||
MacroToolInput,
|
MacroToolInput,
|
||||||
MacroToolResult,
|
MacroToolResult,
|
||||||
} from './types'
|
} from './types'
|
||||||
import { assert, fetchLlmsTxt, normalizeResponse, onAbortTimeout, uid, waitFor } from './utils'
|
import { assert, fetchLlmsTxt, normalizeResponse, uid, waitFor } from './utils'
|
||||||
|
|
||||||
export { tool, type PageAgentTool } from './tools'
|
export { tool, type PageAgentTool } from './tools'
|
||||||
export type * from './types'
|
export type * from './types'
|
||||||
@@ -87,7 +87,7 @@ export class PageAgentCore extends EventTarget {
|
|||||||
* Task cancellation primitive: its signal reaches the LLM fetch, tools
|
* Task cancellation primitive: its signal reaches the LLM fetch, tools
|
||||||
* (via `ctx.signal`) and async callbacks. Aborted only by `stop`/`dispose`
|
* (via `ctx.signal`) and async callbacks. Aborted only by `stop`/`dispose`
|
||||||
* (during a task) or task setup, always WITHOUT a reason so `signal.reason`
|
* (during a task) or task setup, always WITHOUT a reason so `signal.reason`
|
||||||
* stays a standard `AbortError`. Never abort as a cleanup/error shortcut.
|
* stays a standard `AbortError`.
|
||||||
*/
|
*/
|
||||||
#abortController = new AbortController()
|
#abortController = new AbortController()
|
||||||
#observations: string[] = []
|
#observations: string[] = []
|
||||||
@@ -382,7 +382,8 @@ export class PageAgentCore extends EventTarget {
|
|||||||
description: 'You MUST call this tool every step!',
|
description: 'You MUST call this tool every step!',
|
||||||
inputSchema: macroToolSchema as z.ZodType<MacroToolInput>,
|
inputSchema: macroToolSchema as z.ZodType<MacroToolInput>,
|
||||||
execute: async (input: MacroToolInput): Promise<MacroToolResult> => {
|
execute: async (input: MacroToolInput): Promise<MacroToolResult> => {
|
||||||
this.#abortController.signal.throwIfAborted()
|
const signal = this.#abortController.signal
|
||||||
|
signal.throwIfAborted()
|
||||||
|
|
||||||
console.log(chalk.blue.bold('MacroTool input'), input)
|
console.log(chalk.blue.bold('MacroTool input'), input)
|
||||||
const action = input.action
|
const action = input.action
|
||||||
@@ -414,24 +415,9 @@ export class PageAgentCore extends EventTarget {
|
|||||||
|
|
||||||
const startTime = Date.now()
|
const startTime = Date.now()
|
||||||
|
|
||||||
// Run the tool with `this` = agent and the abort signal exposed.
|
const result = await tool.execute.bind(this)(toolInput, { signal })
|
||||||
// The deadline warning surfaces tools that ignore the signal
|
// Enforce abort even if the tool ignored the signal and resolved normally.
|
||||||
// without unblocking the loop, keeping the bug visible.
|
signal.throwIfAborted()
|
||||||
const signal = this.#abortController.signal
|
|
||||||
const unsubscribe = onAbortTimeout(signal, 3000, () => {
|
|
||||||
console.warn(
|
|
||||||
`[PageAgent] Tool "${toolName}" did not respond to abort signal within 3s. ` +
|
|
||||||
`Tools MUST honor ctx.signal for proper cancellation. ` +
|
|
||||||
`See: https://page-agent.dev/docs/custom-tools#abort`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
let result: string
|
|
||||||
try {
|
|
||||||
result = await tool.execute.bind(this)(toolInput, { signal })
|
|
||||||
} finally {
|
|
||||||
unsubscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
const duration = Date.now() - startTime
|
const duration = Date.now() - startTime
|
||||||
console.log(chalk.green.bold(`Tool (${toolName}) executed for ${duration}ms`), result)
|
console.log(chalk.green.bold(`Tool (${toolName}) executed for ${duration}ms`), result)
|
||||||
|
|||||||
@@ -26,27 +26,6 @@ export async function waitFor(seconds: number, signal?: AbortSignal): Promise<vo
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Diagnostic deadline: fire `callback` if `signal` stays aborted for `ms`,
|
|
||||||
* surfacing async work that ignores abort. Returns an unsubscribe to call once
|
|
||||||
* the awaited work settles.
|
|
||||||
*/
|
|
||||||
export function onAbortTimeout(signal: AbortSignal, ms: number, callback: () => void): () => void {
|
|
||||||
if (signal.aborted) {
|
|
||||||
callback()
|
|
||||||
return () => {}
|
|
||||||
}
|
|
||||||
let timer: ReturnType<typeof setTimeout> | null = null
|
|
||||||
const onAbort = () => {
|
|
||||||
timer = setTimeout(callback, ms)
|
|
||||||
}
|
|
||||||
signal.addEventListener('abort', onAbort, { once: true })
|
|
||||||
return () => {
|
|
||||||
signal.removeEventListener('abort', onAbort)
|
|
||||||
if (timer) clearTimeout(timer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
export function truncate(text: string, maxLength: number): string {
|
export function truncate(text: string, maxLength: number): string {
|
||||||
|
|||||||
Reference in New Issue
Block a user