From 5f162b1a1c7bb092fc73f5dc0f7db7b83abfeac7 Mon Sep 17 00:00:00 2001 From: Simon <10131203+gaomeng1900@users.noreply.github.com> Date: Sat, 17 Jan 2026 23:30:12 +0800 Subject: [PATCH] chore: rm `event bus`; separate agent types --- .vscode/settings.json | 2 + packages/page-agent/src/PageAgent.ts | 120 ++---------------------- packages/page-agent/src/config/index.ts | 9 +- packages/page-agent/src/types.ts | 109 +++++++++++++++++++++ packages/page-agent/src/utils/bus.ts | 98 ------------------- 5 files changed, 128 insertions(+), 210 deletions(-) create mode 100644 packages/page-agent/src/types.ts delete mode 100644 packages/page-agent/src/utils/bus.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 4e03833..db6a62e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,7 @@ "editor.fontLigatures": true, "cSpell.words": [ "deepseek", + "historychange", "HITL", "innerhtml", "llms", @@ -10,6 +11,7 @@ "qwen", "retryable", "shadcn", + "statuschange", "wouter" ], "markdownlint.config": { diff --git a/packages/page-agent/src/PageAgent.ts b/packages/page-agent/src/PageAgent.ts index c808d31..ef347d1 100644 --- a/packages/page-agent/src/PageAgent.ts +++ b/packages/page-agent/src/PageAgent.ts @@ -11,122 +11,22 @@ import type { PageAgentConfig } from './config' import { MAX_STEPS } from './config/constants' import SYSTEM_PROMPT from './prompts/system_prompt.md?raw' import { tools } from './tools' +import { + AgentActivity, + AgentReflection, + AgentStatus, + AgentStep, + ExecutionResult, + HistoricalEvent, + MacroToolInput, + MacroToolResult, +} from './types' import { normalizeResponse, trimLines, uid } from './utils' 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 { - action: Record -} - -/** - * MacroTool output structure - */ -export interface MacroToolResult { - input: MacroToolInput - output: string -} - export type { PageAgentConfig } export { tool, type PageAgentTool } from './tools' -/** - * A single agent step with reflection and action - */ -export interface AgentStep { - type: 'step' - reflection: Partial - 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 { config: PageAgentConfig id = uid() diff --git a/packages/page-agent/src/config/index.ts b/packages/page-agent/src/config/index.ts index 6141443..54e3b42 100644 --- a/packages/page-agent/src/config/index.ts +++ b/packages/page-agent/src/config/index.ts @@ -1,8 +1,9 @@ import type { LLMConfig } from '@page-agent/llms' 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 { ExecutionResult, HistoricalEvent } from '../types' export type { LLMConfig } @@ -67,7 +68,11 @@ export interface AgentConfig { // @todo: remove `this` binding, pass agent as explicit parameter instead onBeforeStep?: (this: PageAgent, stepCnt: number) => Promise | void - onAfterStep?: (this: PageAgent, stepCnt: number, history: HistoryEvent[]) => Promise | void + onAfterStep?: ( + this: PageAgent, + stepCnt: number, + history: HistoricalEvent[] + ) => Promise | void onBeforeTask?: (this: PageAgent) => Promise | void onAfterTask?: (this: PageAgent, result: ExecutionResult) => Promise | void diff --git a/packages/page-agent/src/types.ts b/packages/page-agent/src/types.ts new file mode 100644 index 0000000..0503b0f --- /dev/null +++ b/packages/page-agent/src/types.ts @@ -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 { + action: Record +} + +/** + * 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 + 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[] +} diff --git a/packages/page-agent/src/utils/bus.ts b/packages/page-agent/src/utils/bus.ts deleted file mode 100644 index 581d2b4..0000000 --- a/packages/page-agent/src/utils/bus.ts +++ /dev/null @@ -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 = - PageAgentEventMap[T]['params'] extends undefined - ? () => void - : (params: PageAgentEventMap[T]['params']) => void - -/** - * Async event handler type definitions - */ -export type AsyncEventHandler = - PageAgentEventMap[T]['params'] extends undefined - ? () => Promise - : (params: PageAgentEventMap[T]['params']) => Promise - -/** - * 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(event: T, handler: EventHandler): 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(event: T, handler: EventHandler): 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( - 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() - -/** - * 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 }