feat: add lifecycle hooks
This commit is contained in:
@@ -17,9 +17,10 @@ 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] **lifecycle**
|
- [x] **lifecycle hooks**
|
||||||
- [ ] **Pause and intervene**
|
- [ ] **lifecycle events**
|
||||||
- [ ] **Hijacking `page_open/page_change/page_unload` event**
|
- [ ] **❗Pause and intervene**
|
||||||
|
- [ ] **❗Hijack `page_open/page_change/page_unload` behavior**
|
||||||
- [ ] **Custom knowledge base and instructions**
|
- [ ] **Custom knowledge base and instructions**
|
||||||
- [ ] **Black/white-list safeguard**
|
- [ ] **Black/white-list safeguard**
|
||||||
- [ ] **Data-masking**
|
- [ ] **Data-masking**
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export class PageAgent extends EventTarget {
|
|||||||
paused = false
|
paused = false
|
||||||
disposed = false
|
disposed = false
|
||||||
task = ''
|
task = ''
|
||||||
|
taskId = ''
|
||||||
|
|
||||||
#llm: LLM
|
#llm: LLM
|
||||||
#totalWaitTime = 0
|
#totalWaitTime = 0
|
||||||
@@ -125,6 +126,10 @@ export class PageAgent extends EventTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
patchReact(this)
|
patchReact(this)
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', (e) => {
|
||||||
|
if (!this.disposed) this.dispose('PAGE_UNLOADING')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -133,13 +138,14 @@ export class PageAgent extends EventTarget {
|
|||||||
async execute(task: string): Promise<ExecutionResult> {
|
async execute(task: string): Promise<ExecutionResult> {
|
||||||
if (!task) throw new Error('Task is required')
|
if (!task) throw new Error('Task is required')
|
||||||
this.task = task
|
this.task = task
|
||||||
|
this.taskId = uid()
|
||||||
|
|
||||||
const onBeforeStep = this.config.onBeforeStep || (() => void 0)
|
const onBeforeStep = this.config.onBeforeStep || (() => void 0)
|
||||||
const onAfterStep = this.config.onAfterStep || (() => void 0)
|
const onAfterStep = this.config.onAfterStep || (() => void 0)
|
||||||
const onBeforeTask = this.config.onBeforeTask || (() => void 0)
|
const onBeforeTask = this.config.onBeforeTask || (() => void 0)
|
||||||
const onAfterTask = this.config.onAfterTask || (() => void 0)
|
const onAfterTask = this.config.onAfterTask || (() => void 0)
|
||||||
|
|
||||||
await onBeforeTask.call(this, task)
|
await onBeforeTask.call(this)
|
||||||
|
|
||||||
// Show mask and panel
|
// Show mask and panel
|
||||||
this.mask.show()
|
this.mask.show()
|
||||||
@@ -228,7 +234,7 @@ export class PageAgent extends EventTarget {
|
|||||||
data: 'Step count exceeded maximum limit',
|
data: 'Step count exceeded maximum limit',
|
||||||
history: this.history,
|
history: this.history,
|
||||||
}
|
}
|
||||||
await onAfterTask.call(this, task, result)
|
await onAfterTask.call(this, result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
if (actionName === 'done') {
|
if (actionName === 'done') {
|
||||||
@@ -241,7 +247,7 @@ export class PageAgent extends EventTarget {
|
|||||||
data: text,
|
data: text,
|
||||||
history: this.history,
|
history: this.history,
|
||||||
}
|
}
|
||||||
await onAfterTask.call(this, task, result)
|
await onAfterTask.call(this, result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,7 +259,7 @@ export class PageAgent extends EventTarget {
|
|||||||
data: String(error),
|
data: String(error),
|
||||||
history: this.history,
|
history: this.history,
|
||||||
}
|
}
|
||||||
await onAfterTask.call(this, task, result)
|
await onAfterTask.call(this, result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,7 +275,7 @@ export class PageAgent extends EventTarget {
|
|||||||
*/
|
*/
|
||||||
#packMacroTool(): Tool<MacroToolInput, MacroToolResult> {
|
#packMacroTool(): Tool<MacroToolInput, MacroToolResult> {
|
||||||
const tools = this.tools
|
const tools = this.tools
|
||||||
// union version
|
|
||||||
const actionSchemas = Array.from(tools.entries()).map(([toolName, tool]) => {
|
const actionSchemas = Array.from(tools.entries()).map(([toolName, tool]) => {
|
||||||
return zod.object({
|
return zod.object({
|
||||||
[toolName]: tool.inputSchema,
|
[toolName]: tool.inputSchema,
|
||||||
@@ -289,8 +295,6 @@ export class PageAgent extends EventTarget {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// name: MACRO_TOOL_NAME,
|
|
||||||
// description: 'Execute agent action', // @todo remote
|
|
||||||
inputSchema: macroToolSchema as zod.ZodType<MacroToolInput>,
|
inputSchema: macroToolSchema as zod.ZodType<MacroToolInput>,
|
||||||
execute: async (input: MacroToolInput): Promise<MacroToolResult> => {
|
execute: async (input: MacroToolInput): Promise<MacroToolResult> => {
|
||||||
// abort
|
// abort
|
||||||
@@ -512,7 +516,7 @@ export class PageAgent extends EventTarget {
|
|||||||
this.dispatchEvent(new Event('afterUpdate'))
|
this.dispatchEvent(new Event('afterUpdate'))
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose(reason?: string) {
|
||||||
console.log('Disposing PageAgent...')
|
console.log('Disposing PageAgent...')
|
||||||
this.disposed = true
|
this.disposed = true
|
||||||
dom.cleanUpHighlights()
|
dom.cleanUpHighlights()
|
||||||
@@ -522,6 +526,8 @@ export class PageAgent extends EventTarget {
|
|||||||
this.panel.dispose()
|
this.panel.dispose()
|
||||||
this.mask.dispose()
|
this.mask.dispose()
|
||||||
this.history = []
|
this.history = []
|
||||||
this.#abortController.abort('PageAgent disposed')
|
this.#abortController.abort(reason ?? 'PageAgent disposed')
|
||||||
|
|
||||||
|
this.config.onDispose?.call(this, reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,15 +57,34 @@ export interface UIConfig {
|
|||||||
customTools?: Record<string, PageAgentTool | null>
|
customTools?: Record<string, PageAgentTool | null>
|
||||||
|
|
||||||
// lifecycle hooks
|
// lifecycle hooks
|
||||||
|
// @todo: use event instead of 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
|
onBeforeTask?: (this: PageAgent) => Promise<void> | void
|
||||||
onAfterTask?: (this: PageAgent, task: string, result: ExecutionResult) => Promise<void> | void
|
onAfterTask?: (this: PageAgent, result: ExecutionResult) => Promise<void> | void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @note this hook can block the disposal process
|
||||||
|
* @note when dispose caused by page unload, `reason` will be 'PAGE_UNLOADING'. this method CANNOT block the unload process. async operations may be cut.
|
||||||
|
*/
|
||||||
|
onDispose?: (this: PageAgent, reason?: string) => void
|
||||||
|
|
||||||
// page behavior hooks
|
// page behavior hooks
|
||||||
|
|
||||||
onPageUnload?: (this: PageAgent) => Promise<void> | void
|
/**
|
||||||
|
* TODO: @unimplemented
|
||||||
|
* hook when action causes a new page to be opened
|
||||||
|
* @note PageAgent will try to detect new pages and decide if it's caused by an action. But not very reliable.
|
||||||
|
*/
|
||||||
|
onNewPageOpen?: (this: PageAgent, url: string) => Promise<void> | void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: @unimplemented
|
||||||
|
* try to navigate to a new page instead of opening a new tab/window.
|
||||||
|
* @note will unload the current page when a action tries to open a new page. so that things keep in the same tab/window.
|
||||||
|
*/
|
||||||
|
experimentalPreventNewPage?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PageAgentConfig = LLMConfig & DomConfig & UIConfig
|
export type PageAgentConfig = LLMConfig & DomConfig & UIConfig
|
||||||
|
|||||||
Reference in New Issue
Block a user