feat: add lifecycle hooks for task and step events

This commit is contained in:
Simon
2025-10-21 20:05:39 +08:00
parent 5716966b6d
commit 71d1cd4df3
3 changed files with 37 additions and 10 deletions

View File

@@ -17,8 +17,9 @@ The development progress and future plans for PageAgent.
- [x] **Free evaluation plan?** - [x] **Free evaluation plan?**
- [x] **Custom actions and HITL** - [x] **Custom actions and HITL**
- [ ] **Hooks and Events** - [ ] **Hooks and Events**
- [x] **Step lifecycle** - [x] **lifecycle**
- [ ] **Hijacking `page_open` event** - [ ] **Pause and intervene**
- [ ] **Hijacking `page_open/page_change/page_unload` event**
- [ ] **Custom knowledge base and instructions** - [ ] **Custom knowledge base and instructions**
- [ ] **Black/white-list safeguard** - [ ] **Black/white-list safeguard**
- [ ] **Data-masking** - [ ] **Data-masking**

View File

@@ -134,6 +134,13 @@ export class PageAgent extends EventTarget {
if (!task) throw new Error('Task is required') if (!task) throw new Error('Task is required')
this.task = task this.task = task
const onBeforeStep = this.config.onBeforeStep || (() => void 0)
const onAfterStep = this.config.onAfterStep || (() => void 0)
const onBeforeTask = this.config.onBeforeTask || (() => void 0)
const onAfterTask = this.config.onAfterTask || (() => void 0)
await onBeforeTask.call(this, task)
// Show mask and panel // Show mask and panel
this.mask.show() this.mask.show()
@@ -142,7 +149,7 @@ export class PageAgent extends EventTarget {
this.bus.emit('panel:update', { this.bus.emit('panel:update', {
type: 'input', type: 'input',
displayText: task, displayText: this.task,
}) })
if (this.#abortController) { if (this.#abortController) {
@@ -156,7 +163,7 @@ export class PageAgent extends EventTarget {
let step = 0 let step = 0
while (true) { while (true) {
if (this.config.onBeforeStep) await this.config.onBeforeStep.call(this, step) await onBeforeStep.call(this, step)
console.group(`step: ${step + 1}`) console.group(`step: ${step + 1}`)
@@ -211,37 +218,43 @@ export class PageAgent extends EventTarget {
console.log(chalk.green('Step finished:'), actionName) console.log(chalk.green('Step finished:'), actionName)
console.groupEnd() console.groupEnd()
if (this.config.onAfterStep) await this.config.onAfterStep.call(this, step, this.history) await onAfterStep.call(this, step, this.history)
step++ step++
if (step > MAX_STEPS) { if (step > MAX_STEPS) {
this.#onDone('Step count exceeded maximum limit', false) this.#onDone('Step count exceeded maximum limit', false)
return { const result: ExecutionResult = {
success: false, success: false,
data: 'Step count exceeded maximum limit', data: 'Step count exceeded maximum limit',
history: this.history, history: this.history,
} }
await onAfterTask.call(this, task, result)
return result
} }
if (actionName === 'done') { if (actionName === 'done') {
const success = action.input?.success ?? false const success = action.input?.success ?? false
const text = action.input?.text || 'no text provided' const text = action.input?.text || 'no text provided'
console.log(chalk.green.bold('Task completed'), success, text) console.log(chalk.green.bold('Task completed'), success, text)
this.#onDone(text, success) this.#onDone(text, success)
return { const result: ExecutionResult = {
success, success,
data: text, data: text,
history: this.history, history: this.history,
} }
await onAfterTask.call(this, task, result)
return result
} }
} }
} catch (error: unknown) { } catch (error: unknown) {
console.error('Task failed', error) console.error('Task failed', error)
this.#onDone(String(error), false) this.#onDone(String(error), false)
return { const result: ExecutionResult = {
success: false, success: false,
data: String(error), data: String(error),
history: this.history, history: this.history,
} }
await onAfterTask.call(this, task, result)
return result
} }
} }

View File

@@ -1,4 +1,4 @@
import type { AgentHistory, PageAgent } from '@/PageAgent' import type { AgentHistory, ExecutionResult, PageAgent } from '@/PageAgent'
import type { DomConfig } from '@/dom' import type { DomConfig } from '@/dom'
import type { SupportedLanguage } from '@/i18n' import type { SupportedLanguage } from '@/i18n'
import type { PageAgentTool } from '@/tools' import type { PageAgentTool } from '@/tools'
@@ -32,6 +32,7 @@ export interface UIConfig {
* @see [tools](../tools/index.ts) * @see [tools](../tools/index.ts)
* *
* @example * @example
* // override internal tool
* import { tool } from 'page-agent' * import { tool } from 'page-agent'
* const customTools = { * const customTools = {
* ask_user: tool({ * ask_user: tool({
@@ -46,13 +47,25 @@ export interface UIConfig {
* }, * },
* }) * })
* } * }
*
* @example
* // remove internal tool
* const customTools = {
* ask_user: null // never ask user questions
* }
*/ */
customTools?: Record<string, PageAgentTool | null> customTools?: Record<string, PageAgentTool | null>
// hooks // lifecycle hooks
onBeforeStep?: (this: PageAgent, stepCnt: number) => Promise<void> | void onBeforeStep?: (this: PageAgent, stepCnt: number) => Promise<void> | void
onAfterStep?: (this: PageAgent, stepCnt: number, history: AgentHistory[]) => Promise<void> | void onAfterStep?: (this: PageAgent, stepCnt: number, history: AgentHistory[]) => Promise<void> | void
onBeforeTask?: (this: PageAgent, task: string) => Promise<void> | void
onAfterTask?: (this: PageAgent, task: string, result: ExecutionResult) => Promise<void> | void
// page behavior hooks
onPageUnload?: (this: PageAgent) => Promise<void> | void
} }
export type PageAgentConfig = LLMConfig & DomConfig & UIConfig export type PageAgentConfig = LLMConfig & DomConfig & UIConfig