From 4690aefec5944dc1eef250e0e9639fdb58c22e2f Mon Sep 17 00:00:00 2001 From: Simon <10131203+gaomeng1900@users.noreply.github.com> Date: Thu, 11 Jun 2026 17:21:11 +0800 Subject: [PATCH] refactor(core): decouple run settling from terminal status transition Resolve #running before the terminal statuschange so the settle signal can never be lost to re-entrant listeners. Hooks keep middleware semantics: a throwing hook fails the run; integrations that don't want this should suppress errors in their own hooks. Also make suppress() async-aware so rejected promises (e.g. showMask) are actually caught. --- packages/core/src/PageAgentCore.ts | 19 +++++++------------ packages/core/src/utils/index.ts | 4 ++-- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/core/src/PageAgentCore.ts b/packages/core/src/PageAgentCore.ts index a61eda3..1294140 100644 --- a/packages/core/src/PageAgentCore.ts +++ b/packages/core/src/PageAgentCore.ts @@ -194,7 +194,8 @@ export class PageAgentCore extends EventTarget { } /** - * Stop the current task and wait until the run has fully settled. + * Stop the current task and wait until the run has fully settled (including lifecycle hooks). + * @note never await .stop() in a lifecycle hook. */ async stop(): Promise { if (this.#status !== 'running') return @@ -223,14 +224,8 @@ export class PageAgentCore extends EventTarget { this.#setStatus('running') this.#emitHistoryChange() - let settleRun!: (status: AgentStatus) => void - this.#running = new Promise( - (resolve) => - (settleRun = (status) => { - this.#setStatus(status) - resolve() - }) - ) + let resolveRunning!: () => void + this.#running = new Promise((r) => (resolveRunning = r)) // Disable ask_user tool if onAskUser is not set if (!this.onAskUser) this.tools.delete('ask_user') @@ -257,8 +252,7 @@ export class PageAgentCore extends EventTarget { try { console.group(`step: ${step}`) - // Abort may have fired between steps (e.g. during stepDelay). - // Check inside the try so it is classified as `stopped`, and skip the observe phase. + // inside the try: abort between steps must settle as `stopped` this.#abortController.signal.throwIfAborted() // observe @@ -371,7 +365,8 @@ export class PageAgentCore extends EventTarget { suppress(() => this.pageController.cleanUpHighlights()) suppress(() => this.pageController.hideMask()) this.#abortController.abort() - settleRun(finalStatus) + resolveRunning() + this.#setStatus(finalStatus) } } diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index cecbaa9..fe74fb1 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -133,9 +133,9 @@ export function assert(condition: unknown, message?: string, silent?: boolean): /** * Suppress errors from a function. */ -export function suppress(fn: () => T): T | undefined { +export async function suppress(fn: () => T | Promise): Promise | undefined> { try { - return fn() + return await fn() } catch (error) { console.error(error) return undefined