chore: rm event bus; separate agent types
This commit is contained in:
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -2,6 +2,7 @@
|
|||||||
"editor.fontLigatures": true,
|
"editor.fontLigatures": true,
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"deepseek",
|
"deepseek",
|
||||||
|
"historychange",
|
||||||
"HITL",
|
"HITL",
|
||||||
"innerhtml",
|
"innerhtml",
|
||||||
"llms",
|
"llms",
|
||||||
@@ -10,6 +11,7 @@
|
|||||||
"qwen",
|
"qwen",
|
||||||
"retryable",
|
"retryable",
|
||||||
"shadcn",
|
"shadcn",
|
||||||
|
"statuschange",
|
||||||
"wouter"
|
"wouter"
|
||||||
],
|
],
|
||||||
"markdownlint.config": {
|
"markdownlint.config": {
|
||||||
|
|||||||
@@ -11,122 +11,22 @@ import type { PageAgentConfig } from './config'
|
|||||||
import { MAX_STEPS } from './config/constants'
|
import { MAX_STEPS } from './config/constants'
|
||||||
import SYSTEM_PROMPT from './prompts/system_prompt.md?raw'
|
import SYSTEM_PROMPT from './prompts/system_prompt.md?raw'
|
||||||
import { tools } from './tools'
|
import { tools } from './tools'
|
||||||
|
import {
|
||||||
|
AgentActivity,
|
||||||
|
AgentReflection,
|
||||||
|
AgentStatus,
|
||||||
|
AgentStep,
|
||||||
|
ExecutionResult,
|
||||||
|
HistoricalEvent,
|
||||||
|
MacroToolInput,
|
||||||
|
MacroToolResult,
|
||||||
|
} from './types'
|
||||||
import { normalizeResponse, trimLines, uid } from './utils'
|
import { normalizeResponse, trimLines, uid } from './utils'
|
||||||
import { assert } from './utils/assert'
|
import { assert } from './utils/assert'
|
||||||
|
|
||||||
/**
|
|
||||||
* Agent reflection state - the reflection-before-action model
|
|
||||||
*
|
|
||||||
* Every tool call must first reflect on:
|
|
||||||
* - evaluation_previous_goal: How well did the previous action achieve its goal?
|
|
||||||
* - memory: Key information to remember for future steps
|
|
||||||
* - next_goal: What should be accomplished in the next action?
|
|
||||||
*/
|
|
||||||
export interface AgentReflection {
|
|
||||||
evaluation_previous_goal: string
|
|
||||||
memory: string
|
|
||||||
next_goal: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MacroTool input structure
|
|
||||||
*
|
|
||||||
* This is the core abstraction that enforces the "reflection-before-action" mental model.
|
|
||||||
* Before executing any action, the LLM must output its reasoning state.
|
|
||||||
*/
|
|
||||||
export interface MacroToolInput extends Partial<AgentReflection> {
|
|
||||||
action: Record<string, any>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MacroTool output structure
|
|
||||||
*/
|
|
||||||
export interface MacroToolResult {
|
|
||||||
input: MacroToolInput
|
|
||||||
output: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type { PageAgentConfig }
|
export type { PageAgentConfig }
|
||||||
export { tool, type PageAgentTool } from './tools'
|
export { tool, type PageAgentTool } from './tools'
|
||||||
|
|
||||||
/**
|
|
||||||
* A single agent step with reflection and action
|
|
||||||
*/
|
|
||||||
export interface AgentStep {
|
|
||||||
type: 'step'
|
|
||||||
reflection: Partial<AgentReflection>
|
|
||||||
action: {
|
|
||||||
name: string
|
|
||||||
input: any
|
|
||||||
output: string
|
|
||||||
}
|
|
||||||
usage: {
|
|
||||||
promptTokens: number
|
|
||||||
completionTokens: number
|
|
||||||
totalTokens: number
|
|
||||||
cachedTokens?: number
|
|
||||||
reasoningTokens?: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Persistent observation event (stays in memory)
|
|
||||||
*/
|
|
||||||
export interface ObservationEvent {
|
|
||||||
type: 'observation'
|
|
||||||
content: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User takeover event
|
|
||||||
*/
|
|
||||||
export interface UserTakeoverEvent {
|
|
||||||
type: 'user_takeover'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error event (retry or error from LLM)
|
|
||||||
*/
|
|
||||||
export interface ErrorEvent {
|
|
||||||
type: 'error'
|
|
||||||
errorType: 'retry' | 'error'
|
|
||||||
message: string
|
|
||||||
attempt?: number
|
|
||||||
maxAttempts?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Union type for all history events
|
|
||||||
*/
|
|
||||||
export type HistoricalEvent = AgentStep | ObservationEvent | UserTakeoverEvent | ErrorEvent
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Agent execution status
|
|
||||||
*/
|
|
||||||
export type AgentStatus = 'idle' | 'running' | 'completed' | 'error'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Agent activity - transient state for immediate UI feedback.
|
|
||||||
*
|
|
||||||
* Unlike historical events (which are persisted), activities are ephemeral
|
|
||||||
* and represent "what the agent is doing right now". UI components should
|
|
||||||
* listen to 'activity' events to show real-time feedback.
|
|
||||||
*
|
|
||||||
* Note: There is no 'idle' activity - absence of activity events means idle.
|
|
||||||
*/
|
|
||||||
export type AgentActivity =
|
|
||||||
| { type: 'thinking' }
|
|
||||||
| { type: 'executing'; tool: string; input: unknown }
|
|
||||||
| { type: 'executed'; tool: string; input: unknown; output: string; duration: number }
|
|
||||||
| { type: 'retrying'; attempt: number; maxAttempts: number }
|
|
||||||
| { type: 'error'; message: string }
|
|
||||||
|
|
||||||
export interface ExecutionResult {
|
|
||||||
success: boolean
|
|
||||||
data: string
|
|
||||||
history: HistoricalEvent[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PageAgent extends EventTarget {
|
export class PageAgent extends EventTarget {
|
||||||
config: PageAgentConfig
|
config: PageAgentConfig
|
||||||
id = uid()
|
id = uid()
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import type { LLMConfig } from '@page-agent/llms'
|
import type { LLMConfig } from '@page-agent/llms'
|
||||||
import type { PageControllerConfig } from '@page-agent/page-controller'
|
import type { PageControllerConfig } from '@page-agent/page-controller'
|
||||||
|
|
||||||
import type { ExecutionResult, HistoryEvent, PageAgent } from '../PageAgent'
|
import type { PageAgent } from '../PageAgent'
|
||||||
import type { PageAgentTool } from '../tools'
|
import type { PageAgentTool } from '../tools'
|
||||||
|
import type { ExecutionResult, HistoricalEvent } from '../types'
|
||||||
|
|
||||||
export type { LLMConfig }
|
export type { LLMConfig }
|
||||||
|
|
||||||
@@ -67,7 +68,11 @@ export interface AgentConfig {
|
|||||||
// @todo: remove `this` binding, pass agent as explicit parameter instead
|
// @todo: remove `this` binding, pass agent as explicit parameter instead
|
||||||
|
|
||||||
onBeforeStep?: (this: PageAgent, stepCnt: number) => Promise<void> | void
|
onBeforeStep?: (this: PageAgent, stepCnt: number) => Promise<void> | void
|
||||||
onAfterStep?: (this: PageAgent, stepCnt: number, history: HistoryEvent[]) => Promise<void> | void
|
onAfterStep?: (
|
||||||
|
this: PageAgent,
|
||||||
|
stepCnt: number,
|
||||||
|
history: HistoricalEvent[]
|
||||||
|
) => Promise<void> | void
|
||||||
onBeforeTask?: (this: PageAgent) => Promise<void> | void
|
onBeforeTask?: (this: PageAgent) => Promise<void> | void
|
||||||
onAfterTask?: (this: PageAgent, result: ExecutionResult) => Promise<void> | void
|
onAfterTask?: (this: PageAgent, result: ExecutionResult) => Promise<void> | void
|
||||||
|
|
||||||
|
|||||||
109
packages/page-agent/src/types.ts
Normal file
109
packages/page-agent/src/types.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/**
|
||||||
|
* Agent reflection state - the reflection-before-action model
|
||||||
|
*
|
||||||
|
* Every tool call must first reflect on:
|
||||||
|
* - evaluation_previous_goal: How well did the previous action achieve its goal?
|
||||||
|
* - memory: Key information to remember for future steps
|
||||||
|
* - next_goal: What should be accomplished in the next action?
|
||||||
|
*/
|
||||||
|
export interface AgentReflection {
|
||||||
|
evaluation_previous_goal: string
|
||||||
|
memory: string
|
||||||
|
next_goal: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MacroTool input structure
|
||||||
|
*
|
||||||
|
* This is the core abstraction that enforces the "reflection-before-action" mental model.
|
||||||
|
* Before executing any action, the LLM must output its reasoning state.
|
||||||
|
*/
|
||||||
|
export interface MacroToolInput extends Partial<AgentReflection> {
|
||||||
|
action: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MacroTool output structure
|
||||||
|
*/
|
||||||
|
export interface MacroToolResult {
|
||||||
|
input: MacroToolInput
|
||||||
|
output: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single agent step with reflection and action
|
||||||
|
*/
|
||||||
|
export interface AgentStep {
|
||||||
|
type: 'step'
|
||||||
|
reflection: Partial<AgentReflection>
|
||||||
|
action: {
|
||||||
|
name: string
|
||||||
|
input: any
|
||||||
|
output: string
|
||||||
|
}
|
||||||
|
usage: {
|
||||||
|
promptTokens: number
|
||||||
|
completionTokens: number
|
||||||
|
totalTokens: number
|
||||||
|
cachedTokens?: number
|
||||||
|
reasoningTokens?: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persistent observation event (stays in memory)
|
||||||
|
*/
|
||||||
|
export interface ObservationEvent {
|
||||||
|
type: 'observation'
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User takeover event
|
||||||
|
*/
|
||||||
|
export interface UserTakeoverEvent {
|
||||||
|
type: 'user_takeover'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error event (retry or error from LLM)
|
||||||
|
*/
|
||||||
|
export interface ErrorEvent {
|
||||||
|
type: 'error'
|
||||||
|
errorType: 'retry' | 'error'
|
||||||
|
message: string
|
||||||
|
attempt?: number
|
||||||
|
maxAttempts?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Union type for all history events
|
||||||
|
*/
|
||||||
|
export type HistoricalEvent = AgentStep | ObservationEvent | UserTakeoverEvent | ErrorEvent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent execution status
|
||||||
|
*/
|
||||||
|
export type AgentStatus = 'idle' | 'running' | 'completed' | 'error'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent activity - transient state for immediate UI feedback.
|
||||||
|
*
|
||||||
|
* Unlike historical events (which are persisted), activities are ephemeral
|
||||||
|
* and represent "what the agent is doing right now". UI components should
|
||||||
|
* listen to 'activity' events to show real-time feedback.
|
||||||
|
*
|
||||||
|
* Note: There is no 'idle' activity - absence of activity events means idle.
|
||||||
|
*/
|
||||||
|
export type AgentActivity =
|
||||||
|
| { type: 'thinking' }
|
||||||
|
| { type: 'executing'; tool: string; input: unknown }
|
||||||
|
| { type: 'executed'; tool: string; input: unknown; output: string; duration: number }
|
||||||
|
| { type: 'retrying'; attempt: number; maxAttempts: number }
|
||||||
|
| { type: 'error'; message: string }
|
||||||
|
|
||||||
|
export interface ExecutionResult {
|
||||||
|
success: boolean
|
||||||
|
data: string
|
||||||
|
history: HistoricalEvent[]
|
||||||
|
}
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
/**
|
|
||||||
* Event mapping definitions
|
|
||||||
* @note Event bus callbacks must be repeatable without errors
|
|
||||||
*/
|
|
||||||
export interface PageAgentEventMap {
|
|
||||||
// PageAgent status events
|
|
||||||
// 'agent:execute': { params: { task: string } }
|
|
||||||
// 'agent:done': { params: { text: string; success: boolean } }
|
|
||||||
// 'agent:disposed': { params: undefined }
|
|
||||||
// 'agent:error': { params: { error: string | Error } }
|
|
||||||
|
|
||||||
// Task status change events
|
|
||||||
'task:start': { params: { task: string } }
|
|
||||||
// 'task:complete': { params: { text: string; success: boolean } }
|
|
||||||
// 'task:error': { params: { error: string | Error } }
|
|
||||||
|
|
||||||
// Index signature for dynamic event names
|
|
||||||
// [key: string]: { params: any }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event handler type definitions
|
|
||||||
*/
|
|
||||||
export type EventHandler<T extends keyof PageAgentEventMap> =
|
|
||||||
PageAgentEventMap[T]['params'] extends undefined
|
|
||||||
? () => void
|
|
||||||
: (params: PageAgentEventMap[T]['params']) => void
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Async event handler type definitions
|
|
||||||
*/
|
|
||||||
export type AsyncEventHandler<T extends keyof PageAgentEventMap> =
|
|
||||||
PageAgentEventMap[T]['params'] extends undefined
|
|
||||||
? () => Promise<void>
|
|
||||||
: (params: PageAgentEventMap[T]['params']) => Promise<void>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type-safe event bus
|
|
||||||
* @note Mainly used to decouple logic and UI
|
|
||||||
* @note All modules of a PageAgent instance share the same EventBus instance for communication
|
|
||||||
* @note Use with caution if delivery guarantee is needed for logic communication
|
|
||||||
* @note `on` `once` `emit` methods handle built-in events with type protection, use `addEventListener` for other events
|
|
||||||
*/
|
|
||||||
class EventBus extends EventTarget {
|
|
||||||
/**
|
|
||||||
* Listen to built-in events
|
|
||||||
*/
|
|
||||||
on<T extends keyof PageAgentEventMap>(event: T, handler: EventHandler<T>): void {
|
|
||||||
const wrappedHandler = (e: Event) => {
|
|
||||||
const customEvent = e as CustomEvent
|
|
||||||
const params = customEvent.detail?.[0]
|
|
||||||
return handler(params)
|
|
||||||
}
|
|
||||||
this.addEventListener(event, wrappedHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen to built-in events (one-time)
|
|
||||||
*/
|
|
||||||
once<T extends keyof PageAgentEventMap>(event: T, handler: EventHandler<T>): void {
|
|
||||||
const wrappedHandler = (e: Event) => {
|
|
||||||
const customEvent = e as CustomEvent
|
|
||||||
const params = customEvent.detail?.[0]
|
|
||||||
return handler(params)
|
|
||||||
}
|
|
||||||
this.addEventListener(event, wrappedHandler, { once: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emit built-in events
|
|
||||||
*/
|
|
||||||
emit<T extends keyof PageAgentEventMap>(
|
|
||||||
event: T,
|
|
||||||
...args: PageAgentEventMap[T]['params'] extends undefined
|
|
||||||
? []
|
|
||||||
: [PageAgentEventMap[T]['params']]
|
|
||||||
): void {
|
|
||||||
const customEvent = new CustomEvent(event, { detail: args })
|
|
||||||
this.dispatchEvent(customEvent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const buses = new Map<string, EventBus>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the event bus for a given channel
|
|
||||||
*/
|
|
||||||
export function getEventBus(channel: string) {
|
|
||||||
if (buses.has(channel)) {
|
|
||||||
return buses.get(channel)!
|
|
||||||
}
|
|
||||||
const bus = new EventBus()
|
|
||||||
buses.set(channel, bus)
|
|
||||||
return bus
|
|
||||||
}
|
|
||||||
|
|
||||||
export type { EventBus }
|
|
||||||
Reference in New Issue
Block a user