refactor(core): settle terminal status only after run cleanup completes
Defer the terminal statuschange to the outer finally via settleRun, closing the window where a listener could re-enter execute() during teardown. Also check abort at step start so aborts during stepDelay settle as `stopped`.
This commit is contained in:
@@ -195,7 +195,6 @@ 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.
|
||||||
* Once resolved, `status` is `stopped` and the agent can be reused.
|
|
||||||
*/
|
*/
|
||||||
async stop(): Promise<void> {
|
async stop(): Promise<void> {
|
||||||
if (this.#status !== 'running') return
|
if (this.#status !== 'running') return
|
||||||
@@ -224,8 +223,14 @@ export class PageAgentCore extends EventTarget {
|
|||||||
this.#setStatus('running')
|
this.#setStatus('running')
|
||||||
this.#emitHistoryChange()
|
this.#emitHistoryChange()
|
||||||
|
|
||||||
let resolveRunning!: () => void
|
let settleRun!: (status: AgentStatus) => void
|
||||||
this.#running = new Promise<void>((resolve) => (resolveRunning = resolve))
|
this.#running = new Promise<void>(
|
||||||
|
(resolve) =>
|
||||||
|
(settleRun = (status) => {
|
||||||
|
this.#setStatus(status)
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
// 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')
|
||||||
@@ -235,15 +240,16 @@ export class PageAgentCore extends EventTarget {
|
|||||||
const onBeforeTask = this.config.onBeforeTask
|
const onBeforeTask = this.config.onBeforeTask
|
||||||
const onAfterTask = this.config.onAfterTask
|
const onAfterTask = this.config.onAfterTask
|
||||||
|
|
||||||
|
let step = 0
|
||||||
|
let taskResult: ExecutionResult
|
||||||
|
let finalStatus: AgentStatus = 'error'
|
||||||
|
|
||||||
// graceful exit
|
// graceful exit
|
||||||
try {
|
try {
|
||||||
await this.pageController.showMask()
|
await this.pageController.showMask()
|
||||||
|
|
||||||
await onBeforeTask?.(this)
|
await onBeforeTask?.(this)
|
||||||
|
|
||||||
let step = 0
|
|
||||||
let taskResult: ExecutionResult
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
await onBeforeStep?.(this, step)
|
await onBeforeStep?.(this, step)
|
||||||
|
|
||||||
@@ -251,6 +257,10 @@ export class PageAgentCore extends EventTarget {
|
|||||||
try {
|
try {
|
||||||
console.group(`step: ${step}`)
|
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.
|
||||||
|
this.#abortController.signal.throwIfAborted()
|
||||||
|
|
||||||
// observe
|
// observe
|
||||||
|
|
||||||
console.log(chalk.blue.bold('👀 Observing...'))
|
console.log(chalk.blue.bold('👀 Observing...'))
|
||||||
@@ -310,7 +320,7 @@ export class PageAgentCore extends EventTarget {
|
|||||||
console.log(chalk.green.bold('Task completed'), success, data)
|
console.log(chalk.green.bold('Task completed'), success, data)
|
||||||
taskResult = { success, data, history: this.history }
|
taskResult = { success, data, history: this.history }
|
||||||
this.#lastResult = taskResult
|
this.#lastResult = taskResult
|
||||||
this.#setStatus('completed')
|
finalStatus = 'completed'
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
@@ -323,7 +333,7 @@ export class PageAgentCore extends EventTarget {
|
|||||||
this.#emitHistoryChange({ type: 'error', message: message, rawResponse: error })
|
this.#emitHistoryChange({ type: 'error', message: message, rawResponse: error })
|
||||||
taskResult = { success: false, data: message, history: this.history }
|
taskResult = { success: false, data: message, history: this.history }
|
||||||
this.#lastResult = taskResult
|
this.#lastResult = taskResult
|
||||||
this.#setStatus(isAbortError ? 'stopped' : 'error')
|
finalStatus = isAbortError ? 'stopped' : 'error'
|
||||||
break
|
break
|
||||||
} finally {
|
} finally {
|
||||||
// finally block runs before the break above.
|
// finally block runs before the break above.
|
||||||
@@ -343,7 +353,7 @@ export class PageAgentCore extends EventTarget {
|
|||||||
this.#emitHistoryChange({ type: 'error', message: message })
|
this.#emitHistoryChange({ type: 'error', message: message })
|
||||||
taskResult = { success: false, data: message, history: this.history }
|
taskResult = { success: false, data: message, history: this.history }
|
||||||
this.#lastResult = taskResult
|
this.#lastResult = taskResult
|
||||||
this.#setStatus('error')
|
finalStatus = 'error'
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,13 +365,13 @@ export class PageAgentCore extends EventTarget {
|
|||||||
return taskResult
|
return taskResult
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.#emitActivity({ type: 'error', message: String(error) })
|
this.#emitActivity({ type: 'error', message: String(error) })
|
||||||
this.#setStatus('error')
|
finalStatus = 'error'
|
||||||
throw error
|
throw error
|
||||||
} finally {
|
} finally {
|
||||||
this.pageController.cleanUpHighlights()
|
this.pageController.cleanUpHighlights()
|
||||||
this.pageController.hideMask()
|
this.pageController.hideMask()
|
||||||
this.#abortController.abort()
|
this.#abortController.abort()
|
||||||
resolveRunning()
|
settleRun(finalStatus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user