Compare commits

...

7 Commits

Author SHA1 Message Date
Simon af36f43104 chore(version): bump version to 0.2.5 2026-01-17 17:47:50 +08:00
Simon b5f260207c chore: wording 2026-01-17 17:47:04 +08:00
Simon 9075204c29 Merge pull request #118 from alibaba/feat/make-panel-optional
feat: make all ui optional
2026-01-17 17:43:40 +08:00
Simon ce3a80bc53 docs: typo 2026-01-17 17:30:16 +08:00
Simon 64c36f67bb docs: update config; instructions for programmatic usage 2026-01-17 17:28:28 +08:00
Simon 78d4919289 feat: make panel optional 2026-01-17 17:24:17 +08:00
Simon 1af2607cca feat: remove ask_user tool from sys_prompt 2026-01-17 17:18:31 +08:00
16 changed files with 152 additions and 48 deletions
+12 -12
View File
@@ -1,12 +1,12 @@
{
"name": "root",
"version": "0.2.4",
"version": "0.2.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "root",
"version": "0.2.4",
"version": "0.2.5",
"license": "MIT",
"workspaces": [
"packages/page-controller",
@@ -8133,15 +8133,15 @@
},
"packages/cdn": {
"name": "@page-agent/cdn",
"version": "0.2.4",
"version": "0.2.5",
"license": "MIT",
"dependencies": {
"page-agent": "0.2.4"
"page-agent": "0.2.5"
}
},
"packages/llms": {
"name": "@page-agent/llms",
"version": "0.2.4",
"version": "0.2.5",
"license": "MIT",
"dependencies": {
"chalk": "^5.6.2",
@@ -8149,19 +8149,19 @@
}
},
"packages/page-agent": {
"version": "0.2.4",
"version": "0.2.5",
"license": "MIT",
"dependencies": {
"@page-agent/llms": "0.2.4",
"@page-agent/page-controller": "0.2.4",
"@page-agent/ui": "0.2.4",
"@page-agent/llms": "0.2.5",
"@page-agent/page-controller": "0.2.5",
"@page-agent/ui": "0.2.5",
"chalk": "^5.6.2",
"zod": "^4.3.5"
}
},
"packages/page-controller": {
"name": "@page-agent/page-controller",
"version": "0.2.4",
"version": "0.2.5",
"license": "MIT",
"dependencies": {
"ai-motion": "^0.4.8"
@@ -8169,12 +8169,12 @@
},
"packages/ui": {
"name": "@page-agent/ui",
"version": "0.2.4",
"version": "0.2.5",
"license": "MIT"
},
"packages/website": {
"name": "@page-agent/website",
"version": "0.2.4",
"version": "0.2.5",
"dependencies": {
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-separator": "^1.1.8",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "root",
"private": true,
"version": "0.2.4",
"version": "0.2.5",
"type": "module",
"workspaces": [
"packages/page-controller",
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@page-agent/cdn",
"private": false,
"version": "0.2.4",
"version": "0.2.5",
"type": "module",
"files": [
"dist/"
@@ -18,6 +18,6 @@
"build": "vite build && vite build --mode demo"
},
"dependencies": {
"page-agent": "0.2.4"
"page-agent": "0.2.5"
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@page-agent/llms",
"version": "0.2.4",
"version": "0.2.5",
"type": "module",
"main": "./dist/lib/page-agent-llms.js",
"module": "./dist/lib/page-agent-llms.js",
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "page-agent",
"private": false,
"version": "0.2.4",
"version": "0.2.5",
"type": "module",
"main": "./dist/esm/page-agent.js",
"module": "./dist/esm/page-agent.js",
@@ -45,8 +45,8 @@
"dependencies": {
"chalk": "^5.6.2",
"zod": "^4.3.5",
"@page-agent/llms": "0.2.4",
"@page-agent/page-controller": "0.2.4",
"@page-agent/ui": "0.2.4"
"@page-agent/llms": "0.2.5",
"@page-agent/page-controller": "0.2.5",
"@page-agent/ui": "0.2.5"
}
}
+32 -21
View File
@@ -99,7 +99,7 @@ export interface ExecutionResult {
export class PageAgent extends EventTarget {
config: PageAgentConfig
id = uid()
panel: Panel
panel: Panel | null = null
tools: typeof tools
disposed = false
task = ''
@@ -122,7 +122,7 @@ export class PageAgent extends EventTarget {
lastURL: '',
}
/** History event stream */
/** History events */
history: HistoryEvent[] = []
constructor(config: PageAgentConfig) {
@@ -130,11 +130,17 @@ export class PageAgent extends EventTarget {
this.config = config
this.#llm = new LLM(this.config)
this.panel = new Panel({
language: this.config.language,
onExecuteTask: (task) => this.execute(task),
onStop: () => this.dispose(),
})
// Conditionally initialize Panel
if (this.config.enablePanel !== false) {
this.panel = new Panel({
language: this.config.language,
onExecuteTask: (task) => this.execute(task),
onStop: () => this.dispose(),
promptForNextTask: this.config.promptForNextTask,
})
}
this.tools = new Map(tools)
// Initialize PageController with config (mask enabled by default)
@@ -146,11 +152,11 @@ export class PageAgent extends EventTarget {
// Listen to LLM events
this.#llmRetryListener = (e) => {
const { current, max } = (e as CustomEvent).detail
this.panel.update({ type: 'retry', current, max })
this.panel?.update({ type: 'retry', current, max })
}
this.#llmErrorListener = (e) => {
const { error } = (e as CustomEvent).detail
this.panel.update({ type: 'error', message: `step failed: ${error.message}` })
this.panel?.update({ type: 'error', message: `step failed: ${error.message}` })
}
this.#llm.addEventListener('retry', this.#llmRetryListener)
this.#llm.addEventListener('error', this.#llmErrorListener)
@@ -169,6 +175,11 @@ export class PageAgent extends EventTarget {
this.tools.delete('execute_javascript')
}
// Disable ask_user tool if enableAskUser is false or if panel is disabled
if (this.config.enableAskUser === false || this.config.enablePanel === false) {
this.tools.delete('ask_user')
}
this.#beforeUnloadListener = (e) => {
if (!this.disposed) this.dispose('PAGE_UNLOADING')
}
@@ -181,7 +192,7 @@ export class PageAgent extends EventTarget {
*/
pushObservation(content: string): void {
this.history.push({ type: 'observation', content })
this.panel.update({ type: 'observation', content })
this.panel?.update({ type: 'observation', content })
}
async execute(task: string): Promise<ExecutionResult> {
@@ -199,10 +210,10 @@ export class PageAgent extends EventTarget {
// Show mask and panel
this.pageController.showMask()
this.panel.show()
this.panel.reset()
this.panel?.show()
this.panel?.reset()
this.panel.update({ type: 'input', task: this.task })
this.panel?.update({ type: 'input', task: this.task })
if (this.#abortController) {
this.#abortController.abort()
@@ -232,7 +243,7 @@ export class PageAgent extends EventTarget {
// Update status to thinking
console.log(chalk.blue('Thinking...'))
this.panel.update({ type: 'thinking' })
this.panel?.update({ type: 'thinking' })
const result = await this.#llm.invoke(
[
@@ -370,7 +381,7 @@ export class PageAgent extends EventTarget {
if (reflectionText) {
console.log(reflectionText)
this.panel.update({ type: 'thinking', text: reflectionText })
this.panel?.update({ type: 'thinking', text: reflectionText })
}
// Find the corresponding tool
@@ -378,7 +389,7 @@ export class PageAgent extends EventTarget {
assert(tool, `Tool ${toolName} not found. (@note should have been caught before this!!!)`)
console.log(chalk.blue.bold(`Executing tool: ${toolName}`), toolInput)
this.panel.update({ type: 'toolExecuting', toolName, args: toolInput })
this.panel?.update({ type: 'toolExecuting', toolName, args: toolInput })
const startTime = Date.now()
@@ -394,7 +405,7 @@ export class PageAgent extends EventTarget {
}
// Briefly display execution result
this.panel.update({
this.panel?.update({
type: 'toolCompleted',
toolName,
args: toolInput,
@@ -557,13 +568,13 @@ export class PageAgent extends EventTarget {
// Update panel status
if (success) {
this.panel.update({ type: 'output', text })
this.panel?.update({ type: 'output', text })
} else {
this.panel.update({ type: 'error', message: text })
this.panel?.update({ type: 'error', message: text })
}
// Task completed
this.panel.update({ type: 'completed' })
this.panel?.update({ type: 'completed' })
this.pageController.hideMask()
@@ -593,7 +604,7 @@ export class PageAgent extends EventTarget {
console.log('Disposing PageAgent...')
this.disposed = true
this.pageController.dispose()
this.panel.dispose()
this.panel?.dispose()
this.history = []
this.#abortController.abort(reason ?? 'PageAgent disposed')
+21
View File
@@ -11,6 +11,27 @@ export interface AgentConfig {
// theme?: 'light' | 'dark'
language?: SupportedLanguage
/**
* Whether to prompt for next task after task completion
* @default true
*/
promptForNextTask?: boolean
/**
* Enable the UI panel for visual feedback and user interaction
* When disabled, the panel will not be created and all UI operations will be skipped.
* Useful for automated testing or when integrating PageAgent as a library.
* @default true
*/
enablePanel?: boolean
/**
* Enable the ask_user tool for agent to ask questions
* When disabled, the agent cannot ask user questions during execution.
* @default true
*/
enableAskUser?: boolean
/**
* Custom tools to extend PageAgent capabilities
* @experimental
@@ -92,7 +92,6 @@ Strictly follow these rules while using the browser and navigating the web:
- User can be wrong. If the request of user is not achievable, inappropriate or you do not have enough information or tools to achieve it. Tell user to make a better request.
- Webpage can be broken. All webpages or apps have bugs. Some bug will make it hard for your job. It's encouraged to tell user the problem of current page. Your feedbacks (including failing) are valuable for user.
- Trying to hard can be harmful. Repeating some action back and forth or pushing for a complex procedure with little knowledge can cause unwanted result and harmful side-effects. User would rather you to complete the task with a fail.
- If you are not clear about the request or steps. `ask_user` to clarify it.
- If you do not have knowledge for the current webpage or task. You must require user to give specific instructions and detailed steps.
</capability>
@@ -120,7 +119,7 @@ Exhibit the following reasoning patterns to successfully achieve the <user_reque
- Analyze all relevant items in <agent_history> and <browser_state> to understand your state.
- Explicitly judge success/failure/uncertainty of the last action. Never assume an action succeeded just because it appears to be executed in your last step in <agent_history>. If the expected change is missing, mark the last action as failed (or uncertain) and plan a recovery.
- Analyze whether you are stuck, e.g. when you repeat the same actions multiple times without any progress. Then consider alternative approaches e.g. scrolling for more context or ask user for help.
- `ask_user` for help if you have any difficulty. Users want to be kept in the loop.
- Ask user for help if you have any difficulty. Keep user in the loop.
- If you see information relevant to <user_request>, plan saving the information to memory.
- Always reason about the <user_request>. Make sure to carefully analyze the specific steps and information required. E.g. specific filters, specific form fields, specific information to search. Make sure to always compare the current trajectory with the user request and think carefully if thats how the user requested it.
</reasoning_rules>
+3
View File
@@ -80,6 +80,9 @@ tools.set(
question: zod.string(),
}),
execute: async function (this: PageAgent, input) {
if (!this.panel) {
throw new Error('ask_user tool requires panel to be enabled')
}
const answer = await this.panel.askUser(input.question)
return `✅ Received user answer: ${answer}`
},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@page-agent/page-controller",
"version": "0.2.4",
"version": "0.2.5",
"type": "module",
"main": "./dist/lib/page-controller.js",
"module": "./dist/lib/page-controller.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@page-agent/ui",
"version": "0.2.4",
"version": "0.2.5",
"type": "module",
"main": "./dist/lib/page-agent-ui.js",
"module": "./dist/lib/page-agent-ui.js",
+13 -1
View File
@@ -11,6 +11,11 @@ export interface PanelConfig {
language?: SupportedLanguage
onExecuteTask: (task: string) => void
onStop: () => void
/**
* Whether to prompt for next task after task completion
* @default true
*/
promptForNextTask?: boolean
}
/**
@@ -358,7 +363,14 @@ export class Panel {
}
const lastStep = steps[steps.length - 1]
return lastStep.type === 'completed' || lastStep.type === 'error'
const isTaskEnded = lastStep.type === 'completed' || lastStep.type === 'error'
// Only show input area after task completion if configured to do so
if (isTaskEnded) {
return this.#config.promptForNextTask ?? true
}
return false
}
#createWrapper(): HTMLElement {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@page-agent/website",
"private": true,
"version": "0.2.4",
"version": "0.2.5",
"type": "module",
"scripts": {
"dev": "vite --host 0.0.0.0",
+4
View File
@@ -89,6 +89,10 @@ export default function HomePage() {
import.meta.env.DEV && import.meta.env.LLM_API_KEY
? import.meta.env.LLM_API_KEY
: DEMO_API_KEY,
// enableAskUser: false,
// promptForNextTask: false,
// enablePanel: false,
})
}
@@ -61,6 +61,27 @@ export default function Configuration() {
code={`interface AgentConfig {
language?: 'en-US' | 'zh-CN'
/**
* Whether to prompt for next task after task completion
* @default true
*/
promptForNextTask?: boolean
/**
* Enable the UI panel for visual feedback and user interaction
* When disabled, the panel will not be created and all UI operations will be skipped.
* Useful for automated testing or when integrating PageAgent as a library.
* @default true
*/
enablePanel?: boolean
/**
* Enable the ask_user tool for agent to ask questions
* When disabled, the agent cannot ask user questions during execution.
* @default true
*/
enableAskUser?: boolean
/** Custom tools to extend or override built-in tools */
customTools?: Record<string, PageAgentTool | null>
@@ -140,6 +161,39 @@ interface PageControllerConfig extends DomConfig {
code={`type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig`}
/>
</section>
{/* Programmatic Usage Example */}
<section className="mb-10">
<h2 className="text-2xl font-semibold mb-4">
{isZh ? '程序化使用配置' : 'Programmatic Usage'}
</h2>
<p className="text-gray-600 dark:text-gray-400 mb-4">
{isZh
? '对于程序化集成场景,可以禁用 UI。'
: 'For programmatic integration, you can disable UI.'}
</p>
<CodeEditor
language="typescript"
code={`const agent = new PageAgent({
baseURL: 'https://api.openai.com/v1',
apiKey: 'your-api-key',
model: 'your-model-name',
// Disable all UI features for pure programmatic usage
enablePanel: false, // Don't create Panel UI
enableMask: false, // Don't show visual overlay (mask and pointer)
// enableAskUser is automatically disabled when enablePanel is false
// Or keep Panel but disable post-task prompts
// enablePanel: true,
// promptForNextTask: false,
})
// Pure programmatic execution
const result = await agent.execute('search for TypeScript documentation')
console.log(result.success, result.data, result.history)`}
/>
</section>
</div>
)
}
@@ -36,7 +36,7 @@ const pageAgentTool = {
},
execute: async (params) => {
const result = await pageAgent.execute(params.instruction)
return { success: result.success, message: result.message }
return { success: result.success, message: result.data }
}
}