Compare commits

...

16 Commits

Author SHA1 Message Date
Simon c465fd8aa4 chore(version): bump version to 1.6.3 2026-03-30 22:29:03 +08:00
Simon 1eef785a61 fix(ext): tabs deduplication 2026-03-30 22:18:55 +08:00
Simon 0b4eb6b49a docs(ext): experimentalIncludeAllTabs 2026-03-30 22:08:09 +08:00
Simon cba54967b8 Merge pull request #365 from alibaba/docs/simplify-contributing
docs: simplify docs
2026-03-30 22:02:11 +08:00
Simon 23dc3b7f7e Merge pull request #363 from alibaba/feat/ext-controll-all-tabs
fix(ext): MultiPageAgent inside content script can not detect new tabs
feat(ext): experimentalIncludeAllTabs - control all window tabs
2026-03-30 21:59:56 +08:00
Simon 49b137981c fix(ext): tab events do not work for content scripts 2026-03-30 21:40:16 +08:00
Simon cad033d63b feat(ext): add experimentalIncludeAllTabs UI 2026-03-30 20:25:04 +08:00
Simon 312952ec41 fix(ext): multi window errors 2026-03-30 20:24:24 +08:00
Simon 52edd78cd4 chore(ext): improve debug logging 2026-03-30 19:48:52 +08:00
Simon a7c9999d83 chore: mv security file 2026-03-28 19:21:20 +08:00
Simon 7f8ddde748 docs: move setup details to dev-guide; simplify readme and contributing 2026-03-28 19:11:54 +08:00
Simon e1fede1194 feat(ext): option to control all tabs 2026-03-27 20:18:13 +08:00
Simon 2322d6c46b docs: add awesome section 2026-03-27 19:32:04 +08:00
Simon ac72f55731 docs: simplify readme 2026-03-27 19:23:31 +08:00
Simon 5485e852bd Merge pull request #362 from 1245040330/fix-typing-animation
feat(extension): 禁用 EmptyState 组件的自动开始动画
2026-03-27 15:50:09 +08:00
1245040330 48fb65d00b feat(extension): 禁用 EmptyState 组件的自动开始动画
- 在 EmptyState 组件的动画配置中添加了 startOnView=false 属性
- 防止动画在视图加载时自动开始播放
- 优化了用户体验,避免不必要的动画干扰
2026-03-27 15:26:13 +08:00
28 changed files with 422 additions and 363 deletions
+13 -173
View File
@@ -1,45 +1,14 @@
# Contributing to PageAgent # Contributing to PageAgent
> **[A note from the maintainer](https://github.com/alibaba/page-agent/issues/349)** — Please read before opening issues or PRs.
♥️ We welcome contributions from everyone. ♥️ We welcome contributions from everyone.
## 🚀 Quick Start For local development workflows, setup, local LLM config, extension development, testing on other websites, and more details, see [docs/developer-guide.md](docs/developer-guide.md).
### Development Setup
1. **Prerequisites**
- `macOS` / `Linux` / `WSL`
- `node.js >= 20` with `npm >= 10`
- An editor that supports `ts/eslint/prettier`
- Make sure `eslint`, `prettier` and `commitlint` work well. Un-linted code won't pass the CI.
2. **Setup**
```bash
npm i
npm start # Start demo and documentation site
npm run build # Build libs and website
```
### Project Structure
This is a **monorepo** with npm workspaces containing **4 main packages**:
- **Page Agent** (`packages/page-agent/`) - Main entry with built-in UI Panel, published as `page-agent` on npm
- **Core** (`packages/core/`) - Core agent logic without UI (npm: `@page-agent/core`)
- **Extension** (`packages/extension/`) - Chrome extension for multi-page tasks and browser-level automation
- **Website** (`packages/website/`) - React documentation and landing page. Also as demo and test page for the core lib. private package `@page-agent/website`
> We use a simplified monorepo solution with `native npm-workspace + ts reference + vite alias`. No fancy tooling. Hoisting is required.
>
> - When developing. Use alias so that we don't have to pre-build.
> - When bundling. Use external and disable ts `paths` alias.
> - When bundling `IIFE` and `Website`. Bundle everything together.
## 🤝 How to Contribute ## 🤝 How to Contribute
### Reporting Issues > **[Maintainer's Note](https://github.com/alibaba/page-agent/issues/349)**
### Opening Issues
- Use the GitHub issue tracker to report bugs or request features - Use the GitHub issue tracker to report bugs or request features
- Search existing issues before creating new ones - Search existing issues before creating new ones
@@ -48,147 +17,24 @@ This is a **monorepo** with npm workspaces containing **4 main packages**:
### Code Contributions ### Code Contributions
1. **Fork and Clone** 1. Follow existing code style and patterns
2. Update documentation as needed
```bash 3. Add JSDoc for public APIs
git clone https://github.com/your-username/page-agent.git 4. Build and lint everything
cd page-agent 5. Test in our demo website, and on other websites if applicable
``` 6. Include screenshots for UI changes
2. **Create Feature Branch**
```bash
git checkout -b feat/your-feature-name
```
3. **Make Changes**
- Follow existing code style and patterns
- Add tests for new functionality
- Update documentation as needed
4. **Test Your Changes**
- Build and lint everything.
- Test in our demo website
- Test it on other websites if applicable
- `@TODO: test suite`
5. **Commit and Push**
```bash
git add .
git commit -m "feat: add awesome feature"
git push origin feat/your-feature-name
```
6. **Create Pull Request**
- Provide clear description of changes
- Link related issues
- Include screenshots for UI changes
## 📝 Code Style
### General Guidelines
- Use TypeScript for type safety
- Follow existing naming conventions
- Write meaningful commit messages
- Keep functions small and focused
- Add JSDoc for public APIs
### Vibe Coding with AI ### Vibe Coding with AI
> [Vibe coding](https://en.wikipedia.org/wiki/Vibe_coding) - Vibe coding is **NOT** allowed for the core lib or the extension!!!
- Vibe coding is **RECOMMENDED** when maintaining **the demo, the website, the UI and tests**. - Vibe coding is **RECOMMENDED** when maintaining **the demo, the website, the UI and tests**.
- We have a [website/AGENTS.md](packages/website/AGENTS.md) for that. - Make sure your AI references `AGENTS.md` and `website/AGENTS.md` for better quality.
- Vibe coding is **NOT** allowed for the core lib!!!
- NEVER try to vibe coding the MV3 extension!!! It is HELL.
- Review anything AI wrote before make a commit. You are the author of anything you commit. NOT AI. - Review anything AI wrote before make a commit. You are the author of anything you commit. NOT AI.
If your AI assistant does not support [AGENTS.md](https://agents.md/). Add an alias for it:
- claude-code (`CLAUDE.md`)
```markdown
@AGENTS.md
```
- antigravity (`.agent/rules/alias.md`)
```markdown
---
trigger: always_on
---
@../../AGENTS.md
```
## 🔧 Development Workflows
### Test With Your Own LLM API
- Create a `.env` file in the repo root with your LLM API config
```env
LLM_MODEL_NAME=gpt-5.2
LLM_API_KEY=your-api-key
LLM_BASE_URL=https://api.your-llm-provider.com/v1
```
- **Ollama example** (tested on 0.15 + qwen3:14b, RTX3090 24GB):
```env
LLM_BASE_URL="http://localhost:11434/v1"
LLM_API_KEY="NA"
LLM_MODEL_NAME="qwen3:14b"
```
> @see https://alibaba.github.io/page-agent/docs/features/models#ollama for configuration
- **Restart the dev server** to load new env vars
- If not provided, the demo will use the free testing proxy by default. By using it, you agree to its [terms](https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md).
### Extension Development
```bash
# make sure you ran `npm run build:libs` first
# and every time you changed the core libs
npm run dev -w @page-agent/ext
npm run zip -w @page-agent/ext
```
- Update `packages/extension/docs/extension_api.md` for API integration details
### Testing on Other Websites
- Start and serve a local `iife` script
```bash
npm run dev:demo # Serving IIFE with auto rebuild at http://localhost:5174/page-agent.demo.js
```
- Add a new bookmark
```javascript
javascript:(function(){var s=document.createElement('script');s.src=`http://localhost:5174/page-agent.demo.js?t=${Math.random()}`;s.onload=()=>console.log(%27PageAgent ready!%27);document.head.appendChild(s);})();
```
- Click the bookmark on any page to load Page-Agent
> Warning: AK in your local `.env` will be inlined in the iife script. Be very careful when you distribute the script.
### Adding Documentation
Ask an AI to help you add documentation to the `website/` package. Follow the existing style.
> Our AGENTS.md file and guardrails are designed for this purpose. But please be careful and review anything AI generated.
## 🚫 What We Don't Accept ## 🚫 What We Don't Accept
- Breaking changes and large PRs without prior discussion - Breaking changes and large PRs without prior discussion
- Heavy dependencies to core libs - Heavy dependencies to core libs
- Contributions without proper testing
- Code that doesn't follow project conventions
- Dependencies or code with licenses incompatible with MIT - Dependencies or code with licenses incompatible with MIT
- Bot or AI-generated pull requests without meaningful human involvement - Bot or AI-generated pull requests without meaningful human involvement
@@ -196,12 +42,6 @@ Ask an AI to help you add documentation to the `website/` package. Follow the ex
By contributing to this project, you agree that your contributions will be licensed under the MIT License. By contributing to this project, you agree that your contributions will be licensed under the MIT License.
> CLA is optional. ---
## 💬 Questions?
- Open a GitHub issue for technical questions
- Check existing documentation and issues first
- Be respectful and constructive in discussions
Thank you for helping make PageAgent better! 🎉 Thank you for helping make PageAgent better! 🎉
+19 -19
View File
@@ -33,7 +33,8 @@ The GUI Agent Living in Your Webpage. Control web interfaces with natural langua
- **SaaS AI Copilot** — Ship an AI copilot in your product in lines of code. No backend rewrite. - **SaaS AI Copilot** — Ship an AI copilot in your product in lines of code. No backend rewrite.
- **Smart Form Filling** — Turn 20-click workflows into one sentence. Perfect for ERP, CRM, and admin systems. - **Smart Form Filling** — Turn 20-click workflows into one sentence. Perfect for ERP, CRM, and admin systems.
- **Accessibility** — Make any web app accessible through natural language. Voice commands, screen readers, zero barrier. - **Accessibility** — Make any web app accessible through natural language. Voice commands, screen readers, zero barrier.
- **Multi-page Agent** — Extend your own agent's reach across browser tabs with the optional [chrome extension](https://alibaba.github.io/page-agent/docs/features/chrome-extension). - **Multi-page Agent** — Extend your own web agent's reach across browser tabs [chrome extension](https://alibaba.github.io/page-agent/docs/features/chrome-extension).
- **MCP** - Allow your agent clients to control your browser.
## 🚀 Quick Start ## 🚀 Quick Start
@@ -49,8 +50,8 @@ Fastest way to try PageAgent with our free Demo LLM:
| Mirrors | URL | | Mirrors | URL |
| ------- | ---------------------------------------------------------------------------------- | | ------- | ---------------------------------------------------------------------------------- |
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.6.2/dist/iife/page-agent.demo.js | | Global | https://cdn.jsdelivr.net/npm/page-agent@1.6.3/dist/iife/page-agent.demo.js |
| China | https://registry.npmmirror.com/page-agent/1.6.2/files/dist/iife/page-agent.demo.js | | China | https://registry.npmmirror.com/page-agent/1.6.3/files/dist/iife/page-agent.demo.js |
### NPM Installation ### NPM Installation
@@ -75,11 +76,15 @@ For more programmatic usage, see [📖 Documentations](https://alibaba.github.io
## 🤝 Contributing ## 🤝 Contributing
We welcome contributions from the community! Follow our instructions in [CONTRIBUTING.md](CONTRIBUTING.md) for setup and guidelines. We welcome contributions from the community! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines and [docs/developer-guide.md](docs/developer-guide.md) for local development workflows.
Please read the [maintainer note](https://github.com/alibaba/page-agent/issues/349) and [Code of Conduct](docs/CODE_OF_CONDUCT.md) before opening issues or PRs. Please read the [maintainer's note](https://github.com/alibaba/page-agent/issues/349) on principles and current state.
Contributions generated entirely by **bots or agents** without substantial human involvement will **not be accepted**. Contributions generated entirely by **bots or AI** without substantial human involvement will **not be accepted**.
## ⚖️ License
[MIT License](LICENSE)
## 👏 Acknowledgments ## 👏 Acknowledgments
@@ -97,23 +102,18 @@ Licensed under the MIT License
We gratefully acknowledge the browser-use project and its contributors for their We gratefully acknowledge the browser-use project and its contributors for their
excellent work on web automation and DOM interaction patterns that helped make excellent work on web automation and DOM interaction patterns that helped make
this project possible. this project possible.
Third-party dependencies and their licenses can be found in the package.json
file and in the node_modules directory after installation.
``` ```
## 📄 License ## 🌟 Awesome Page Agent
[MIT License](LICENSE) Built something cool with PageAgent? Add it here! Open a PR to share your project.
> These are community projects — not maintained or endorsed by us. Use at your own discretion.
| Project | Description |
| -------- | ----------------------------------------------------------- |
| _Yours?_ | [Open a PR](https://github.com/alibaba/page-agent/pulls) 🙌 |
--- ---
**⭐ Star this repo if you find PageAgent helpful!** **⭐ Star this repo if you find PageAgent helpful!**
<a href="https://www.star-history.com/?repos=alibaba%2Fpage-agent&type=date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=alibaba/page-agent&type=date&theme=dark&legend=top-left&v=7" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=alibaba/page-agent&type=date&legend=top-left&v=7" />
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=alibaba/page-agent&type=date&legend=top-left&v=7" />
</picture>
</a>
+8 -17
View File
@@ -32,7 +32,8 @@
- **SaaS AI Copilot** — 几行代码为你的产品加上 AI 副驾驶,无需重写后端。 - **SaaS AI Copilot** — 几行代码为你的产品加上 AI 副驾驶,无需重写后端。
- **智能表单填写** — 把 20 次点击变成一句话。ERP、CRM、管理后台的最佳拍档。 - **智能表单填写** — 把 20 次点击变成一句话。ERP、CRM、管理后台的最佳拍档。
- **无障碍增强** — 用自然语言让任何网页无障碍。语音指令、屏幕阅读器,零门槛。 - **无障碍增强** — 用自然语言让任何网页无障碍。语音指令、屏幕阅读器,零门槛。
- **跨页面 Agent** — 通过可选的 [Chrome 扩展](https://alibaba.github.io/page-agent/docs/features/chrome-extension),让你自己的 Agent 跨标签页工作。 - **跨页面 Agent** — 通过可选的 [Chrome 扩展](https://alibaba.github.io/page-agent/docs/features/chrome-extension),让你自己的 Web Agent 跨标签页工作。
- 通过 MCP 为现有 Agent 加入浏览器控制能力。
## 🚀 快速开始 ## 🚀 快速开始
@@ -48,8 +49,8 @@
| Mirrors | URL | | Mirrors | URL |
| ------- | ---------------------------------------------------------------------------------- | | ------- | ---------------------------------------------------------------------------------- |
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.6.2/dist/iife/page-agent.demo.js | | Global | https://cdn.jsdelivr.net/npm/page-agent@1.6.3/dist/iife/page-agent.demo.js |
| China | https://registry.npmmirror.com/page-agent/1.6.2/files/dist/iife/page-agent.demo.js | | China | https://registry.npmmirror.com/page-agent/1.6.3/files/dist/iife/page-agent.demo.js |
### NPM 安装 ### NPM 安装
@@ -76,11 +77,11 @@ await agent.execute('点击登录按钮')
欢迎社区贡献!请参阅 [CONTRIBUTING.md](../CONTRIBUTING.md) 了解安装与贡献指南。 欢迎社区贡献!请参阅 [CONTRIBUTING.md](../CONTRIBUTING.md) 了解安装与贡献指南。
提交 issue 或 PR 之前,请先阅读[维护者说](https://github.com/alibaba/page-agent/issues/349)和[行为准则](CODE_OF_CONDUCT.md)。 提交 issue 或 PR 之前,请先阅读[作者声](https://github.com/alibaba/page-agent/issues/349)和[行为准则](CODE_OF_CONDUCT.md)。
我们不接受未经实质性人类参与、完全由 Bot 或 Agent 自动生成的代码,机器人账号可能被禁止参与互动 我们不接受未经实质性人类参与、完全由 Bot 或 Agent 自动生成的代码。
## 👏 致谢 ## 👏 声明与致谢
本项目基于 **[`browser-use`](https://github.com/browser-use/browser-use)** 的优秀工作构建。 本项目基于 **[`browser-use`](https://github.com/browser-use/browser-use)** 的优秀工作构建。
@@ -96,12 +97,9 @@ Licensed under the MIT License
We gratefully acknowledge the browser-use project and its contributors for their We gratefully acknowledge the browser-use project and its contributors for their
excellent work on web automation and DOM interaction patterns that helped make excellent work on web automation and DOM interaction patterns that helped make
this project possible. this project possible.
Third-party dependencies and their licenses can be found in the package.json
file and in the node_modules directory after installation.
``` ```
## 📄 许可证 ## ⚖️ 许可证
[MIT License](../LICENSE) [MIT License](../LICENSE)
@@ -109,10 +107,3 @@ file and in the node_modules directory after installation.
**⭐ 如果觉得 PageAgent 有用或有趣,请给项目点个星!** **⭐ 如果觉得 PageAgent 有用或有趣,请给项目点个星!**
<a href="https://www.star-history.com/?repos=alibaba%2Fpage-agent&type=date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=alibaba/page-agent&type=date&theme=dark&legend=top-left&v=7" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=alibaba/page-agent&type=date&legend=top-left&v=7" />
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=alibaba/page-agent&type=date&legend=top-left&v=7" />
</picture>
</a>
View File
+117
View File
@@ -0,0 +1,117 @@
# Developer Guide
This file is for local development workflows.
For contribution rules and expectations, see [../CONTRIBUTING.md](../CONTRIBUTING.md).
## 🚀 Quick Start
### Development Setup
1. **Prerequisites**
- `macOS` / `Linux` / `WSL`
- `node.js >= 20` with `npm >= 10`
- An editor that supports `ts/eslint/prettier`
- Make sure `eslint`, `prettier` and `commitlint` work well. Un-linted code won't pass the CI.
2. **Setup**
```bash
npm i
npm start # Start demo and documentation site
npm run build # Build libs and website
```
## 📦 Project Structure
This is a **monorepo** with npm workspaces containing **4 main packages**:
- **Page Agent** (`packages/page-agent/`) - Main entry with built-in UI Panel, published as `page-agent` on npm
- **Core** (`packages/core/`) - Core agent logic without UI (npm: `@page-agent/core`)
- **Extension** (`packages/extension/`) - Chrome extension for multi-page tasks and browser-level automation
- **Website** (`packages/website/`) - React documentation and landing page. Also as demo and test page for the core lib. private package `@page-agent/website`
> We use a simplified monorepo solution with `native npm-workspace + ts reference + vite alias`. No fancy tooling. Hoisting is required.
>
> - When developing. Use alias so that we don't have to pre-build.
> - When bundling. Use external and disable ts `paths` alias.
> - When bundling `IIFE` and `Website`. Bundle everything together.
## 🤖 AGENTS.md Alias
If your AI assistant does not support [AGENTS.md](https://agents.md/). Add an alias for it:
- claude-code (`CLAUDE.md`)
```markdown
@AGENTS.md
```
- antigravity (`.agent/rules/alias.md`)
```markdown
---
trigger: always_on
---
@../../AGENTS.md
```
## 🔧 Development Workflows
### Test With Your Own LLM API
- Create a `.env` file in the repo root with your LLM API config
```env
LLM_MODEL_NAME=gpt-5.2
LLM_API_KEY=your-api-key
LLM_BASE_URL=https://api.your-llm-provider.com/v1
```
- **Ollama example** (tested on 0.15 + qwen3:14b, RTX3090 24GB):
```env
LLM_BASE_URL="http://localhost:11434/v1"
LLM_API_KEY="NA"
LLM_MODEL_NAME="qwen3:14b"
```
> @see https://alibaba.github.io/page-agent/docs/features/models#ollama for configuration
- **Restart the dev server** to load new env vars
- If not provided, the demo will use the free testing proxy by default. By using it, you agree to its [terms](./terms-and-privacy.md).
### Extension Development
```bash
# make sure you ran `npm run build:libs` first and every time you changed the core libs
npm run dev -w @page-agent/ext
npm run zip -w @page-agent/ext
```
- Update `packages/extension/docs/extension_api.md` for API integration details
### Testing on Other Websites
- Start and serve a local `iife` script
```bash
npm run dev:demo # Serving IIFE with auto rebuild at http://localhost:5174/page-agent.demo.js
```
- Add a new bookmark
```javascript
javascript:(function(){var s=document.createElement('script');s.src=`http://localhost:5174/page-agent.demo.js?t=${Math.random()}`;s.onload=()=>console.log(%27PageAgent ready!%27);document.head.appendChild(s);})();
```
- Click the bookmark on any page to load Page-Agent
> Warning: AK in your local `.env` will be inlined in the iife script. Be very careful when you distribute the script.
### Adding Documentation
Ask an AI to help you add documentation to the `website/` package. Follow the existing style.
> Our AGENTS.md file and guardrails are designed for this purpose. But please be careful and review anything AI generated.
+20 -20
View File
@@ -1,12 +1,12 @@
{ {
"name": "root", "name": "root",
"version": "1.6.2", "version": "1.6.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "root", "name": "root",
"version": "1.6.2", "version": "1.6.3",
"license": "MIT", "license": "MIT",
"workspaces": [ "workspaces": [
"packages/page-controller", "packages/page-controller",
@@ -11799,11 +11799,11 @@
}, },
"packages/core": { "packages/core": {
"name": "@page-agent/core", "name": "@page-agent/core",
"version": "1.6.2", "version": "1.6.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@page-agent/llms": "1.6.2", "@page-agent/llms": "1.6.3",
"@page-agent/page-controller": "1.6.2", "@page-agent/page-controller": "1.6.3",
"chalk": "^5.6.2" "chalk": "^5.6.2"
}, },
"devDependencies": { "devDependencies": {
@@ -11815,13 +11815,13 @@
}, },
"packages/extension": { "packages/extension": {
"name": "@page-agent/ext", "name": "@page-agent/ext",
"version": "1.6.2", "version": "1.6.3",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@page-agent/core": "1.6.2", "@page-agent/core": "1.6.3",
"@page-agent/llms": "1.6.2", "@page-agent/llms": "1.6.3",
"@page-agent/page-controller": "1.6.2", "@page-agent/page-controller": "1.6.3",
"@page-agent/ui": "1.6.2", "@page-agent/ui": "1.6.3",
"ai-motion": "^0.4.8", "ai-motion": "^0.4.8",
"chalk": "^5.6.2" "chalk": "^5.6.2"
}, },
@@ -11858,7 +11858,7 @@
}, },
"packages/llms": { "packages/llms": {
"name": "@page-agent/llms", "name": "@page-agent/llms",
"version": "1.6.2", "version": "1.6.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chalk": "^5.6.2" "chalk": "^5.6.2"
@@ -11872,7 +11872,7 @@
}, },
"packages/mcp": { "packages/mcp": {
"name": "@page-agent/mcp", "name": "@page-agent/mcp",
"version": "1.6.2", "version": "1.6.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "^1.27.1", "@modelcontextprotocol/sdk": "^1.27.1",
@@ -11887,13 +11887,13 @@
} }
}, },
"packages/page-agent": { "packages/page-agent": {
"version": "1.6.2", "version": "1.6.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@page-agent/core": "1.6.2", "@page-agent/core": "1.6.3",
"@page-agent/llms": "1.6.2", "@page-agent/llms": "1.6.3",
"@page-agent/page-controller": "1.6.2", "@page-agent/page-controller": "1.6.3",
"@page-agent/ui": "1.6.2", "@page-agent/ui": "1.6.3",
"chalk": "^5.6.2" "chalk": "^5.6.2"
}, },
"devDependencies": { "devDependencies": {
@@ -11905,7 +11905,7 @@
}, },
"packages/page-controller": { "packages/page-controller": {
"name": "@page-agent/page-controller", "name": "@page-agent/page-controller",
"version": "1.6.2", "version": "1.6.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ai-motion": "^0.4.8" "ai-motion": "^0.4.8"
@@ -11913,12 +11913,12 @@
}, },
"packages/ui": { "packages/ui": {
"name": "@page-agent/ui", "name": "@page-agent/ui",
"version": "1.6.2", "version": "1.6.3",
"license": "MIT" "license": "MIT"
}, },
"packages/website": { "packages/website": {
"name": "@page-agent/website", "name": "@page-agent/website",
"version": "1.6.2", "version": "1.6.3",
"devDependencies": { "devDependencies": {
"@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-separator": "^1.1.8",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "root", "name": "root",
"private": true, "private": true,
"version": "1.6.2", "version": "1.6.3",
"type": "module", "type": "module",
"workspaces": [ "workspaces": [
"packages/page-controller", "packages/page-controller",
+3 -3
View File
@@ -1,7 +1,7 @@
{ {
"name": "@page-agent/core", "name": "@page-agent/core",
"private": false, "private": false,
"version": "1.6.2", "version": "1.6.3",
"type": "module", "type": "module",
"main": "./dist/esm/page-agent-core.js", "main": "./dist/esm/page-agent-core.js",
"module": "./dist/esm/page-agent-core.js", "module": "./dist/esm/page-agent-core.js",
@@ -44,8 +44,8 @@
}, },
"dependencies": { "dependencies": {
"chalk": "^5.6.2", "chalk": "^5.6.2",
"@page-agent/llms": "1.6.2", "@page-agent/llms": "1.6.3",
"@page-agent/page-controller": "1.6.2" "@page-agent/page-controller": "1.6.3"
}, },
"peerDependencies": { "peerDependencies": {
"zod": "^3.25.0 || ^4.0.0" "zod": "^3.25.0 || ^4.0.0"
+6
View File
@@ -121,6 +121,11 @@ export interface ExecuteConfig {
// Include the initial tab where page JS starts. Default: true. // Include the initial tab where page JS starts. Default: true.
includeInitialTab?: boolean includeInitialTab?: boolean
// Control all unpinned tabs in the window instead of only the tab group.
// When enabled, agent sees and can switch to every non-pinned tab.
// Default: false. Experimental.
experimentalIncludeAllTabs?: boolean
onStatusChange?: (status: AgentStatus) => void onStatusChange?: (status: AgentStatus) => void
onActivity?: (activity: AgentActivity) => void onActivity?: (activity: AgentActivity) => void
onHistoryUpdate?: (history: HistoricalEvent[]) => void onHistoryUpdate?: (history: HistoricalEvent[]) => void
@@ -208,6 +213,7 @@ interface ExecuteConfig {
model: string model: string
apiKey?: string apiKey?: string
includeInitialTab?: boolean includeInitialTab?: boolean
experimentalIncludeAllTabs?: boolean
onStatusChange?: (status: AgentStatus) => void onStatusChange?: (status: AgentStatus) => void
onActivity?: (activity: AgentActivity) => void onActivity?: (activity: AgentActivity) => void
onHistoryUpdate?: (history: HistoricalEvent[]) => void onHistoryUpdate?: (history: HistoricalEvent[]) => void
+5 -5
View File
@@ -1,7 +1,7 @@
{ {
"name": "@page-agent/ext", "name": "@page-agent/ext",
"private": true, "private": true,
"version": "1.6.2", "version": "1.6.3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "wxt", "dev": "wxt",
@@ -37,10 +37,10 @@
"wxt": "^0.20.20" "wxt": "^0.20.20"
}, },
"dependencies": { "dependencies": {
"@page-agent/core": "1.6.2", "@page-agent/core": "1.6.3",
"@page-agent/llms": "1.6.2", "@page-agent/llms": "1.6.3",
"@page-agent/page-controller": "1.6.2", "@page-agent/page-controller": "1.6.3",
"@page-agent/ui": "1.6.2", "@page-agent/ui": "1.6.3",
"ai-motion": "^0.4.8", "ai-motion": "^0.4.8",
"chalk": "^5.6.2" "chalk": "^5.6.2"
}, },
@@ -11,13 +11,18 @@ function detectLanguage(): 'en-US' | 'zh-CN' {
return lang.startsWith('zh') ? 'zh-CN' : 'en-US' return lang.startsWith('zh') ? 'zh-CN' : 'en-US'
} }
interface MultiPageAgentConfig extends AgentConfig {
includeInitialTab?: boolean
experimentalIncludeAllTabs?: boolean
}
/** /**
* MultiPageAgent * MultiPageAgent
* - use with extension * - use with extension
* - can be used from a side panel or a content script * - can be used from a side panel or a content script
*/ */
export class MultiPageAgent extends PageAgentCore { export class MultiPageAgent extends PageAgentCore {
constructor(config: AgentConfig & { includeInitialTab?: boolean }) { constructor(config: MultiPageAgentConfig) {
// multi page controller // multi page controller
const tabsController = new TabsController() const tabsController = new TabsController()
const pageController = new RemotePageController(tabsController) const pageController = new RemotePageController(tabsController)
@@ -31,8 +36,8 @@ export class MultiPageAgent extends PageAgentCore {
`Default working language: **${targetLanguage}**` `Default working language: **${targetLanguage}**`
) )
// include initial tab for controlling
const includeInitialTab = config.includeInitialTab ?? true const includeInitialTab = config.includeInitialTab ?? true
const experimentalIncludeAllTabs = config.experimentalIncludeAllTabs ?? false
/** /**
* When the agent is in side-panel and user closed the side-panel. * When the agent is in side-panel and user closed the side-panel.
@@ -50,7 +55,7 @@ export class MultiPageAgent extends PageAgentCore {
customSystemPrompt: systemPrompt, customSystemPrompt: systemPrompt,
onBeforeTask: async (agent) => { onBeforeTask: async (agent) => {
await tabsController.init(agent.task, includeInitialTab) await tabsController.init(agent.task, { includeInitialTab, experimentalIncludeAllTabs })
heartBeatInterval = window.setInterval(() => { heartBeatInterval = window.setInterval(() => {
chrome.storage.local.set({ chrome.storage.local.set({
@@ -10,9 +10,7 @@ export function handlePageControlMessage(
): true | undefined { ): true | undefined {
const PREFIX = '[RemotePageController.background]' const PREFIX = '[RemotePageController.background]'
function debug(...messages: any[]) { const debug = console.debug.bind(console, `\x1b[90m${PREFIX}\x1b[0m`)
console.debug(`\x1b[90m${PREFIX}\x1b[0m`, ...messages)
}
const { action, payload, targetTabId } = message const { action, payload, targetTabId } = message
@@ -4,9 +4,7 @@ import type { TabsController } from './TabsController'
const PREFIX = '[RemotePageController]' const PREFIX = '[RemotePageController]'
function debug(...messages: any[]) { const debug = console.debug.bind(console, `\x1b[90m${PREFIX}\x1b[0m`)
console.debug(`\x1b[90m${PREFIX}\x1b[0m`, ...messages)
}
function sendMessage(message: { function sendMessage(message: {
type: 'PAGE_CONTROL' type: 'PAGE_CONTROL'
@@ -5,9 +5,7 @@ import type { TabAction } from './TabsController'
const PREFIX = '[TabsController.background]' const PREFIX = '[TabsController.background]'
function debug(...messages: any[]) { const debug = console.debug.bind(console, `\x1b[90m${PREFIX}\x1b[0m`)
console.debug(`\x1b[90m${PREFIX}\x1b[0m`, ...messages)
}
export function handleTabControlMessage( export function handleTabControlMessage(
message: { type: 'TAB_CONTROL'; action: TabAction; payload: any }, message: { type: 'TAB_CONTROL'; action: TabAction; payload: any },
@@ -20,11 +18,10 @@ export function handleTabControlMessage(
case 'get_active_tab': { case 'get_active_tab': {
debug('get_active_tab') debug('get_active_tab')
chrome.tabs chrome.tabs
.query({ active: true, currentWindow: true }) .query({ active: true })
.then((tabs) => { .then((tabs) => {
const tabId = tabs.length > 0 ? tabs[0].id || null : null debug('get_active_tab: success', tabs)
debug('get_active_tab: success', tabId) sendResponse({ success: true, tab: tabs[0] })
sendResponse({ success: true, tabId })
}) })
.catch((error) => { .catch((error) => {
sendResponse({ error: error instanceof Error ? error.message : String(error) }) sendResponse({ error: error instanceof Error ? error.message : String(error) })
@@ -63,7 +60,7 @@ export function handleTabControlMessage(
case 'create_tab_group': { case 'create_tab_group': {
debug('create_tab_group', payload) debug('create_tab_group', payload)
chrome.tabs chrome.tabs
.group({ tabIds: payload.tabIds }) .group({ tabIds: payload.tabIds, createProperties: { windowId: payload.windowId } })
.then((groupId) => { .then((groupId) => {
debug('create_tab_group: success', groupId) debug('create_tab_group: success', groupId)
sendResponse({ success: true, groupId }) sendResponse({ success: true, groupId })
@@ -114,47 +111,59 @@ export function handleTabControlMessage(
return true // async response return true // async response
} }
case 'get_window_tabs': {
debug('get_window_tabs', payload)
chrome.tabs
.query({ windowId: payload.windowId })
.then((tabs) => {
sendResponse({ success: true, tabs })
})
.catch((error) => {
sendResponse({ error: error instanceof Error ? error.message : String(error) })
})
return true
}
default: default:
sendResponse({ error: `Unknown action: ${action}` }) sendResponse({ error: `Unknown action: ${action}` })
return return
} }
} }
export function setupTabChangeEvents() { const tabEventPorts = new Set<chrome.runtime.Port>()
console.log('[TabsController.background] setupTabChangeEvents')
function broadcastTabEvent(message: object) {
for (const port of tabEventPorts) {
port.postMessage(message)
}
}
/**
* Port-based tab events: agents connect via `chrome.runtime.connect({ name: 'tab-events' })`
* and receive tab change events through the port. Works for both extension pages and content scripts.
*/
export function setupTabEventsPort() {
chrome.runtime.onConnect.addListener((port) => {
if (port.name !== 'tab-events') return
debug('port connected', port.sender?.tab?.id ?? port.sender?.url)
tabEventPorts.add(port)
port.onDisconnect.addListener(() => {
debug('port disconnected')
tabEventPorts.delete(port)
})
})
chrome.tabs.onCreated.addListener((tab) => { chrome.tabs.onCreated.addListener((tab) => {
debug('onCreated', tab) broadcastTabEvent({ action: 'created', payload: { tab } })
chrome.runtime
.sendMessage({ type: 'TAB_CHANGE', action: 'created', payload: { tab } })
.catch((error) => {
debug('onCreated error:', error)
})
}) })
chrome.tabs.onRemoved.addListener((tabId, removeInfo) => { chrome.tabs.onRemoved.addListener((tabId, removeInfo) => {
debug('onRemoved', tabId, removeInfo) broadcastTabEvent({ action: 'removed', payload: { tabId, removeInfo } })
chrome.runtime
.sendMessage({
type: 'TAB_CHANGE',
action: 'removed',
payload: { tabId, removeInfo },
})
.catch((error) => {
debug('onRemoved error:', error)
})
}) })
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
debug('onUpdated', tabId, changeInfo) broadcastTabEvent({ action: 'updated', payload: { tabId, changeInfo, tab } })
chrome.runtime
.sendMessage({
type: 'TAB_CHANGE',
action: 'updated',
payload: { tabId, changeInfo, tab },
})
.catch((error) => {
debug('onUpdated error:', error)
})
}) })
} }
+137 -66
View File
@@ -2,9 +2,7 @@ import { isContentScriptAllowed } from './RemotePageController'
const PREFIX = '[TabsController]' const PREFIX = '[TabsController]'
function debug(...messages: any[]) { const debug = console.debug.bind(console, `\x1b[90m${PREFIX}\x1b[0m`)
console.debug(`\x1b[90m${PREFIX}\x1b[0m`, ...messages)
}
function sendMessage(message: { function sendMessage(message: {
type: 'TAB_CONTROL' type: 'TAB_CONTROL'
@@ -22,46 +20,91 @@ function sendMessage(message: {
* - live in the agent env (extension page or content script) * - live in the agent env (extension page or content script)
* - no chrome apis. call sw for tab operations * - no chrome apis. call sw for tab operations
*/ */
export class TabsController extends EventTarget { export class TabsController {
currentTabId: number | null = null currentTabId: number | null = null
private disposed = false
private port: chrome.runtime.Port | null = null
private portRetries = 0
private windowId: number | null = null
private tabs: TabMeta[] = [] private tabs: TabMeta[] = []
private initialTabId: number | null = null private initialTabId: number | null = null
private tabGroupId: number | null = null private tabGroupId: number | null = null
private experimentalIncludeAllTabs = false
private task: string = '' private task: string = ''
async init(task: string, includeInitialTab: boolean = true) { async init(task: string, options: TabsInitOptions = {}) {
debug('init', task, includeInitialTab) const { includeInitialTab = true, experimentalIncludeAllTabs = false } = options
debug('init', task, options)
if (this.disposed) {
throw new Error('TabsController already disposed')
}
this.task = task
this.tabs = []
this.currentTabId = null this.currentTabId = null
this.disposed = false
this.port = null
this.portRetries = 0
this.windowId = null
this.tabs = []
this.tabGroupId = null this.tabGroupId = null
this.initialTabId = null this.initialTabId = null
this.experimentalIncludeAllTabs = experimentalIncludeAllTabs
this.task = task
const result = await sendMessage({ const activeTabResult = await sendMessage({
type: 'TAB_CONTROL', type: 'TAB_CONTROL',
action: 'get_active_tab', action: 'get_active_tab',
}) })
this.initialTabId = result.tabId this.initialTabId = activeTabResult.tab?.id
this.windowId = activeTabResult.tab?.windowId
if (!this.initialTabId) { if (!this.initialTabId || !this.windowId) {
throw new Error('Failed to get initial tab ID') if (activeTabResult.error) {
throw new Error(activeTabResult.error)
} else {
throw new Error('Failed to get active tab')
}
} }
if (includeInitialTab) { this.connectTabEvents()
if (experimentalIncludeAllTabs) {
const allTabs = await sendMessage({
type: 'TAB_CONTROL',
action: 'get_window_tabs',
payload: { windowId: this.windowId },
})
for (const tab of allTabs.tabs as chrome.tabs.Tab[]) {
if (tab.id && !tab.pinned && isContentScriptAllowed(tab.url)) {
this.addTab({
id: tab.id,
isInitial: tab.id === this.initialTabId,
url: tab.url,
title: tab.title,
status: tab.status,
})
}
}
if (this.tabs.find((t) => t.id === this.initialTabId)) {
this.currentTabId = this.initialTabId
await this.createTabGroup([this.initialTabId])
}
} else if (includeInitialTab) {
const info = await sendMessage({ const info = await sendMessage({
type: 'TAB_CONTROL', type: 'TAB_CONTROL',
action: 'get_tab_info', action: 'get_tab_info',
payload: { tabId: this.initialTabId }, payload: { tabId: this.initialTabId },
}) })
if (isContentScriptAllowed(info.url)) { if (isContentScriptAllowed(info.url) && !info.pinned) {
this.currentTabId = this.initialTabId this.currentTabId = this.initialTabId
this.tabs.push({ this.addTab({
id: result.tabId, id: this.initialTabId,
isInitial: true, isInitial: true,
url: info.url, url: info.url,
title: info.title, title: info.title,
@@ -73,52 +116,6 @@ export class TabsController extends EventTarget {
} }
await this.updateCurrentTabId(this.currentTabId) await this.updateCurrentTabId(this.currentTabId)
const tabChangeHandler = (message: any): void => {
if (message.type !== 'TAB_CHANGE') {
// throw new Error(`[TabsController]: Invalid message type: ${message.type}`)
return
}
if (message.action === 'created') {
const tab = message.payload.tab as chrome.tabs.Tab
if (tab.groupId === this.tabGroupId && tab.id != null) {
// Tab created in our controlled group
if (!this.tabs.find((t) => t.id === tab.id)) {
this.tabs.push({ id: tab.id, isInitial: false })
}
this.switchToTab(tab.id)
}
} else if (message.action === 'removed') {
const { tabId } = message.payload as { tabId: number }
const targetTab = this.tabs.find((t) => t.id === tabId)
if (targetTab) {
this.tabs = this.tabs.filter((t) => t.id !== tabId)
if (this.currentTabId === tabId) {
const newCurrentTab = this.tabs[this.tabs.length - 1] || null
if (newCurrentTab) {
this.switchToTab(newCurrentTab.id)
} else {
this.updateCurrentTabId(null)
}
}
}
} else if (message.action === 'updated') {
const { tabId, tab } = message.payload as { tabId: number; tab: chrome.tabs.Tab }
const targetTab = this.tabs.find((t) => t.id === tabId)
if (targetTab) {
targetTab.url = tab.url
targetTab.title = tab.title
targetTab.status = tab.status
}
}
}
chrome.runtime.onMessage.addListener(tabChangeHandler)
this.addEventListener('dispose', () => {
chrome.runtime.onMessage.removeListener(tabChangeHandler)
})
} }
async openNewTab(url: string): Promise<string> { async openNewTab(url: string): Promise<string> {
@@ -136,7 +133,7 @@ export class TabsController extends EventTarget {
const tabId = result.tabId as number const tabId = result.tabId as number
this.tabs.push({ this.addTab({
id: tabId, id: tabId,
isInitial: false, isInitial: false,
}) })
@@ -209,7 +206,7 @@ export class TabsController extends EventTarget {
const result = await sendMessage({ const result = await sendMessage({
type: 'TAB_CONTROL', type: 'TAB_CONTROL',
action: 'create_tab_group', action: 'create_tab_group',
payload: { tabIds }, payload: { tabIds, windowId: this.windowId },
}) })
if (!result?.success) { if (!result?.success) {
@@ -232,6 +229,11 @@ export class TabsController extends EventTarget {
}) })
} }
private addTab(meta: TabMeta) {
if (this.tabs.find((t) => t.id === meta.id)) return
this.tabs.push(meta)
}
async updateCurrentTabId(tabId: number | null) { async updateCurrentTabId(tabId: number | null) {
debug('updateCurrentTabId', tabId) debug('updateCurrentTabId', tabId)
@@ -288,9 +290,77 @@ export class TabsController extends EventTarget {
await waitUntil(() => tab.status === 'complete', 4_000) await waitUntil(() => tab.status === 'complete', 4_000)
} }
dispose() { /**
this.dispatchEvent(new Event('dispose')) * Connect to background SW via port to receive tab change events.
*
* @note Port is 1:1 (runtime.connect → background SW has no frames),
* so onDisconnect fires exactly once and we can safely reconnect.
* Reconnection may miss events during the gap.
* TODO: refresh this.tabs from background after reconnect to stay consistent.
*/
private connectTabEvents() {
this.port = chrome.runtime.connect({ name: 'tab-events' })
this.port.onMessage.addListener((message: any) => {
if (this.disposed) return
this.portRetries = 0
if (message.action === 'created') {
const tab = message.payload.tab as chrome.tabs.Tab
const shouldTrack = this.experimentalIncludeAllTabs || tab.groupId === this.tabGroupId
if (shouldTrack && tab.id != null) {
this.addTab({ id: tab.id, isInitial: false })
this.switchToTab(tab.id)
}
} else if (message.action === 'removed') {
const { tabId } = message.payload as { tabId: number }
const targetTab = this.tabs.find((t) => t.id === tabId)
if (targetTab) {
this.tabs = this.tabs.filter((t) => t.id !== tabId)
if (this.currentTabId === tabId) {
const newCurrentTab = this.tabs[this.tabs.length - 1] || null
if (newCurrentTab) {
this.switchToTab(newCurrentTab.id)
} else {
this.updateCurrentTabId(null)
}
}
}
} else if (message.action === 'updated') {
const { tabId, tab } = message.payload as { tabId: number; tab: chrome.tabs.Tab }
const targetTab = this.tabs.find((t) => t.id === tabId)
if (targetTab) {
targetTab.url = tab.url
targetTab.title = tab.title
targetTab.status = tab.status
}
}
})
this.port.onDisconnect.addListener(() => {
this.port = null
if (this.disposed) return
if (this.portRetries >= 7) {
console.error(PREFIX, 'tab events port failed after 3 retries, giving up')
return
}
debug('port disconnected, reconnecting...')
this.portRetries++
this.connectTabEvents()
})
} }
dispose() {
debug('dispose')
this.disposed = true
this.port?.disconnect()
this.port = null
}
}
export interface TabsInitOptions {
includeInitialTab?: boolean
experimentalIncludeAllTabs?: boolean
} }
export type TabAction = export type TabAction =
@@ -302,6 +372,7 @@ export type TabAction =
| 'add_tab_to_group' | 'add_tab_to_group'
| 'close_tab' | 'close_tab'
| 'get_tab_title' | 'get_tab_title'
| 'get_window_tabs'
interface TabMeta { interface TabMeta {
id: number id: number
+3
View File
@@ -21,6 +21,7 @@ export interface AdvancedConfig {
maxSteps?: number maxSteps?: number
systemInstruction?: string systemInstruction?: string
experimentalLlmsTxt?: boolean experimentalLlmsTxt?: boolean
experimentalIncludeAllTabs?: boolean
disableNamedToolChoice?: boolean disableNamedToolChoice?: boolean
} }
@@ -125,6 +126,7 @@ export function useAgent(): UseAgentResult {
maxSteps, maxSteps,
systemInstruction, systemInstruction,
experimentalLlmsTxt, experimentalLlmsTxt,
experimentalIncludeAllTabs,
disableNamedToolChoice, disableNamedToolChoice,
...llmConfig ...llmConfig
}: ExtConfig) => { }: ExtConfig) => {
@@ -138,6 +140,7 @@ export function useAgent(): UseAgentResult {
maxSteps, maxSteps,
systemInstruction, systemInstruction,
experimentalLlmsTxt, experimentalLlmsTxt,
experimentalIncludeAllTabs,
disableNamedToolChoice, disableNamedToolChoice,
} }
await chrome.storage.local.set({ advancedConfig }) await chrome.storage.local.set({ advancedConfig })
@@ -36,6 +36,9 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
const [experimentalLlmsTxt, setExperimentalLlmsTxt] = useState( const [experimentalLlmsTxt, setExperimentalLlmsTxt] = useState(
config?.experimentalLlmsTxt ?? false config?.experimentalLlmsTxt ?? false
) )
const [experimentalIncludeAllTabs, setExperimentalIncludeAllTabs] = useState(
config?.experimentalIncludeAllTabs ?? false
)
const [disableNamedToolChoice, setDisableNamedToolChoice] = useState( const [disableNamedToolChoice, setDisableNamedToolChoice] = useState(
config?.disableNamedToolChoice ?? false config?.disableNamedToolChoice ?? false
) )
@@ -54,6 +57,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
setMaxSteps(config?.maxSteps) setMaxSteps(config?.maxSteps)
setSystemInstruction(config?.systemInstruction ?? '') setSystemInstruction(config?.systemInstruction ?? '')
setExperimentalLlmsTxt(config?.experimentalLlmsTxt ?? false) setExperimentalLlmsTxt(config?.experimentalLlmsTxt ?? false)
setExperimentalIncludeAllTabs(config?.experimentalIncludeAllTabs ?? false)
setDisableNamedToolChoice(config?.disableNamedToolChoice ?? false) setDisableNamedToolChoice(config?.disableNamedToolChoice ?? false)
}, [config]) }, [config])
@@ -100,6 +104,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
maxSteps: maxSteps || undefined, maxSteps: maxSteps || undefined,
systemInstruction: systemInstruction || undefined, systemInstruction: systemInstruction || undefined,
experimentalLlmsTxt, experimentalLlmsTxt,
experimentalIncludeAllTabs,
disableNamedToolChoice, disableNamedToolChoice,
}) })
} finally { } finally {
@@ -285,6 +290,14 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
<span className="text-xs text-muted-foreground">Experimental llms.txt support</span> <span className="text-xs text-muted-foreground">Experimental llms.txt support</span>
<Switch checked={experimentalLlmsTxt} onCheckedChange={setExperimentalLlmsTxt} /> <Switch checked={experimentalLlmsTxt} onCheckedChange={setExperimentalLlmsTxt} />
</label> </label>
<label className="flex items-center justify-between cursor-pointer">
<span className="text-xs text-muted-foreground">Experimental include all tabs</span>
<Switch
checked={experimentalIncludeAllTabs}
onCheckedChange={setExperimentalIncludeAllTabs}
/>
</label>
</> </>
)} )}
@@ -111,6 +111,7 @@ export function EmptyState() {
]} ]}
cursorStyle="underscore" cursorStyle="underscore"
loop loop
startOnView={false}
typeSpeed={20} typeSpeed={20}
deleteSpeed={10} deleteSpeed={10}
pauseDelay={3000} pauseDelay={3000}
@@ -1,12 +1,12 @@
import { handlePageControlMessage } from '@/agent/RemotePageController.background' import { handlePageControlMessage } from '@/agent/RemotePageController.background'
import { handleTabControlMessage, setupTabChangeEvents } from '@/agent/TabsController.background' import { handleTabControlMessage, setupTabEventsPort } from '@/agent/TabsController.background'
export default defineBackground(() => { export default defineBackground(() => {
console.log('[Background] Service Worker started') console.log('[Background] Service Worker started')
// tab change events // tab change events
setupTabChangeEvents() setupTabEventsPort()
// generate user auth token // generate user auth token
@@ -13,6 +13,9 @@ export interface ExecuteConfig {
*/ */
includeInitialTab?: boolean includeInitialTab?: boolean
/** Control all unpinned tabs in the window instead of only the tab group. */
experimentalIncludeAllTabs?: boolean
onStatusChange?: (status: AgentStatus) => void onStatusChange?: (status: AgentStatus) => void
onActivity?: (activity: AgentActivity) => void onActivity?: (activity: AgentActivity) => void
onHistoryUpdate?: (history: HistoricalEvent[]) => void onHistoryUpdate?: (history: HistoricalEvent[]) => void
@@ -87,6 +90,7 @@ export default defineUnlistedScript(() => {
model: config.model, model: config.model,
apiKey: config.apiKey, apiKey: config.apiKey,
includeInitialTab: config.includeInitialTab, includeInitialTab: config.includeInitialTab,
experimentalIncludeAllTabs: config.experimentalIncludeAllTabs,
}, },
}, },
}, },
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@page-agent/llms", "name": "@page-agent/llms",
"version": "1.6.2", "version": "1.6.3",
"type": "module", "type": "module",
"main": "./dist/lib/page-agent-llms.js", "main": "./dist/lib/page-agent-llms.js",
"module": "./dist/lib/page-agent-llms.js", "module": "./dist/lib/page-agent-llms.js",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@page-agent/mcp", "name": "@page-agent/mcp",
"private": false, "private": false,
"version": "1.6.2", "version": "1.6.3",
"type": "module", "type": "module",
"bin": { "bin": {
"page-agent-mcp": "src/index.js" "page-agent-mcp": "src/index.js"
+5 -5
View File
@@ -1,7 +1,7 @@
{ {
"name": "page-agent", "name": "page-agent",
"private": false, "private": false,
"version": "1.6.2", "version": "1.6.3",
"type": "module", "type": "module",
"main": "./dist/esm/page-agent.js", "main": "./dist/esm/page-agent.js",
"module": "./dist/esm/page-agent.js", "module": "./dist/esm/page-agent.js",
@@ -44,10 +44,10 @@
"postpublish": "node -e \"['README.md','LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\"" "postpublish": "node -e \"['README.md','LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
}, },
"dependencies": { "dependencies": {
"@page-agent/core": "1.6.2", "@page-agent/core": "1.6.3",
"@page-agent/llms": "1.6.2", "@page-agent/llms": "1.6.3",
"@page-agent/page-controller": "1.6.2", "@page-agent/page-controller": "1.6.3",
"@page-agent/ui": "1.6.2", "@page-agent/ui": "1.6.3",
"chalk": "^5.6.2" "chalk": "^5.6.2"
}, },
"peerDependencies": { "peerDependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@page-agent/page-controller", "name": "@page-agent/page-controller",
"version": "1.6.2", "version": "1.6.3",
"type": "module", "type": "module",
"main": "./dist/lib/page-controller.js", "main": "./dist/lib/page-controller.js",
"module": "./dist/lib/page-controller.js", "module": "./dist/lib/page-controller.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@page-agent/ui", "name": "@page-agent/ui",
"version": "1.6.2", "version": "1.6.3",
"type": "module", "type": "module",
"main": "./dist/lib/page-agent-ui.js", "main": "./dist/lib/page-agent-ui.js",
"module": "./dist/lib/page-agent-ui.js", "module": "./dist/lib/page-agent-ui.js",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@page-agent/website", "name": "@page-agent/website",
"private": true, "private": true,
"version": "1.6.2", "version": "1.6.3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --host 0.0.0.0", "dev": "vite --host 0.0.0.0",
+2 -2
View File
@@ -1,8 +1,8 @@
// Demo build (auto-init with demo LLM, for quick testing) // Demo build (auto-init with demo LLM, for quick testing)
export const CDN_DEMO_URL = export const CDN_DEMO_URL =
'https://cdn.jsdelivr.net/npm/page-agent@1.6.2/dist/iife/page-agent.demo.js' 'https://cdn.jsdelivr.net/npm/page-agent@1.6.3/dist/iife/page-agent.demo.js'
export const CDN_DEMO_CN_URL = export const CDN_DEMO_CN_URL =
'https://registry.npmmirror.com/page-agent/1.6.2/files/dist/iife/page-agent.demo.js' 'https://registry.npmmirror.com/page-agent/1.6.3/files/dist/iife/page-agent.demo.js'
// Demo LLM for website testing (homepage quick trial uses flash) // Demo LLM for website testing (homepage quick trial uses flash)
export const DEMO_MODEL = 'qwen3.5-flash' export const DEMO_MODEL = 'qwen3.5-flash'
@@ -200,6 +200,7 @@ interface ExecuteConfig {
apiKey?: string // LLM AK apiKey?: string // LLM AK
includeInitialTab?: boolean includeInitialTab?: boolean
experimentalIncludeAllTabs?: boolean // Control all unpinned tabs in the window
onStatusChange?: (status: AgentStatus) => void onStatusChange?: (status: AgentStatus) => void
onActivity?: (activity: AgentActivity) => void onActivity?: (activity: AgentActivity) => void
onHistoryUpdate?: (history: HistoricalEvent[]) => void onHistoryUpdate?: (history: HistoricalEvent[]) => void
@@ -233,6 +234,7 @@ const result = await window.PAGE_AGENT_EXT.execute(
apiKey: 'your-api-key', apiKey: 'your-api-key',
model: 'gpt-5.2', model: 'gpt-5.2',
// includeInitialTab: false, // 设为 false 排除初始标签页 // includeInitialTab: false, // 设为 false 排除初始标签页
// experimentalIncludeAllTabs: true, // 控制窗口内所有非固定标签页
onStatusChange: status => console.log('状态变化:', status), onStatusChange: status => console.log('状态变化:', status),
onActivity: activity => console.log('活动:', activity), onActivity: activity => console.log('活动:', activity),
onHistoryUpdate: history => console.log('历史更新:', history) onHistoryUpdate: history => console.log('历史更新:', history)
@@ -248,6 +250,7 @@ const result = await window.PAGE_AGENT_EXT.execute(
apiKey: 'your-api-key', apiKey: 'your-api-key',
model: 'gpt-5.2', model: 'gpt-5.2',
// includeInitialTab: false, // Set to false to exclude initial tab // includeInitialTab: false, // Set to false to exclude initial tab
// experimentalIncludeAllTabs: true, // Control all unpinned tabs in the window
onStatusChange: status => console.log('Status change:', status), onStatusChange: status => console.log('Status change:', status),
onActivity: activity => console.log('Activity:', activity), onActivity: activity => console.log('Activity:', activity),
onHistoryUpdate: history => console.log('History update:', history) onHistoryUpdate: history => console.log('History update:', history)