fix(core): harden run settlement edge cases from review

- install #running before the `running` statuschange fires, so a listener
  calling stop() immediately awaits the current run
- await async mask/highlight cleanup before settling: once settled, the
  agent must be safely reusable
- make the inter-step delay abortable so stop() settles promptly; abort
  during the delay is classified as `stopped`
This commit is contained in:
Simon
2026-06-11 19:15:27 +08:00
parent 255e8fc861
commit 0438bf6265

View File

@@ -220,13 +220,14 @@ export class PageAgentCore extends EventTarget {
this.#observations = [] this.#observations = []
this.#states = { totalWaitTime: 0, lastURL: '', browserState: null } this.#states = { totalWaitTime: 0, lastURL: '', browserState: null }
this.#abortController = new AbortController() this.#abortController = new AbortController()
const signal = this.#abortController.signal
this.#setStatus('running')
this.#emitHistoryChange()
let resolveRunning!: () => void let resolveRunning!: () => void
this.#running = new Promise<void>((r) => (resolveRunning = r)) this.#running = new Promise<void>((r) => (resolveRunning = r))
this.#setStatus('running')
this.#emitHistoryChange()
// Disable ask_user tool if onAskUser is not set // Disable ask_user tool if onAskUser is not set
if (!this.onAskUser) this.tools.delete('ask_user') if (!this.onAskUser) this.tools.delete('ask_user')
@@ -234,6 +235,8 @@ export class PageAgentCore extends EventTarget {
const onAfterStep = this.config.onAfterStep const onAfterStep = this.config.onAfterStep
const onBeforeTask = this.config.onBeforeTask const onBeforeTask = this.config.onBeforeTask
const onAfterTask = this.config.onAfterTask const onAfterTask = this.config.onAfterTask
const stepDelay = this.config.stepDelay ?? 0.4
const maxSteps = this.config.maxSteps
let step = 0 let step = 0
let taskResult: ExecutionResult let taskResult: ExecutionResult
@@ -252,8 +255,9 @@ export class PageAgentCore extends EventTarget {
try { try {
console.group(`step: ${step}`) console.group(`step: ${step}`)
// inside the try: abort between steps must settle as `stopped` if (step > 0) await waitFor(stepDelay, signal)
this.#abortController.signal.throwIfAborted()
signal.throwIfAborted()
// observe // observe
@@ -276,7 +280,7 @@ export class PageAgentCore extends EventTarget {
console.log(chalk.blue.bold('🧠 Thinking...')) console.log(chalk.blue.bold('🧠 Thinking...'))
this.#emitActivity({ type: 'thinking' }) this.#emitActivity({ type: 'thinking' })
const result = await this.#llm.invoke(messages, macroTool, this.#abortController.signal, { const result = await this.#llm.invoke(messages, macroTool, signal, {
toolChoiceName: 'AgentOutput', toolChoiceName: 'AgentOutput',
normalizeResponse: (res) => normalizeResponse(res, this.tools), normalizeResponse: (res) => normalizeResponse(res, this.tools),
}) })
@@ -340,7 +344,7 @@ export class PageAgentCore extends EventTarget {
} }
step++ step++
if (step > this.config.maxSteps) { if (step > maxSteps) {
const message = 'Step count exceeded maximum limit' const message = 'Step count exceeded maximum limit'
console.error(message) console.error(message)
this.#emitActivity({ type: 'error', message: message }) this.#emitActivity({ type: 'error', message: message })
@@ -350,8 +354,6 @@ export class PageAgentCore extends EventTarget {
finalStatus = 'error' finalStatus = 'error'
break break
} }
await waitFor(this.config.stepDelay ?? 0.4)
} // while } // while
await onAfterTask?.(this, taskResult) await onAfterTask?.(this, taskResult)
@@ -362,8 +364,8 @@ export class PageAgentCore extends EventTarget {
finalStatus = 'error' finalStatus = 'error'
throw error throw error
} finally { } finally {
suppress(() => this.pageController.cleanUpHighlights()) await suppress(() => this.pageController.cleanUpHighlights())
suppress(() => this.pageController.hideMask()) await suppress(() => this.pageController.hideMask())
this.#abortController.abort() this.#abortController.abort()
resolveRunning() resolveRunning()
this.#setStatus(finalStatus) this.#setStatus(finalStatus)