Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c465fd8aa4 | |||
| 1eef785a61 | |||
| 0b4eb6b49a | |||
| cba54967b8 | |||
| 23dc3b7f7e | |||
| 49b137981c | |||
| cad033d63b | |||
| 312952ec41 | |||
| 52edd78cd4 | |||
| a7c9999d83 | |||
| 7f8ddde748 | |||
| e1fede1194 | |||
| 2322d6c46b | |||
| ac72f55731 | |||
| 5485e852bd | |||
| 48fb65d00b | |||
| 75bc69c0c0 | |||
| 0d9b9e4b9a | |||
| 78a31ef5d1 | |||
| 98005e323a | |||
| f3c253c044 | |||
| 02f63ec04d | |||
| 583f757f0a | |||
| 53c9149d06 | |||
| 64a90e3b3a | |||
| abfa903c9c | |||
| 6dd0ceab73 | |||
| eede819ea8 | |||
| ce3047428a | |||
| 3456f45590 | |||
| 4f71f065df | |||
| cd2d33a9f6 | |||
| 80e96d0b9e | |||
| 2c3b27e337 | |||
| b6691b4732 | |||
| a557a85d28 | |||
| eeb5b6a5af | |||
| ad31e2b750 | |||
| e98d80b6a0 | |||
| 93b1e52376 | |||
| 66a85c0dd3 | |||
| 50ce56a4f6 | |||
| 5aeb40abac | |||
| 8c06cdc898 | |||
| dfde89aa70 | |||
| 49b3316a5f | |||
| c35f367b39 | |||
| 3459836a14 | |||
| b7b5b6db30 | |||
| 05d16313c7 | |||
| a3a96d85d5 | |||
| c89042f142 | |||
| 2e18bd862d | |||
| 0bc47a997d | |||
| bcc7dfea2d | |||
| ba242d3a1b | |||
| fe82b11285 | |||
| bdf79b7c10 | |||
| 2f92a9cb32 |
+13
-171
@@ -2,42 +2,13 @@
|
||||
|
||||
♥️ We welcome contributions from everyone.
|
||||
|
||||
## 🚀 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.
|
||||
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).
|
||||
|
||||
## 🤝 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
|
||||
- Search existing issues before creating new ones
|
||||
@@ -46,147 +17,24 @@ This is a **monorepo** with npm workspaces containing **4 main packages**:
|
||||
|
||||
### Code Contributions
|
||||
|
||||
1. **Fork and Clone**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/your-username/page-agent.git
|
||||
cd page-agent
|
||||
```
|
||||
|
||||
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
|
||||
1. Follow existing code style and patterns
|
||||
2. Update documentation as needed
|
||||
3. Add JSDoc for public APIs
|
||||
4. Build and lint everything
|
||||
5. Test in our demo website, and on other websites if applicable
|
||||
6. Include screenshots for UI changes
|
||||
|
||||
### 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**.
|
||||
- We have a [website/AGENTS.md](packages/website/AGENTS.md) for that.
|
||||
- Vibe coding is **NOT** allowed for the core lib!!!
|
||||
- NEVER try to vibe coding the MV3 extension!!! It is HELL.
|
||||
- Make sure your AI references `AGENTS.md` and `website/AGENTS.md` for better quality.
|
||||
- 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
|
||||
|
||||
- Breaking changes and large PRs without prior discussion
|
||||
- Heavy dependencies to core libs
|
||||
- Contributions without proper testing
|
||||
- Code that doesn't follow project conventions
|
||||
- Dependencies or code with licenses incompatible with MIT
|
||||
- Bot or AI-generated pull requests without meaningful human involvement
|
||||
|
||||
@@ -194,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.
|
||||
|
||||
> 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! 🎉
|
||||
|
||||
@@ -25,15 +25,16 @@ The GUI Agent Living in Your Webpage. Control web interfaces with natural langua
|
||||
- **📖 Text-based DOM manipulation**
|
||||
- No screenshots. No multi-modal LLMs or special permissions needed.
|
||||
- **🧠 Bring your own LLMs**
|
||||
- **🎨 Pretty UI with human-in-the-loop**
|
||||
- **🐙 Optional [chrome extension](https://alibaba.github.io/page-agent/docs/features/chrome-extension) for multi-page tasks.**
|
||||
- And an [MCP Server (Beta)](https://alibaba.github.io/page-agent/docs/features/mcp-server) to control it from outside
|
||||
|
||||
## 💡 Use Cases
|
||||
|
||||
- **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.
|
||||
- **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
|
||||
|
||||
@@ -49,8 +50,8 @@ Fastest way to try PageAgent with our free Demo LLM:
|
||||
|
||||
| Mirrors | URL |
|
||||
| ------- | ---------------------------------------------------------------------------------- |
|
||||
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.5.10/dist/iife/page-agent.demo.js |
|
||||
| China | https://registry.npmmirror.com/page-agent/1.5.10/files/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.3/files/dist/iife/page-agent.demo.js |
|
||||
|
||||
### NPM Installation
|
||||
|
||||
@@ -75,11 +76,15 @@ For more programmatic usage, see [📖 Documentations](https://alibaba.github.io
|
||||
|
||||
## 🤝 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 [Code of Conduct](docs/CODE_OF_CONDUCT.md) before contributing.
|
||||
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, and bot accounts may be blocked.
|
||||
Contributions generated entirely by **bots or AI** without substantial human involvement will **not be accepted**.
|
||||
|
||||
## ⚖️ License
|
||||
|
||||
[MIT License](LICENSE)
|
||||
|
||||
## 👏 Acknowledgments
|
||||
|
||||
@@ -97,23 +102,18 @@ Licensed under the MIT License
|
||||
We gratefully acknowledge the browser-use project and its contributors for their
|
||||
excellent work on web automation and DOM interaction patterns that helped make
|
||||
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!**
|
||||
|
||||
<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>
|
||||
|
||||
+25
-36
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.6.0] - 2026-03-21
|
||||
|
||||
### Features
|
||||
|
||||
- **Beta MCP support** - New `@page-agent/mcp` package lets MCP clients such as Claude Desktop and Copilot control the browser through the Page Agent extension
|
||||
- **Better iframe handling** - Same-origin iframe elements are handled more reliably during DOM extraction and actions
|
||||
- **Extension history workflows** - Users can rerun past tasks, export history sessions as JSON, and approve MCP-triggered tasks before execution
|
||||
|
||||
### Improvements
|
||||
|
||||
- **Unified versioning across packages** - The extension now follows the root workspace version. Changelog entries are no longer split into a separate extension version section
|
||||
- **Configurable `stepDelay`** - Agent pacing between steps is now configurable via `stepDelay`
|
||||
- **Optional API key** - `apiKey` can now be omitted for compatible deployments that do not require one
|
||||
- **Optional named tool choice** - Tool invocation can disable named tool choice for providers that behave better without it
|
||||
- **Better rich-text input support** - Improved `contenteditable` handling with better event dispatching and `execCommand` fallback for more editors
|
||||
- **More flexible DOM extraction** - `includeAttributes` now supports wildcards, `contenteditable` is included by default, and heuristically interactive elements expose more useful attributes
|
||||
- **MiniMax model support** - Added MiniMax compatibility, with the default recommendation updated to `MiniMax-M2.7`
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed Safari issues when `requestIdleCallback` is unavailable
|
||||
- Avoid throwing when `webgl2` initialization fails
|
||||
- Improved OpenAI-compatible request patches for GPT-5.4 chat tools and MiniMax temperature/tool-call compatibility
|
||||
- Fixed several UI polish issues in the extension and website, including cursor and layout regressions
|
||||
|
||||
## [1.5.1] - 2026-03-05
|
||||
|
||||
### Breaking Changes
|
||||
@@ -153,42 +178,6 @@ PageAgent is now ready for production use. The API is stable and breaking change
|
||||
- **Ask User Tool** - Agent can ask users for clarification
|
||||
- **i18n Support** - English and Chinese localization
|
||||
|
||||
### Configuration
|
||||
|
||||
```typescript
|
||||
// Version 1.0.0
|
||||
interface PageAgentConfig {
|
||||
// LLM Configuration (required)
|
||||
baseURL: string
|
||||
apiKey: string
|
||||
model: string
|
||||
temperature?: number
|
||||
maxRetries?: number
|
||||
customFetch?: typeof fetch
|
||||
|
||||
// Agent Configuration
|
||||
language?: 'en-US' | 'zh-CN'
|
||||
maxSteps?: number // default: 20
|
||||
customTools?: Record<string, PageAgentTool> // experimental
|
||||
instructions?: InstructionsConfig
|
||||
transformPageContent?: (content: string) => string | Promise<string>
|
||||
experimentalScriptExecutionTool?: boolean // default: false
|
||||
|
||||
// Lifecycle Hooks (experimental)
|
||||
onBeforeTask?: (agent, result) => void
|
||||
onAfterTask?: (agent, result) => void
|
||||
onBeforeStep?: (agent, stepCount) => void
|
||||
onAfterStep?: (agent, history) => void
|
||||
onDispose?: (agent, reason?) => void
|
||||
|
||||
// Page Controller Configuration
|
||||
enableMask?: boolean // default: true
|
||||
viewportExpansion?: number
|
||||
interactiveBlacklist?: Element[]
|
||||
interactiveWhitelist?: Element[]
|
||||
}
|
||||
```
|
||||
|
||||
### Packages
|
||||
|
||||
| Package | Description |
|
||||
|
||||
+16
-24
@@ -20,20 +20,20 @@
|
||||
## ✨ Features
|
||||
|
||||
- **🎯 轻松集成**
|
||||
- 无需 `浏览器插件` / `Python` / `无头浏览器`。
|
||||
- 纯页面内 JavaScript,一切都在你的网页中完成。
|
||||
- 无需 `浏览器插件` / `Python` / `无头浏览器`,纯页面内 JavaScript
|
||||
- **📖 基于文本的 DOM 操作**
|
||||
- 无需截图,无需多模态模型或特殊权限。
|
||||
- **🧠 用你自己的 LLM**
|
||||
- **🎨 精美 UI,支持人机协同**
|
||||
- **🐙 可选的 [Chrome 扩展](https://alibaba.github.io/page-agent/docs/features/chrome-extension),支持跨页面任务。**
|
||||
- 无需截图,无需多模态模型或特殊权限
|
||||
- **🧠 自备 LLM**
|
||||
- 🐙 可选的 [Chrome 扩展](https://alibaba.github.io/page-agent/docs/features/chrome-extension),支持跨页面任务
|
||||
- [MCP Server (Beta)](https://alibaba.github.io/page-agent/docs/features/mcp-server)
|
||||
|
||||
## 💡 应用场景
|
||||
|
||||
- **SaaS AI 副驾驶** — 几行代码为你的产品加上 AI 副驾驶,无需重写后端。
|
||||
- **SaaS AI Copilot** — 几行代码为你的产品加上 AI 副驾驶,无需重写后端。
|
||||
- **智能表单填写** — 把 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 加入浏览器控制能力。
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
@@ -49,8 +49,8 @@
|
||||
|
||||
| Mirrors | URL |
|
||||
| ------- | ---------------------------------------------------------------------------------- |
|
||||
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.5.10/dist/iife/page-agent.demo.js |
|
||||
| China | https://registry.npmmirror.com/page-agent/1.5.10/files/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.3/files/dist/iife/page-agent.demo.js |
|
||||
|
||||
### NPM 安装
|
||||
|
||||
@@ -75,11 +75,13 @@ await agent.execute('点击登录按钮')
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
欢迎社区贡献!请参阅 [CONTRIBUTING.md](../CONTRIBUTING.md) 了解安装与贡献指南。请在贡献前阅读[行为准则](CODE_OF_CONDUCT.md)。
|
||||
欢迎社区贡献!请参阅 [CONTRIBUTING.md](../CONTRIBUTING.md) 了解安装与贡献指南。
|
||||
|
||||
我们不接受未经实质性人类参与、完全由 Bot 或 Agent 自动生成的代码,机器人账号可能被禁止参与互动。
|
||||
提交 issue 或 PR 之前,请先阅读[作者声明](https://github.com/alibaba/page-agent/issues/349)和[行为准则](CODE_OF_CONDUCT.md)。
|
||||
|
||||
## 👏 致谢
|
||||
我们不接受未经实质性人类参与、完全由 Bot 或 Agent 自动生成的代码。
|
||||
|
||||
## 👏 声明与致谢
|
||||
|
||||
本项目基于 **[`browser-use`](https://github.com/browser-use/browser-use)** 的优秀工作构建。
|
||||
|
||||
@@ -95,12 +97,9 @@ Licensed under the MIT License
|
||||
We gratefully acknowledge the browser-use project and its contributors for their
|
||||
excellent work on web automation and DOM interaction patterns that helped make
|
||||
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)
|
||||
|
||||
@@ -108,10 +107,3 @@ file and in the node_modules directory after installation.
|
||||
|
||||
**⭐ 如果觉得 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>
|
||||
|
||||
@@ -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.
|
||||
Generated
+242
-242
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "root",
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "root",
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"packages/page-controller",
|
||||
@@ -23,7 +23,7 @@
|
||||
"@commitlint/config-conventional": "^20.5.0",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@microsoft/api-extractor": "^7.57.7",
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
||||
"@types/node": "^25.5.0",
|
||||
"@vitejs/plugin-react-swc": "^4.3.0",
|
||||
@@ -41,7 +41,7 @@
|
||||
"lint-staged": "^16.4.0",
|
||||
"prettier": "^3.8.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.57.1",
|
||||
"typescript-eslint": "^8.57.2",
|
||||
"unplugin-dts": "^1.0.0-beta.6",
|
||||
"vite": "^7.3.1",
|
||||
"vite-bundle-analyzer": "^1.3.6",
|
||||
@@ -3352,49 +3352,49 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz",
|
||||
"integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz",
|
||||
"integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
"enhanced-resolve": "^5.19.0",
|
||||
"jiti": "^2.6.1",
|
||||
"lightningcss": "1.31.1",
|
||||
"lightningcss": "1.32.0",
|
||||
"magic-string": "^0.30.21",
|
||||
"source-map-js": "^1.2.1",
|
||||
"tailwindcss": "4.2.1"
|
||||
"tailwindcss": "4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz",
|
||||
"integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz",
|
||||
"integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tailwindcss/oxide-android-arm64": "4.2.1",
|
||||
"@tailwindcss/oxide-darwin-arm64": "4.2.1",
|
||||
"@tailwindcss/oxide-darwin-x64": "4.2.1",
|
||||
"@tailwindcss/oxide-freebsd-x64": "4.2.1",
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1",
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.2.1",
|
||||
"@tailwindcss/oxide-linux-arm64-musl": "4.2.1",
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "4.2.1",
|
||||
"@tailwindcss/oxide-linux-x64-musl": "4.2.1",
|
||||
"@tailwindcss/oxide-wasm32-wasi": "4.2.1",
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.2.1",
|
||||
"@tailwindcss/oxide-win32-x64-msvc": "4.2.1"
|
||||
"@tailwindcss/oxide-android-arm64": "4.2.2",
|
||||
"@tailwindcss/oxide-darwin-arm64": "4.2.2",
|
||||
"@tailwindcss/oxide-darwin-x64": "4.2.2",
|
||||
"@tailwindcss/oxide-freebsd-x64": "4.2.2",
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2",
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.2.2",
|
||||
"@tailwindcss/oxide-linux-arm64-musl": "4.2.2",
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "4.2.2",
|
||||
"@tailwindcss/oxide-linux-x64-musl": "4.2.2",
|
||||
"@tailwindcss/oxide-wasm32-wasi": "4.2.2",
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.2.2",
|
||||
"@tailwindcss/oxide-win32-x64-msvc": "4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-android-arm64": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz",
|
||||
"integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz",
|
||||
"integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3409,9 +3409,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz",
|
||||
"integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz",
|
||||
"integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3426,9 +3426,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz",
|
||||
"integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz",
|
||||
"integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3443,9 +3443,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz",
|
||||
"integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz",
|
||||
"integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3460,9 +3460,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz",
|
||||
"integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz",
|
||||
"integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -3477,9 +3477,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz",
|
||||
"integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz",
|
||||
"integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3494,9 +3494,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz",
|
||||
"integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz",
|
||||
"integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3511,9 +3511,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz",
|
||||
"integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz",
|
||||
"integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3528,9 +3528,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz",
|
||||
"integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz",
|
||||
"integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3545,9 +3545,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz",
|
||||
"integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz",
|
||||
"integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==",
|
||||
"bundleDependencies": [
|
||||
"@napi-rs/wasm-runtime",
|
||||
"@emnapi/core",
|
||||
@@ -3639,9 +3639,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz",
|
||||
"integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz",
|
||||
"integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3656,9 +3656,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz",
|
||||
"integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz",
|
||||
"integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3673,18 +3673,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz",
|
||||
"integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz",
|
||||
"integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tailwindcss/node": "4.2.1",
|
||||
"@tailwindcss/oxide": "4.2.1",
|
||||
"tailwindcss": "4.2.1"
|
||||
"@tailwindcss/node": "4.2.2",
|
||||
"@tailwindcss/oxide": "4.2.2",
|
||||
"tailwindcss": "4.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^5.2.0 || ^6 || ^7"
|
||||
"vite": "^5.2.0 || ^6 || ^7 || ^8"
|
||||
}
|
||||
},
|
||||
"node_modules/@trivago/prettier-plugin-sort-imports": {
|
||||
@@ -3807,9 +3807,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/chrome": {
|
||||
"version": "0.1.37",
|
||||
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.1.37.tgz",
|
||||
"integrity": "sha512-IJE4ceuDO7lrEuua7Pow47zwNcI8E6qqkowRP7aFPaZ0lrjxh6y836OPqqkIZeTX64FTogbw+4RNH0+QrweCTQ==",
|
||||
"version": "0.1.38",
|
||||
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.1.38.tgz",
|
||||
"integrity": "sha512-5aK4m9wZqoWAoB98aElESLm/5pXpqJnFWMNoiCs/XdPsXR6wNdVkJFSdQ9Wr4PnTuUrxD0SuNuDHh3EG5QeBzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3893,17 +3893,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.1.tgz",
|
||||
"integrity": "sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==",
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz",
|
||||
"integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.57.1",
|
||||
"@typescript-eslint/type-utils": "8.57.1",
|
||||
"@typescript-eslint/utils": "8.57.1",
|
||||
"@typescript-eslint/visitor-keys": "8.57.1",
|
||||
"@typescript-eslint/scope-manager": "8.57.2",
|
||||
"@typescript-eslint/type-utils": "8.57.2",
|
||||
"@typescript-eslint/utils": "8.57.2",
|
||||
"@typescript-eslint/visitor-keys": "8.57.2",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
@@ -3916,7 +3916,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.57.1",
|
||||
"@typescript-eslint/parser": "^8.57.2",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
@@ -3932,16 +3932,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.1.tgz",
|
||||
"integrity": "sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==",
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz",
|
||||
"integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.57.1",
|
||||
"@typescript-eslint/types": "8.57.1",
|
||||
"@typescript-eslint/typescript-estree": "8.57.1",
|
||||
"@typescript-eslint/visitor-keys": "8.57.1",
|
||||
"@typescript-eslint/scope-manager": "8.57.2",
|
||||
"@typescript-eslint/types": "8.57.2",
|
||||
"@typescript-eslint/typescript-estree": "8.57.2",
|
||||
"@typescript-eslint/visitor-keys": "8.57.2",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3957,14 +3957,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.1.tgz",
|
||||
"integrity": "sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==",
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz",
|
||||
"integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.57.1",
|
||||
"@typescript-eslint/types": "^8.57.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.57.2",
|
||||
"@typescript-eslint/types": "^8.57.2",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3979,14 +3979,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.1.tgz",
|
||||
"integrity": "sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==",
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz",
|
||||
"integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.57.1",
|
||||
"@typescript-eslint/visitor-keys": "8.57.1"
|
||||
"@typescript-eslint/types": "8.57.2",
|
||||
"@typescript-eslint/visitor-keys": "8.57.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -3997,9 +3997,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.1.tgz",
|
||||
"integrity": "sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==",
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz",
|
||||
"integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -4014,15 +4014,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.1.tgz",
|
||||
"integrity": "sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==",
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz",
|
||||
"integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.57.1",
|
||||
"@typescript-eslint/typescript-estree": "8.57.1",
|
||||
"@typescript-eslint/utils": "8.57.1",
|
||||
"@typescript-eslint/types": "8.57.2",
|
||||
"@typescript-eslint/typescript-estree": "8.57.2",
|
||||
"@typescript-eslint/utils": "8.57.2",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
},
|
||||
@@ -4039,9 +4039,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.1.tgz",
|
||||
"integrity": "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==",
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz",
|
||||
"integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -4053,16 +4053,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.1.tgz",
|
||||
"integrity": "sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==",
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz",
|
||||
"integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.57.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.57.1",
|
||||
"@typescript-eslint/types": "8.57.1",
|
||||
"@typescript-eslint/visitor-keys": "8.57.1",
|
||||
"@typescript-eslint/project-service": "8.57.2",
|
||||
"@typescript-eslint/tsconfig-utils": "8.57.2",
|
||||
"@typescript-eslint/types": "8.57.2",
|
||||
"@typescript-eslint/visitor-keys": "8.57.2",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -4081,16 +4081,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.1.tgz",
|
||||
"integrity": "sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==",
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz",
|
||||
"integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.57.1",
|
||||
"@typescript-eslint/types": "8.57.1",
|
||||
"@typescript-eslint/typescript-estree": "8.57.1"
|
||||
"@typescript-eslint/scope-manager": "8.57.2",
|
||||
"@typescript-eslint/types": "8.57.2",
|
||||
"@typescript-eslint/typescript-estree": "8.57.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -4105,13 +4105,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.57.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.1.tgz",
|
||||
"integrity": "sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==",
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz",
|
||||
"integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.57.1",
|
||||
"@typescript-eslint/types": "8.57.2",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4237,9 +4237,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@wxt-dev/browser": {
|
||||
"version": "0.1.37",
|
||||
"resolved": "https://registry.npmjs.org/@wxt-dev/browser/-/browser-0.1.37.tgz",
|
||||
"integrity": "sha512-I32XWCNRy2W6UgbaVXz8BHGBGtm8urGRRBrcNLagUBXTrBi7wCE6zWePUvvK+nUl7qUCZ7iQ1ufdP0c1DEWisw==",
|
||||
"version": "0.1.38",
|
||||
"resolved": "https://registry.npmjs.org/@wxt-dev/browser/-/browser-0.1.38.tgz",
|
||||
"integrity": "sha512-Y9nUfNOMqgsoO8KQ1BssrwzHEmrSr/2pUowAG4Wcr9EyKyhOK7mC7Vdyj2kXAmp5NOUXHjhghzJ6qIb5h+RbCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -5851,9 +5851,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.19.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
|
||||
"integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==",
|
||||
"version": "5.20.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz",
|
||||
"integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -6676,13 +6676,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "12.37.0",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.37.0.tgz",
|
||||
"integrity": "sha512-j/QUcZS9Nw3NzZWoAbkzr3ETRFHyVeQMlGOUYUmG15U+uiyn9DqIktYruVPDcqY8I35qYR70JaZBvFmS6p+Pdg==",
|
||||
"version": "12.38.0",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz",
|
||||
"integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-dom": "^12.37.0",
|
||||
"motion-dom": "^12.38.0",
|
||||
"motion-utils": "^12.36.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
@@ -7821,9 +7821,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.31.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
|
||||
"integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==",
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
||||
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
@@ -7837,23 +7837,23 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"lightningcss-android-arm64": "1.31.1",
|
||||
"lightningcss-darwin-arm64": "1.31.1",
|
||||
"lightningcss-darwin-x64": "1.31.1",
|
||||
"lightningcss-freebsd-x64": "1.31.1",
|
||||
"lightningcss-linux-arm-gnueabihf": "1.31.1",
|
||||
"lightningcss-linux-arm64-gnu": "1.31.1",
|
||||
"lightningcss-linux-arm64-musl": "1.31.1",
|
||||
"lightningcss-linux-x64-gnu": "1.31.1",
|
||||
"lightningcss-linux-x64-musl": "1.31.1",
|
||||
"lightningcss-win32-arm64-msvc": "1.31.1",
|
||||
"lightningcss-win32-x64-msvc": "1.31.1"
|
||||
"lightningcss-android-arm64": "1.32.0",
|
||||
"lightningcss-darwin-arm64": "1.32.0",
|
||||
"lightningcss-darwin-x64": "1.32.0",
|
||||
"lightningcss-freebsd-x64": "1.32.0",
|
||||
"lightningcss-linux-arm-gnueabihf": "1.32.0",
|
||||
"lightningcss-linux-arm64-gnu": "1.32.0",
|
||||
"lightningcss-linux-arm64-musl": "1.32.0",
|
||||
"lightningcss-linux-x64-gnu": "1.32.0",
|
||||
"lightningcss-linux-x64-musl": "1.32.0",
|
||||
"lightningcss-win32-arm64-msvc": "1.32.0",
|
||||
"lightningcss-win32-x64-msvc": "1.32.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-android-arm64": {
|
||||
"version": "1.31.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz",
|
||||
"integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==",
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
|
||||
"integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -7872,9 +7872,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-arm64": {
|
||||
"version": "1.31.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz",
|
||||
"integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==",
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
|
||||
"integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -7893,9 +7893,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-x64": {
|
||||
"version": "1.31.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz",
|
||||
"integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==",
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
|
||||
"integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -7914,9 +7914,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-freebsd-x64": {
|
||||
"version": "1.31.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz",
|
||||
"integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==",
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
|
||||
"integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -7935,9 +7935,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
||||
"version": "1.31.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz",
|
||||
"integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==",
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
|
||||
"integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -7956,9 +7956,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-gnu": {
|
||||
"version": "1.31.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz",
|
||||
"integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==",
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
|
||||
"integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -7977,9 +7977,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-musl": {
|
||||
"version": "1.31.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz",
|
||||
"integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==",
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
|
||||
"integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -7998,9 +7998,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-gnu": {
|
||||
"version": "1.31.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz",
|
||||
"integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==",
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
|
||||
"integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -8019,9 +8019,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-musl": {
|
||||
"version": "1.31.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz",
|
||||
"integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==",
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
|
||||
"integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -8040,9 +8040,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-arm64-msvc": {
|
||||
"version": "1.31.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz",
|
||||
"integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==",
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
|
||||
"integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -8061,9 +8061,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-x64-msvc": {
|
||||
"version": "1.31.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz",
|
||||
"integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==",
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
|
||||
"integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -8296,9 +8296,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.577.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.577.0.tgz",
|
||||
"integrity": "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.0.1.tgz",
|
||||
"integrity": "sha512-lih7tKEczCYOQjVEzpFuxEuNzlwf+1yhvlMlEkGWJM3va8Pugv8bYXc/pRtcjPncaP7k84X0Pt/71ufxvqEPtQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
@@ -8521,13 +8521,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/motion": {
|
||||
"version": "12.37.0",
|
||||
"resolved": "https://registry.npmjs.org/motion/-/motion-12.37.0.tgz",
|
||||
"integrity": "sha512-Ph6oyO5hGSIAPjDsqwchEP+EKXjyFK0ci6FTIFBbx+qaMl8zLzLzPLzd9q3DKhAHcvnV7LxQonMyA+FyAv9+gA==",
|
||||
"version": "12.38.0",
|
||||
"resolved": "https://registry.npmjs.org/motion/-/motion-12.38.0.tgz",
|
||||
"integrity": "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"framer-motion": "^12.37.0",
|
||||
"framer-motion": "^12.38.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -8548,9 +8548,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/motion-dom": {
|
||||
"version": "12.37.0",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.37.0.tgz",
|
||||
"integrity": "sha512-LnppZuwX1jQizRWTl9LBLMN3RbAEmdQkX/2Af0UW70NCqYJI/7GfI83vQP9Ucel/Avc0Tf2ZWy8FHawuc0O6Vg==",
|
||||
"version": "12.38.0",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz",
|
||||
"integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -10114,9 +10114,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/simple-icons": {
|
||||
"version": "16.12.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-16.12.0.tgz",
|
||||
"integrity": "sha512-fDJDqXUpkb2twqH+eBQpJsCYUE6jEH7VkuuPL9dH16sbLf6KKnwyijULmcx7SCoy3c2L6pl8WCzt+4rpYjoWfw==",
|
||||
"version": "16.13.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-16.13.0.tgz",
|
||||
"integrity": "sha512-N4AMZvFERU5YLEtUudtUesiM2H4O5xQ9qfS3K0oOV5II5KVtxOUAlmZ7KqBgiTSGBgCVkuLD/Z9dJKBtnI3kKQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -10424,16 +10424,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz",
|
||||
"integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
|
||||
"integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
|
||||
"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz",
|
||||
"integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -10683,16 +10683,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.57.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.1.tgz",
|
||||
"integrity": "sha512-fLvZWf+cAGw3tqMCYzGIU6yR8K+Y9NT2z23RwOjlNFF2HwSB3KhdEFI5lSBv8tNmFkkBShSjsCjzx1vahZfISA==",
|
||||
"version": "8.57.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.2.tgz",
|
||||
"integrity": "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.57.1",
|
||||
"@typescript-eslint/parser": "8.57.1",
|
||||
"@typescript-eslint/typescript-estree": "8.57.1",
|
||||
"@typescript-eslint/utils": "8.57.1"
|
||||
"@typescript-eslint/eslint-plugin": "8.57.2",
|
||||
"@typescript-eslint/parser": "8.57.2",
|
||||
"@typescript-eslint/typescript-estree": "8.57.2",
|
||||
"@typescript-eslint/utils": "8.57.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -11451,9 +11451,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||
"version": "8.20.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
|
||||
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
@@ -11489,9 +11489,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wxt": {
|
||||
"version": "0.20.19",
|
||||
"resolved": "https://registry.npmjs.org/wxt/-/wxt-0.20.19.tgz",
|
||||
"integrity": "sha512-LNQXDyStuenNSLLbSs3aXDscKB6g6NYUXppBu7uAmIUZNKLy04Hyg3EE9p9w683t0B+j2CBYciDmqglfwisNuA==",
|
||||
"version": "0.20.20",
|
||||
"resolved": "https://registry.npmjs.org/wxt/-/wxt-0.20.20.tgz",
|
||||
"integrity": "sha512-OGvOD1YEXwasjlOmfYzCGlIa88Jm9mxjM+hqx7zw+Xctg+TKjhF1bIt7vVJ1oT1t4RqvczTAcD2mUduiDltZaw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -11500,7 +11500,7 @@
|
||||
"@webext-core/fake-browser": "^1.3.4",
|
||||
"@webext-core/isolated-element": "^1.1.3",
|
||||
"@webext-core/match-patterns": "^1.0.3",
|
||||
"@wxt-dev/browser": "^0.1.37",
|
||||
"@wxt-dev/browser": "^0.1.38",
|
||||
"@wxt-dev/storage": "^1.0.0",
|
||||
"async-mutex": "^0.5.0",
|
||||
"c12": "^3.3.3",
|
||||
@@ -11537,7 +11537,7 @@
|
||||
"tinyglobby": "^0.2.15",
|
||||
"unimport": "^3.13.1 || ^4.0.0 || ^5.0.0 || ^6.0.0",
|
||||
"vite": "^5.4.19 || ^6.3.4 || ^7.0.0 || ^8.0.0-0",
|
||||
"vite-node": "^3.2.4 || ^5.0.0",
|
||||
"vite-node": "^3.2.4 || ^5.0.0 || ^6.0.0",
|
||||
"web-ext-run": "^0.2.4"
|
||||
},
|
||||
"bin": {
|
||||
@@ -11799,11 +11799,11 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@page-agent/core",
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@page-agent/llms": "1.5.10",
|
||||
"@page-agent/page-controller": "1.5.10",
|
||||
"@page-agent/llms": "1.6.3",
|
||||
"@page-agent/page-controller": "1.6.3",
|
||||
"chalk": "^5.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -11815,13 +11815,13 @@
|
||||
},
|
||||
"packages/extension": {
|
||||
"name": "@page-agent/ext",
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@page-agent/core": "1.5.10",
|
||||
"@page-agent/llms": "1.5.10",
|
||||
"@page-agent/page-controller": "1.5.10",
|
||||
"@page-agent/ui": "1.5.10",
|
||||
"@page-agent/core": "1.6.3",
|
||||
"@page-agent/llms": "1.6.3",
|
||||
"@page-agent/page-controller": "1.6.3",
|
||||
"@page-agent/ui": "1.6.3",
|
||||
"ai-motion": "^0.4.8",
|
||||
"chalk": "^5.6.2"
|
||||
},
|
||||
@@ -11832,25 +11832,25 @@
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-switch": "^1.2.6",
|
||||
"@types/chrome": "^0.1.37",
|
||||
"@types/chrome": "^0.1.38",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.1",
|
||||
"@wxt-dev/module-react": "^1.2.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"idb": "^8.0.3",
|
||||
"lucide-react": "^0.577.0",
|
||||
"motion": "^12.37.0",
|
||||
"lucide-react": "^1.0.1",
|
||||
"motion": "^12.38.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"rough-notation": "^0.5.1",
|
||||
"simple-icons": "^16.12.0",
|
||||
"simple-icons": "^16.13.0",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"wxt": "^0.20.19"
|
||||
"wxt": "^0.20.20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
@@ -11858,7 +11858,7 @@
|
||||
},
|
||||
"packages/llms": {
|
||||
"name": "@page-agent/llms",
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "^5.6.2"
|
||||
@@ -11872,11 +11872,11 @@
|
||||
},
|
||||
"packages/mcp": {
|
||||
"name": "@page-agent/mcp",
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"ws": "^8.19.0",
|
||||
"ws": "^8.20.0",
|
||||
"zod": "^4.3.5"
|
||||
},
|
||||
"bin": {
|
||||
@@ -11887,13 +11887,13 @@
|
||||
}
|
||||
},
|
||||
"packages/page-agent": {
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@page-agent/core": "1.5.10",
|
||||
"@page-agent/llms": "1.5.10",
|
||||
"@page-agent/page-controller": "1.5.10",
|
||||
"@page-agent/ui": "1.5.10",
|
||||
"@page-agent/core": "1.6.3",
|
||||
"@page-agent/llms": "1.6.3",
|
||||
"@page-agent/page-controller": "1.6.3",
|
||||
"@page-agent/ui": "1.6.3",
|
||||
"chalk": "^5.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -11905,7 +11905,7 @@
|
||||
},
|
||||
"packages/page-controller": {
|
||||
"name": "@page-agent/page-controller",
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ai-motion": "^0.4.8"
|
||||
@@ -11913,12 +11913,12 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@page-agent/ui",
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"license": "MIT"
|
||||
},
|
||||
"packages/website": {
|
||||
"name": "@page-agent/website",
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"devDependencies": {
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
@@ -11929,13 +11929,13 @@
|
||||
"@types/react-dom": "^19.2.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.577.0",
|
||||
"motion": "^12.37.0",
|
||||
"lucide-react": "^1.0.1",
|
||||
"motion": "^12.38.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"rough-notation": "^0.5.1",
|
||||
"simple-icons": "^16.12.0",
|
||||
"simple-icons": "^16.13.0",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss": "^4.1.14",
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "root",
|
||||
"private": true,
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"packages/page-controller",
|
||||
@@ -42,7 +42,7 @@
|
||||
"@commitlint/config-conventional": "^20.5.0",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@microsoft/api-extractor": "^7.57.7",
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
||||
"@types/node": "^25.5.0",
|
||||
"@vitejs/plugin-react-swc": "^4.3.0",
|
||||
@@ -60,7 +60,7 @@
|
||||
"lint-staged": "^16.4.0",
|
||||
"prettier": "^3.8.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.57.1",
|
||||
"typescript-eslint": "^8.57.2",
|
||||
"unplugin-dts": "^1.0.0-beta.6",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-css-injected-by-js": "^4.0.1",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@page-agent/core",
|
||||
"private": false,
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"type": "module",
|
||||
"main": "./dist/esm/page-agent-core.js",
|
||||
"module": "./dist/esm/page-agent-core.js",
|
||||
@@ -44,8 +44,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^5.6.2",
|
||||
"@page-agent/llms": "1.5.10",
|
||||
"@page-agent/page-controller": "1.5.10"
|
||||
"@page-agent/llms": "1.6.3",
|
||||
"@page-agent/page-controller": "1.6.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
|
||||
@@ -115,12 +115,17 @@ import type {
|
||||
|
||||
export interface ExecuteConfig {
|
||||
baseURL: string
|
||||
apiKey: string
|
||||
model: string
|
||||
apiKey?: string
|
||||
|
||||
// Include the initial tab where page JS starts. Default: true.
|
||||
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
|
||||
onActivity?: (activity: AgentActivity) => void
|
||||
onHistoryUpdate?: (history: HistoricalEvent[]) => void
|
||||
@@ -205,9 +210,10 @@ import type {
|
||||
|
||||
interface ExecuteConfig {
|
||||
baseURL: string
|
||||
apiKey: string
|
||||
model: string
|
||||
apiKey?: string
|
||||
includeInitialTab?: boolean
|
||||
experimentalIncludeAllTabs?: boolean
|
||||
onStatusChange?: (status: AgentStatus) => void
|
||||
onActivity?: (activity: AgentActivity) => void
|
||||
onHistoryUpdate?: (history: HistoricalEvent[]) => void
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@page-agent/ext",
|
||||
"private": true,
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
@@ -16,31 +16,31 @@
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-switch": "^1.2.6",
|
||||
"@types/chrome": "^0.1.37",
|
||||
"@types/chrome": "^0.1.38",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.1",
|
||||
"@wxt-dev/module-react": "^1.2.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"idb": "^8.0.3",
|
||||
"lucide-react": "^0.577.0",
|
||||
"motion": "^12.37.0",
|
||||
"lucide-react": "^1.0.1",
|
||||
"motion": "^12.38.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"rough-notation": "^0.5.1",
|
||||
"simple-icons": "^16.12.0",
|
||||
"simple-icons": "^16.13.0",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"wxt": "^0.20.19"
|
||||
"wxt": "^0.20.20"
|
||||
},
|
||||
"dependencies": {
|
||||
"@page-agent/core": "1.5.10",
|
||||
"@page-agent/llms": "1.5.10",
|
||||
"@page-agent/page-controller": "1.5.10",
|
||||
"@page-agent/ui": "1.5.10",
|
||||
"@page-agent/core": "1.6.3",
|
||||
"@page-agent/llms": "1.6.3",
|
||||
"@page-agent/page-controller": "1.6.3",
|
||||
"@page-agent/ui": "1.6.3",
|
||||
"ai-motion": "^0.4.8",
|
||||
"chalk": "^5.6.2"
|
||||
},
|
||||
|
||||
@@ -11,13 +11,18 @@ function detectLanguage(): 'en-US' | 'zh-CN' {
|
||||
return lang.startsWith('zh') ? 'zh-CN' : 'en-US'
|
||||
}
|
||||
|
||||
interface MultiPageAgentConfig extends AgentConfig {
|
||||
includeInitialTab?: boolean
|
||||
experimentalIncludeAllTabs?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* MultiPageAgent
|
||||
* - use with extension
|
||||
* - can be used from a side panel or a content script
|
||||
*/
|
||||
export class MultiPageAgent extends PageAgentCore {
|
||||
constructor(config: AgentConfig & { includeInitialTab?: boolean }) {
|
||||
constructor(config: MultiPageAgentConfig) {
|
||||
// multi page controller
|
||||
const tabsController = new TabsController()
|
||||
const pageController = new RemotePageController(tabsController)
|
||||
@@ -31,8 +36,8 @@ export class MultiPageAgent extends PageAgentCore {
|
||||
`Default working language: **${targetLanguage}**`
|
||||
)
|
||||
|
||||
// include initial tab for controlling
|
||||
const includeInitialTab = config.includeInitialTab ?? true
|
||||
const experimentalIncludeAllTabs = config.experimentalIncludeAllTabs ?? false
|
||||
|
||||
/**
|
||||
* When the agent is in side-panel and user closed the side-panel.
|
||||
@@ -50,7 +55,7 @@ export class MultiPageAgent extends PageAgentCore {
|
||||
customSystemPrompt: systemPrompt,
|
||||
|
||||
onBeforeTask: async (agent) => {
|
||||
await tabsController.init(agent.task, includeInitialTab)
|
||||
await tabsController.init(agent.task, { includeInitialTab, experimentalIncludeAllTabs })
|
||||
|
||||
heartBeatInterval = window.setInterval(() => {
|
||||
chrome.storage.local.set({
|
||||
|
||||
@@ -10,9 +10,7 @@ export function handlePageControlMessage(
|
||||
): true | undefined {
|
||||
const PREFIX = '[RemotePageController.background]'
|
||||
|
||||
function debug(...messages: any[]) {
|
||||
console.debug(`\x1b[90m${PREFIX}\x1b[0m`, ...messages)
|
||||
}
|
||||
const debug = console.debug.bind(console, `\x1b[90m${PREFIX}\x1b[0m`)
|
||||
|
||||
const { action, payload, targetTabId } = message
|
||||
|
||||
|
||||
@@ -4,9 +4,7 @@ import type { TabsController } from './TabsController'
|
||||
|
||||
const PREFIX = '[RemotePageController]'
|
||||
|
||||
function debug(...messages: any[]) {
|
||||
console.debug(`\x1b[90m${PREFIX}\x1b[0m`, ...messages)
|
||||
}
|
||||
const debug = console.debug.bind(console, `\x1b[90m${PREFIX}\x1b[0m`)
|
||||
|
||||
function sendMessage(message: {
|
||||
type: 'PAGE_CONTROL'
|
||||
|
||||
@@ -5,9 +5,7 @@ import type { TabAction } from './TabsController'
|
||||
|
||||
const PREFIX = '[TabsController.background]'
|
||||
|
||||
function debug(...messages: any[]) {
|
||||
console.debug(`\x1b[90m${PREFIX}\x1b[0m`, ...messages)
|
||||
}
|
||||
const debug = console.debug.bind(console, `\x1b[90m${PREFIX}\x1b[0m`)
|
||||
|
||||
export function handleTabControlMessage(
|
||||
message: { type: 'TAB_CONTROL'; action: TabAction; payload: any },
|
||||
@@ -20,11 +18,10 @@ export function handleTabControlMessage(
|
||||
case 'get_active_tab': {
|
||||
debug('get_active_tab')
|
||||
chrome.tabs
|
||||
.query({ active: true, currentWindow: true })
|
||||
.query({ active: true })
|
||||
.then((tabs) => {
|
||||
const tabId = tabs.length > 0 ? tabs[0].id || null : null
|
||||
debug('get_active_tab: success', tabId)
|
||||
sendResponse({ success: true, tabId })
|
||||
debug('get_active_tab: success', tabs)
|
||||
sendResponse({ success: true, tab: tabs[0] })
|
||||
})
|
||||
.catch((error) => {
|
||||
sendResponse({ error: error instanceof Error ? error.message : String(error) })
|
||||
@@ -63,7 +60,7 @@ export function handleTabControlMessage(
|
||||
case 'create_tab_group': {
|
||||
debug('create_tab_group', payload)
|
||||
chrome.tabs
|
||||
.group({ tabIds: payload.tabIds })
|
||||
.group({ tabIds: payload.tabIds, createProperties: { windowId: payload.windowId } })
|
||||
.then((groupId) => {
|
||||
debug('create_tab_group: success', groupId)
|
||||
sendResponse({ success: true, groupId })
|
||||
@@ -114,47 +111,59 @@ export function handleTabControlMessage(
|
||||
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:
|
||||
sendResponse({ error: `Unknown action: ${action}` })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
export function setupTabChangeEvents() {
|
||||
console.log('[TabsController.background] setupTabChangeEvents')
|
||||
const tabEventPorts = new Set<chrome.runtime.Port>()
|
||||
|
||||
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) => {
|
||||
debug('onCreated', tab)
|
||||
chrome.runtime
|
||||
.sendMessage({ type: 'TAB_CHANGE', action: 'created', payload: { tab } })
|
||||
.catch((error) => {
|
||||
debug('onCreated error:', error)
|
||||
})
|
||||
broadcastTabEvent({ action: 'created', payload: { tab } })
|
||||
})
|
||||
|
||||
chrome.tabs.onRemoved.addListener((tabId, removeInfo) => {
|
||||
debug('onRemoved', tabId, removeInfo)
|
||||
chrome.runtime
|
||||
.sendMessage({
|
||||
type: 'TAB_CHANGE',
|
||||
action: 'removed',
|
||||
payload: { tabId, removeInfo },
|
||||
})
|
||||
.catch((error) => {
|
||||
debug('onRemoved error:', error)
|
||||
})
|
||||
broadcastTabEvent({ action: 'removed', payload: { tabId, removeInfo } })
|
||||
})
|
||||
|
||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
debug('onUpdated', tabId, changeInfo)
|
||||
chrome.runtime
|
||||
.sendMessage({
|
||||
type: 'TAB_CHANGE',
|
||||
action: 'updated',
|
||||
payload: { tabId, changeInfo, tab },
|
||||
})
|
||||
.catch((error) => {
|
||||
debug('onUpdated error:', error)
|
||||
})
|
||||
broadcastTabEvent({ action: 'updated', payload: { tabId, changeInfo, tab } })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@ import { isContentScriptAllowed } from './RemotePageController'
|
||||
|
||||
const PREFIX = '[TabsController]'
|
||||
|
||||
function debug(...messages: any[]) {
|
||||
console.debug(`\x1b[90m${PREFIX}\x1b[0m`, ...messages)
|
||||
}
|
||||
const debug = console.debug.bind(console, `\x1b[90m${PREFIX}\x1b[0m`)
|
||||
|
||||
function sendMessage(message: {
|
||||
type: 'TAB_CONTROL'
|
||||
@@ -22,46 +20,91 @@ function sendMessage(message: {
|
||||
* - live in the agent env (extension page or content script)
|
||||
* - no chrome apis. call sw for tab operations
|
||||
*/
|
||||
export class TabsController extends EventTarget {
|
||||
export class TabsController {
|
||||
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 initialTabId: number | null = null
|
||||
private tabGroupId: number | null = null
|
||||
private experimentalIncludeAllTabs = false
|
||||
private task: string = ''
|
||||
|
||||
async init(task: string, includeInitialTab: boolean = true) {
|
||||
debug('init', task, includeInitialTab)
|
||||
async init(task: string, options: TabsInitOptions = {}) {
|
||||
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.disposed = false
|
||||
this.port = null
|
||||
this.portRetries = 0
|
||||
|
||||
this.windowId = null
|
||||
this.tabs = []
|
||||
this.tabGroupId = null
|
||||
this.initialTabId = null
|
||||
this.experimentalIncludeAllTabs = experimentalIncludeAllTabs
|
||||
this.task = task
|
||||
|
||||
const result = await sendMessage({
|
||||
const activeTabResult = await sendMessage({
|
||||
type: 'TAB_CONTROL',
|
||||
action: 'get_active_tab',
|
||||
})
|
||||
|
||||
this.initialTabId = result.tabId
|
||||
this.initialTabId = activeTabResult.tab?.id
|
||||
this.windowId = activeTabResult.tab?.windowId
|
||||
|
||||
if (!this.initialTabId) {
|
||||
throw new Error('Failed to get initial tab ID')
|
||||
if (!this.initialTabId || !this.windowId) {
|
||||
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({
|
||||
type: 'TAB_CONTROL',
|
||||
action: 'get_tab_info',
|
||||
payload: { tabId: this.initialTabId },
|
||||
})
|
||||
|
||||
if (isContentScriptAllowed(info.url)) {
|
||||
if (isContentScriptAllowed(info.url) && !info.pinned) {
|
||||
this.currentTabId = this.initialTabId
|
||||
|
||||
this.tabs.push({
|
||||
id: result.tabId,
|
||||
this.addTab({
|
||||
id: this.initialTabId,
|
||||
isInitial: true,
|
||||
url: info.url,
|
||||
title: info.title,
|
||||
@@ -73,52 +116,6 @@ export class TabsController extends EventTarget {
|
||||
}
|
||||
|
||||
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> {
|
||||
@@ -136,7 +133,7 @@ export class TabsController extends EventTarget {
|
||||
|
||||
const tabId = result.tabId as number
|
||||
|
||||
this.tabs.push({
|
||||
this.addTab({
|
||||
id: tabId,
|
||||
isInitial: false,
|
||||
})
|
||||
@@ -209,7 +206,7 @@ export class TabsController extends EventTarget {
|
||||
const result = await sendMessage({
|
||||
type: 'TAB_CONTROL',
|
||||
action: 'create_tab_group',
|
||||
payload: { tabIds },
|
||||
payload: { tabIds, windowId: this.windowId },
|
||||
})
|
||||
|
||||
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) {
|
||||
debug('updateCurrentTabId', tabId)
|
||||
|
||||
@@ -288,9 +290,77 @@ export class TabsController extends EventTarget {
|
||||
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 =
|
||||
@@ -302,6 +372,7 @@ export type TabAction =
|
||||
| 'add_tab_to_group'
|
||||
| 'close_tab'
|
||||
| 'get_tab_title'
|
||||
| 'get_window_tabs'
|
||||
|
||||
interface TabMeta {
|
||||
id: number
|
||||
|
||||
@@ -3,12 +3,12 @@ import type { LLMConfig } from '@page-agent/llms'
|
||||
// Demo LLM for testing
|
||||
export const DEMO_MODEL = 'qwen3.5-plus'
|
||||
export const DEMO_BASE_URL = 'https://page-ag-testing-ohftxirgbn.cn-shanghai.fcapp.run'
|
||||
export const DEMO_API_KEY = 'NA'
|
||||
// export const DEMO_API_KEY = 'NA'
|
||||
|
||||
export const DEMO_CONFIG: LLMConfig = {
|
||||
apiKey: DEMO_API_KEY,
|
||||
baseURL: DEMO_BASE_URL,
|
||||
model: DEMO_MODEL,
|
||||
// apiKey: DEMO_API_KEY,
|
||||
}
|
||||
|
||||
/** Legacy testing endpoints that should be auto-migrated to DEMO_BASE_URL */
|
||||
|
||||
@@ -21,6 +21,8 @@ export interface AdvancedConfig {
|
||||
maxSteps?: number
|
||||
systemInstruction?: string
|
||||
experimentalLlmsTxt?: boolean
|
||||
experimentalIncludeAllTabs?: boolean
|
||||
disableNamedToolChoice?: boolean
|
||||
}
|
||||
|
||||
export interface ExtConfig extends LLMConfig, AdvancedConfig {
|
||||
@@ -124,6 +126,8 @@ export function useAgent(): UseAgentResult {
|
||||
maxSteps,
|
||||
systemInstruction,
|
||||
experimentalLlmsTxt,
|
||||
experimentalIncludeAllTabs,
|
||||
disableNamedToolChoice,
|
||||
...llmConfig
|
||||
}: ExtConfig) => {
|
||||
await chrome.storage.local.set({ llmConfig })
|
||||
@@ -132,7 +136,13 @@ export function useAgent(): UseAgentResult {
|
||||
} else {
|
||||
await chrome.storage.local.remove('language')
|
||||
}
|
||||
const advancedConfig: AdvancedConfig = { maxSteps, systemInstruction, experimentalLlmsTxt }
|
||||
const advancedConfig: AdvancedConfig = {
|
||||
maxSteps,
|
||||
systemInstruction,
|
||||
experimentalLlmsTxt,
|
||||
experimentalIncludeAllTabs,
|
||||
disableNamedToolChoice,
|
||||
}
|
||||
await chrome.storage.local.set({ advancedConfig })
|
||||
setConfig({ ...llmConfig, ...advancedConfig, language })
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import { useEffect, useState } from 'react'
|
||||
import { siGithub } from 'simple-icons'
|
||||
|
||||
import { DEMO_API_KEY, DEMO_BASE_URL, DEMO_MODEL, isTestingEndpoint } from '@/agent/constants'
|
||||
import { DEMO_BASE_URL, DEMO_MODEL, isTestingEndpoint } from '@/agent/constants'
|
||||
import type { ExtConfig, LanguagePreference } from '@/agent/useAgent'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
@@ -27,30 +27,38 @@ interface ConfigPanelProps {
|
||||
}
|
||||
|
||||
export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
||||
const [apiKey, setApiKey] = useState(config?.apiKey || DEMO_API_KEY)
|
||||
const [baseURL, setBaseURL] = useState(config?.baseURL || DEMO_BASE_URL)
|
||||
const [model, setModel] = useState(config?.model || DEMO_MODEL)
|
||||
const [apiKey, setApiKey] = useState(config?.apiKey)
|
||||
const [language, setLanguage] = useState<LanguagePreference>(config?.language)
|
||||
const [maxSteps, setMaxSteps] = useState<number | undefined>(config?.maxSteps)
|
||||
const [maxSteps, setMaxSteps] = useState(config?.maxSteps)
|
||||
const [systemInstruction, setSystemInstruction] = useState(config?.systemInstruction ?? '')
|
||||
const [experimentalLlmsTxt, setExperimentalLlmsTxt] = useState(
|
||||
config?.experimentalLlmsTxt ?? false
|
||||
)
|
||||
const [experimentalIncludeAllTabs, setExperimentalIncludeAllTabs] = useState(
|
||||
config?.experimentalIncludeAllTabs ?? false
|
||||
)
|
||||
const [disableNamedToolChoice, setDisableNamedToolChoice] = useState(
|
||||
config?.disableNamedToolChoice ?? false
|
||||
)
|
||||
const [advancedOpen, setAdvancedOpen] = useState(false)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [userAuthToken, setUserAuthToken] = useState<string>('')
|
||||
const [userAuthToken, setUserAuthToken] = useState('')
|
||||
const [copied, setCopied] = useState(false)
|
||||
const [showToken, setShowToken] = useState(false)
|
||||
const [showApiKey, setShowApiKey] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setApiKey(config?.apiKey || DEMO_API_KEY)
|
||||
setBaseURL(config?.baseURL || DEMO_BASE_URL)
|
||||
setModel(config?.model || DEMO_MODEL)
|
||||
setApiKey(config?.apiKey)
|
||||
setLanguage(config?.language)
|
||||
setMaxSteps(config?.maxSteps)
|
||||
setSystemInstruction(config?.systemInstruction ?? '')
|
||||
setExperimentalLlmsTxt(config?.experimentalLlmsTxt ?? false)
|
||||
setExperimentalIncludeAllTabs(config?.experimentalIncludeAllTabs ?? false)
|
||||
setDisableNamedToolChoice(config?.disableNamedToolChoice ?? false)
|
||||
}, [config])
|
||||
|
||||
// Poll for user auth token every second until found
|
||||
@@ -96,6 +104,8 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
||||
maxSteps: maxSteps || undefined,
|
||||
systemInstruction: systemInstruction || undefined,
|
||||
experimentalLlmsTxt,
|
||||
experimentalIncludeAllTabs,
|
||||
disableNamedToolChoice,
|
||||
})
|
||||
} finally {
|
||||
setSaving(false)
|
||||
@@ -194,7 +204,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-xs text-muted-foreground">Model</label>
|
||||
<Input
|
||||
placeholder="gpt-5.2"
|
||||
placeholder="gpt-5.1"
|
||||
value={model}
|
||||
onChange={(e) => setModel(e.target.value)}
|
||||
className="text-xs h-8"
|
||||
@@ -206,7 +216,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
||||
<div className="flex gap-2 items-center">
|
||||
<Input
|
||||
type={showApiKey ? 'text' : 'password'}
|
||||
placeholder="sk-..."
|
||||
// placeholder="sk-..."
|
||||
value={apiKey}
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
className="text-xs h-8"
|
||||
@@ -271,10 +281,23 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="flex items-center justify-between cursor-pointer">
|
||||
<span className="text-xs text-muted-foreground">Disable named tool_choice</span>
|
||||
<Switch checked={disableNamedToolChoice} onCheckedChange={setDisableNamedToolChoice} />
|
||||
</label>
|
||||
|
||||
<label className="flex items-center justify-between cursor-pointer">
|
||||
<span className="text-xs text-muted-foreground">Experimental llms.txt support</span>
|
||||
<Switch checked={experimentalLlmsTxt} onCheckedChange={setExperimentalLlmsTxt} />
|
||||
</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>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
import { ArrowLeft } from 'lucide-react'
|
||||
import { ArrowLeft, RotateCcw, Trash2 } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { type SessionRecord, getSession } from '@/lib/db'
|
||||
import { type SessionRecord, deleteSession, getSession } from '@/lib/db'
|
||||
|
||||
import { EventCard } from './cards'
|
||||
|
||||
export function HistoryDetail({ sessionId, onBack }: { sessionId: string; onBack: () => void }) {
|
||||
export function HistoryDetail({
|
||||
sessionId,
|
||||
onBack,
|
||||
onRerun,
|
||||
}: {
|
||||
sessionId: string
|
||||
onBack: () => void
|
||||
onRerun: (task: string) => void
|
||||
}) {
|
||||
const [session, setSession] = useState<SessionRecord | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -37,6 +45,27 @@ export function HistoryDetail({ sessionId, onBack }: { sessionId: string; onBack
|
||||
<div className="text-xs font-medium" title={session.task}>
|
||||
{session.task}
|
||||
</div>
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onRerun(session.task)}
|
||||
className="flex items-center gap-1 text-[10px] text-muted-foreground hover:text-foreground transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<RotateCcw className="size-3" />
|
||||
Run again
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
await deleteSession(sessionId)
|
||||
onBack()
|
||||
}}
|
||||
className="flex items-center gap-1 text-[10px] text-muted-foreground hover:text-destructive transition-colors cursor-pointer"
|
||||
>
|
||||
<Trash2 className="size-3" />
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Events (read-only) */}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ArrowLeft, CheckCircle, Trash2, XCircle } from 'lucide-react'
|
||||
import { ArrowDownToLine, ArrowLeft, CheckCircle, RotateCcw, Trash2, XCircle } from 'lucide-react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { type SessionRecord, clearSessions, deleteSession, listSessions } from '@/lib/db'
|
||||
import { downloadHistoryExport } from '@/lib/history-export'
|
||||
|
||||
function timeAgo(ts: number): string {
|
||||
const seconds = Math.floor((Date.now() - ts) / 1000)
|
||||
@@ -18,9 +19,11 @@ function timeAgo(ts: number): string {
|
||||
export function HistoryList({
|
||||
onSelect,
|
||||
onBack,
|
||||
onRerun,
|
||||
}: {
|
||||
onSelect: (id: string) => void
|
||||
onBack: () => void
|
||||
onRerun: (task: string) => void
|
||||
}) {
|
||||
const [sessions, setSessions] = useState<SessionRecord[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
@@ -41,6 +44,16 @@ export function HistoryList({
|
||||
setSessions((prev) => prev.filter((s) => s.id !== id))
|
||||
}
|
||||
|
||||
const handleExport = (e: React.MouseEvent, session: SessionRecord) => {
|
||||
e.stopPropagation()
|
||||
downloadHistoryExport(session.task, session.createdAt, session.history)
|
||||
}
|
||||
|
||||
const handleRerun = (e: React.MouseEvent, task: string) => {
|
||||
e.stopPropagation()
|
||||
onRerun(task)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-screen bg-background">
|
||||
{/* Header */}
|
||||
@@ -85,7 +98,6 @@ export function HistoryList({
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => onSelect(session.id)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && onSelect(session.id)}
|
||||
className="w-full text-left px-3 py-2.5 border-b hover:bg-muted/50 transition-colors cursor-pointer flex items-start gap-2 group"
|
||||
>
|
||||
{/* Status icon */}
|
||||
@@ -98,19 +110,41 @@ export function HistoryList({
|
||||
{/* Content */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-xs font-medium truncate">{session.task}</p>
|
||||
<p className="text-[10px] text-muted-foreground mt-0.5">
|
||||
{timeAgo(session.createdAt)} · {session.history.length} steps
|
||||
</p>
|
||||
<div className="flex items-center mt-0.5">
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
{timeAgo(session.createdAt)} · {session.history.length} steps
|
||||
</p>
|
||||
<div className="flex items-center gap-0.5 ml-auto opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleRerun(e, session.task)}
|
||||
className="p-0.5 text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
|
||||
title="Run task again"
|
||||
aria-label={`Run history task again: ${session.task}`}
|
||||
>
|
||||
<RotateCcw className="size-3" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleExport(e, session)}
|
||||
className="p-1 text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
|
||||
title="Export history JSON"
|
||||
aria-label={`Export history for ${session.task}`}
|
||||
>
|
||||
<ArrowDownToLine className="size-3" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleDelete(e, session.id)}
|
||||
className="p-0.5 text-muted-foreground hover:text-destructive transition-colors cursor-pointer"
|
||||
title="Delete history"
|
||||
aria-label={`Delete history for ${session.task}`}
|
||||
>
|
||||
<Trash2 className="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Delete */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => handleDelete(e, session.id)}
|
||||
className="opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:text-destructive cursor-pointer shrink-0"
|
||||
>
|
||||
<Trash2 className="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -111,6 +111,7 @@ export function EmptyState() {
|
||||
]}
|
||||
cursorStyle="underscore"
|
||||
loop
|
||||
startOnView={false}
|
||||
typeSpeed={20}
|
||||
deleteSpeed={10}
|
||||
pauseDelay={3000}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { handlePageControlMessage } from '@/agent/RemotePageController.background'
|
||||
import { handleTabControlMessage, setupTabChangeEvents } from '@/agent/TabsController.background'
|
||||
import { handleTabControlMessage, setupTabEventsPort } from '@/agent/TabsController.background'
|
||||
|
||||
export default defineBackground(() => {
|
||||
console.log('[Background] Service Worker started')
|
||||
|
||||
// tab change events
|
||||
|
||||
setupTabChangeEvents()
|
||||
setupTabEventsPort()
|
||||
|
||||
// generate user auth token
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ export type Execute = (task: string, config: ExecuteConfig) => Promise<Execution
|
||||
|
||||
export interface ExecuteConfig {
|
||||
baseURL: string
|
||||
apiKey: string
|
||||
model: string
|
||||
apiKey?: string
|
||||
|
||||
/**
|
||||
* Whether to include the initial tab (that holds this main world script) in the task.
|
||||
@@ -13,6 +13,9 @@ export interface ExecuteConfig {
|
||||
*/
|
||||
includeInitialTab?: boolean
|
||||
|
||||
/** Control all unpinned tabs in the window instead of only the tab group. */
|
||||
experimentalIncludeAllTabs?: boolean
|
||||
|
||||
onStatusChange?: (status: AgentStatus) => void
|
||||
onActivity?: (activity: AgentActivity) => void
|
||||
onHistoryUpdate?: (history: HistoricalEvent[]) => void
|
||||
@@ -30,7 +33,6 @@ export default defineUnlistedScript(() => {
|
||||
if (task.trim().length === 0) throw new Error('Task cannot be empty')
|
||||
if (!config) throw new Error('Config is required')
|
||||
if (!config.baseURL) throw new Error('Config must have a baseURL')
|
||||
if (!config.apiKey) throw new Error('Config must have an apiKey')
|
||||
if (!config.model) throw new Error('Config must have a model')
|
||||
|
||||
const id = getId()
|
||||
@@ -85,9 +87,10 @@ export default defineUnlistedScript(() => {
|
||||
task,
|
||||
config: {
|
||||
baseURL: config.baseURL,
|
||||
apiKey: config.apiKey,
|
||||
model: config.model,
|
||||
apiKey: config.apiKey,
|
||||
includeInitialTab: config.includeInitialTab,
|
||||
experimentalIncludeAllTabs: config.experimentalIncludeAllTabs,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -56,19 +56,27 @@ export default function App() {
|
||||
}
|
||||
}, [history, activity])
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(e?: React.SyntheticEvent) => {
|
||||
e?.preventDefault()
|
||||
if (!inputValue.trim() || status === 'running') return
|
||||
const runTask = useCallback(
|
||||
(task: string) => {
|
||||
const normalizedTask = task.trim()
|
||||
if (!normalizedTask || status === 'running') return
|
||||
|
||||
const taskToExecute = inputValue.trim()
|
||||
setInputValue('')
|
||||
setView({ name: 'chat' })
|
||||
|
||||
execute(taskToExecute).catch((error) => {
|
||||
execute(normalizedTask).catch((error) => {
|
||||
console.error('[SidePanel] Failed to execute task:', error)
|
||||
})
|
||||
},
|
||||
[inputValue, status, execute]
|
||||
[execute, status]
|
||||
)
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(e?: React.SyntheticEvent) => {
|
||||
e?.preventDefault()
|
||||
runTask(inputValue)
|
||||
},
|
||||
[inputValue, runTask]
|
||||
)
|
||||
|
||||
const handleStop = useCallback(() => {
|
||||
@@ -103,12 +111,19 @@ export default function App() {
|
||||
<HistoryList
|
||||
onSelect={(id) => setView({ name: 'history-detail', sessionId: id })}
|
||||
onBack={() => setView({ name: 'chat' })}
|
||||
onRerun={runTask}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (view.name === 'history-detail') {
|
||||
return <HistoryDetail sessionId={view.sessionId} onBack={() => setView({ name: 'history' })} />
|
||||
return (
|
||||
<HistoryDetail
|
||||
sessionId={view.sessionId}
|
||||
onBack={() => setView({ name: 'history' })}
|
||||
onRerun={runTask}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// --- Chat view ---
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { HistoricalEvent } from '@page-agent/core'
|
||||
|
||||
const EXPORT_FILE_PREFIX = 'page-agent-history'
|
||||
const MAX_TASK_SLUG_LENGTH = 40
|
||||
|
||||
export function serializeHistoryExport(history: HistoricalEvent[]): string {
|
||||
return `${JSON.stringify(history, null, 2)}\n`
|
||||
}
|
||||
|
||||
export function buildHistoryExportFilename(task: string, createdAt: number): string {
|
||||
const taskSlug = sanitizeTaskForFilename(task)
|
||||
const timestamp = formatTimestampForFilename(createdAt)
|
||||
|
||||
return taskSlug
|
||||
? `${EXPORT_FILE_PREFIX}-${taskSlug}-${timestamp}.json`
|
||||
: `${EXPORT_FILE_PREFIX}-${timestamp}.json`
|
||||
}
|
||||
|
||||
export function downloadHistoryExport(
|
||||
task: string,
|
||||
createdAt: number,
|
||||
history: HistoricalEvent[]
|
||||
): void {
|
||||
const filename = buildHistoryExportFilename(task, createdAt)
|
||||
const content = serializeHistoryExport(history)
|
||||
const blob = new Blob([content], { type: 'application/json;charset=utf-8' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
|
||||
link.href = url
|
||||
link.download = filename
|
||||
link.click()
|
||||
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
function sanitizeTaskForFilename(task: string): string {
|
||||
return task
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
.slice(0, MAX_TASK_SLUG_LENGTH)
|
||||
}
|
||||
|
||||
function formatTimestampForFilename(createdAt: number): string {
|
||||
const date = new Date(createdAt)
|
||||
const year = date.getFullYear()
|
||||
const month = pad(date.getMonth() + 1)
|
||||
const day = pad(date.getDate())
|
||||
const hours = pad(date.getHours())
|
||||
const minutes = pad(date.getMinutes())
|
||||
const seconds = pad(date.getSeconds())
|
||||
|
||||
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`
|
||||
}
|
||||
|
||||
function pad(value: number): string {
|
||||
return value.toString().padStart(2, '0')
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@page-agent/llms",
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"type": "module",
|
||||
"main": "./dist/lib/page-agent-llms.js",
|
||||
"module": "./dist/lib/page-agent-llms.js",
|
||||
|
||||
@@ -29,16 +29,19 @@ export class OpenAIClient implements LLMClient {
|
||||
const openaiTools = Object.entries(tools).map(([name, t]) => zodToOpenAITool(name, t))
|
||||
|
||||
// Build request body
|
||||
|
||||
let toolChoice: unknown = 'required'
|
||||
if (options?.toolChoiceName && !this.config.disableNamedToolChoice) {
|
||||
toolChoice = { type: 'function', function: { name: options.toolChoiceName } }
|
||||
}
|
||||
|
||||
const requestBody: Record<string, unknown> = {
|
||||
model: this.config.model,
|
||||
temperature: this.config.temperature,
|
||||
messages,
|
||||
tools: openaiTools,
|
||||
parallel_tool_calls: false,
|
||||
// Require tool call: specific tool if provided, otherwise any tool
|
||||
tool_choice: options?.toolChoiceName
|
||||
? { type: 'function', function: { name: options.toolChoiceName } }
|
||||
: 'required',
|
||||
tool_choice: toolChoice,
|
||||
}
|
||||
|
||||
modelPatch(requestBody)
|
||||
@@ -50,7 +53,7 @@ export class OpenAIClient implements LLMClient {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${this.config.apiKey}`,
|
||||
...(this.config.apiKey && { Authorization: `Bearer ${this.config.apiKey}` }),
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
signal: abortSignal,
|
||||
|
||||
@@ -8,19 +8,20 @@ export type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool }
|
||||
|
||||
export function parseLLMConfig(config: LLMConfig): Required<LLMConfig> {
|
||||
// Runtime validation as defensive programming (types already guarantee these)
|
||||
if (!config.baseURL || !config.apiKey || !config.model) {
|
||||
if (!config.baseURL || !config.model) {
|
||||
throw new Error(
|
||||
'[PageAgent] LLM configuration required. Please provide: baseURL, apiKey, model. ' +
|
||||
'[PageAgent] LLM configuration required. Please provide: baseURL, model. ' +
|
||||
'See: https://alibaba.github.io/page-agent/docs/features/models'
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
baseURL: config.baseURL,
|
||||
apiKey: config.apiKey,
|
||||
model: config.model,
|
||||
apiKey: config.apiKey || '',
|
||||
temperature: config.temperature ?? DEFAULT_TEMPERATURE,
|
||||
maxRetries: config.maxRetries ?? LLM_MAX_RETRIES,
|
||||
disableNamedToolChoice: config.disableNamedToolChoice ?? false,
|
||||
customFetch: (config.customFetch ?? fetch).bind(globalThis), // fetch will be illegal unless bound
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,12 +89,18 @@ export interface InvokeResult<TResult = unknown> {
|
||||
*/
|
||||
export interface LLMConfig {
|
||||
baseURL: string
|
||||
apiKey: string
|
||||
model: string
|
||||
apiKey?: string
|
||||
|
||||
temperature?: number
|
||||
maxRetries?: number
|
||||
|
||||
/**
|
||||
* remove the tool_choice field from the request.
|
||||
* @note fix "Invalid tool_choice type: 'object'" for some LLMs.
|
||||
*/
|
||||
disableNamedToolChoice?: boolean
|
||||
|
||||
/**
|
||||
* Custom fetch function for LLM API requests.
|
||||
* Use this to customize headers, credentials, proxy, etc.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@page-agent/mcp",
|
||||
"private": false,
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"page-agent-mcp": "src/index.js"
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"ws": "^8.19.0",
|
||||
"ws": "^8.20.0",
|
||||
"zod": "^4.3.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ const port = parseInt(env.PORT || '38401')
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
const llmConfig = {}
|
||||
if (env.LLM_API_KEY) llmConfig.apiKey = env.LLM_API_KEY
|
||||
if (env.LLM_BASE_URL) llmConfig.baseURL = env.LLM_BASE_URL
|
||||
if (env.LLM_MODEL_NAME) llmConfig.model = env.LLM_MODEL_NAME
|
||||
if (env.LLM_API_KEY) llmConfig.apiKey = env.LLM_API_KEY
|
||||
|
||||
// --- Hub bridge (HTTP + WebSocket) ---
|
||||
|
||||
@@ -35,11 +35,14 @@ const mcpServer = new McpServer({ name: 'page-agent', version: '1.5.8' })
|
||||
mcpServer.registerTool(
|
||||
'execute_task',
|
||||
{
|
||||
description:
|
||||
'Execute a browser automation task described in natural language. ' +
|
||||
'The Page Agent extension will control the browser to complete the task. ' +
|
||||
'Blocks until the task is complete.',
|
||||
inputSchema: { task: z.string().describe('Task description in natural language') },
|
||||
description: "Execute a task in user's browser.",
|
||||
inputSchema: {
|
||||
task: z
|
||||
.string()
|
||||
.describe(
|
||||
'Task description. Give specific instructions for the task. Steps preferable. And the information you want to get after the task is done.'
|
||||
),
|
||||
},
|
||||
},
|
||||
async ({ task }) => {
|
||||
try {
|
||||
@@ -50,7 +53,7 @@ mcpServer.registerTool(
|
||||
{
|
||||
type: 'text',
|
||||
text: result.success
|
||||
? `Task completed successfully.\n\n${result.data}`
|
||||
? `Task completed.\n\n${result.data}`
|
||||
: `Task failed.\n\n${result.data}`,
|
||||
},
|
||||
],
|
||||
@@ -67,7 +70,7 @@ mcpServer.registerTool(
|
||||
mcpServer.registerTool(
|
||||
'get_status',
|
||||
{
|
||||
description: 'Check the current status of the Page Agent hub connection and agent.',
|
||||
description: 'Check the current status of the Page Agent hub.',
|
||||
},
|
||||
async () => ({
|
||||
content: [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "page-agent",
|
||||
"private": false,
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"type": "module",
|
||||
"main": "./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{}})\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@page-agent/core": "1.5.10",
|
||||
"@page-agent/llms": "1.5.10",
|
||||
"@page-agent/page-controller": "1.5.10",
|
||||
"@page-agent/ui": "1.5.10",
|
||||
"@page-agent/core": "1.6.3",
|
||||
"@page-agent/llms": "1.6.3",
|
||||
"@page-agent/page-controller": "1.6.3",
|
||||
"@page-agent/ui": "1.6.3",
|
||||
"chalk": "^5.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
*/
|
||||
import { type AgentConfig, PageAgentCore } from '@page-agent/core'
|
||||
import { PageController, type PageControllerConfig } from '@page-agent/page-controller'
|
||||
import { Panel } from '@page-agent/ui'
|
||||
import { Panel, type PanelConfig } from '@page-agent/ui'
|
||||
|
||||
export * from '@page-agent/core'
|
||||
|
||||
export type PageAgentConfig = AgentConfig & PageControllerConfig
|
||||
export type PageAgentConfig = AgentConfig & PageControllerConfig & Omit<PanelConfig, 'language'>
|
||||
|
||||
export class PageAgent extends PageAgentCore {
|
||||
panel: Panel
|
||||
@@ -23,6 +23,7 @@ export class PageAgent extends PageAgentCore {
|
||||
|
||||
this.panel = new Panel(this, {
|
||||
language: config.language,
|
||||
promptForNextTask: config.promptForNextTask,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@page-agent/page-controller",
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"type": "module",
|
||||
"main": "./dist/lib/page-controller.js",
|
||||
"module": "./dist/lib/page-controller.js",
|
||||
|
||||
@@ -18,6 +18,7 @@ import * as dom from './dom'
|
||||
import type { FlatDomTree, InteractiveElementDomNode } from './dom/dom_tree/type'
|
||||
import { getPageInfo } from './dom/getPageInfo'
|
||||
import { patchReact } from './patches/react'
|
||||
import { isAnchorElement } from './utils'
|
||||
|
||||
/**
|
||||
* Configuration for PageController
|
||||
@@ -243,7 +244,7 @@ export class PageController extends EventTarget {
|
||||
await clickElement(element)
|
||||
|
||||
// Handle links that open in new tabs
|
||||
if (element instanceof HTMLAnchorElement && element.target === '_blank') {
|
||||
if (isAnchorElement(element) && element.target === '_blank') {
|
||||
return {
|
||||
success: true,
|
||||
message: `✅ Clicked element (${elemText ?? index}). ⚠️ Link opened in a new tab.`,
|
||||
@@ -423,3 +424,5 @@ export class PageController extends EventTarget {
|
||||
this.mask = null
|
||||
}
|
||||
}
|
||||
|
||||
export * from './actions'
|
||||
|
||||
@@ -3,27 +3,19 @@
|
||||
* All rights reserved.
|
||||
*/
|
||||
import type { InteractiveElementDomNode } from './dom/dom_tree/type'
|
||||
|
||||
// ======= general utils =======
|
||||
|
||||
async function waitFor(seconds: number): Promise<void> {
|
||||
await new Promise((resolve) => setTimeout(resolve, seconds * 1000))
|
||||
}
|
||||
|
||||
// ======= dom utils =======
|
||||
|
||||
export async function movePointerToElement(element: HTMLElement) {
|
||||
const rect = element.getBoundingClientRect()
|
||||
const x = rect.left + rect.width / 2
|
||||
const y = rect.top + rect.height / 2
|
||||
|
||||
window.dispatchEvent(new CustomEvent('PageAgent::MovePointerTo', { detail: { x, y } }))
|
||||
|
||||
await waitFor(0.3)
|
||||
}
|
||||
import {
|
||||
getNativeValueSetter,
|
||||
isHTMLElement,
|
||||
isInputElement,
|
||||
isSelectElement,
|
||||
isTextAreaElement,
|
||||
movePointerToElement,
|
||||
waitFor,
|
||||
} from './utils'
|
||||
|
||||
/**
|
||||
* Get the HTMLElement by index from a selectorMap.
|
||||
* @private Internal method, subject to change at any time.
|
||||
*/
|
||||
export function getElementByIndex(
|
||||
selectorMap: Map<number, InteractiveElementDomNode>,
|
||||
@@ -39,7 +31,7 @@ export function getElementByIndex(
|
||||
throw new Error(`Element at index ${index} does not have a reference`)
|
||||
}
|
||||
|
||||
if (!(element instanceof HTMLElement)) {
|
||||
if (!isHTMLElement(element)) {
|
||||
throw new Error(`Element at index ${index} is not an HTMLElement`)
|
||||
}
|
||||
|
||||
@@ -54,20 +46,30 @@ function blurLastClickedElement() {
|
||||
lastClickedElement.dispatchEvent(
|
||||
new MouseEvent('mouseout', { bubbles: true, cancelable: true })
|
||||
)
|
||||
lastClickedElement.dispatchEvent(
|
||||
new MouseEvent('mouseleave', { bubbles: false, cancelable: true })
|
||||
)
|
||||
lastClickedElement = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate a click on the element
|
||||
* @private Internal method, subject to change at any time.
|
||||
*/
|
||||
export async function clickElement(element: HTMLElement) {
|
||||
blurLastClickedElement()
|
||||
|
||||
lastClickedElement = element
|
||||
|
||||
await scrollIntoViewIfNeeded(element)
|
||||
// Scroll the iframe element itself into view if needed
|
||||
const frame = element.ownerDocument.defaultView?.frameElement
|
||||
if (frame) await scrollIntoViewIfNeeded(frame)
|
||||
|
||||
await movePointerToElement(element)
|
||||
window.dispatchEvent(new CustomEvent('PageAgent::ClickPointer'))
|
||||
|
||||
await waitFor(0.1)
|
||||
|
||||
// hover it
|
||||
@@ -89,25 +91,12 @@ export async function clickElement(element: HTMLElement) {
|
||||
await waitFor(0.2) // Wait to ensure click event processing completes
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
|
||||
window.HTMLInputElement.prototype,
|
||||
'value'
|
||||
)!.set!
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
const nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(
|
||||
window.HTMLTextAreaElement.prototype,
|
||||
'value'
|
||||
)!.set!
|
||||
|
||||
/**
|
||||
* @private Internal method, subject to change at any time.
|
||||
*/
|
||||
export async function inputTextElement(element: HTMLElement, text: string) {
|
||||
const isContentEditable = element.isContentEditable
|
||||
if (
|
||||
!(element instanceof HTMLInputElement) &&
|
||||
!(element instanceof HTMLTextAreaElement) &&
|
||||
!isContentEditable
|
||||
) {
|
||||
if (!isInputElement(element) && !isTextAreaElement(element) && !isContentEditable) {
|
||||
throw new Error('Element is not an input, textarea, or contenteditable')
|
||||
}
|
||||
|
||||
@@ -119,9 +108,12 @@ export async function inputTextElement(element: HTMLElement, text: string) {
|
||||
// - Monaco/CodeMirror: Require direct JS instance access. No universal way to obtain.
|
||||
// - Draft.js: Not responsive to synthetic/execCommand/Range/DataTransfer. Unmaintained.
|
||||
//
|
||||
// Strategy: Try Plan A (synthetic events) first, then verify and fall back
|
||||
// to Plan B (execCommand) if the text wasn't actually inserted.
|
||||
//
|
||||
// Plan A: Dispatch synthetic events
|
||||
// Works: LinkedIn, React contenteditable, Quill.
|
||||
// Fails: Slate.js
|
||||
// Works: React contenteditable, Quill.
|
||||
// Fails: Slate.js, some contenteditable editors that ignore synthetic events.
|
||||
// Sequence: beforeinput -> mutation -> input -> change -> blur
|
||||
|
||||
// Dispatch beforeinput + mutation + input for clearing
|
||||
@@ -164,22 +156,37 @@ export async function inputTextElement(element: HTMLElement, text: string) {
|
||||
)
|
||||
}
|
||||
|
||||
// Verify Plan A worked by checking if the text was actually inserted
|
||||
const planASucceeded = element.innerText.trim() === text.trim()
|
||||
|
||||
if (!planASucceeded) {
|
||||
// Plan B: execCommand fallback (deprecated but widely supported)
|
||||
// Works: Quill, Slate.js, react contenteditable components.
|
||||
// This approach integrates with the browser's undo stack and is handled
|
||||
// natively by most rich-text editors.
|
||||
element.focus()
|
||||
|
||||
// Select all existing content and delete it
|
||||
const doc = element.ownerDocument
|
||||
const selection = (doc.defaultView || window).getSelection()
|
||||
const range = doc.createRange()
|
||||
range.selectNodeContents(element)
|
||||
selection?.removeAllRanges()
|
||||
selection?.addRange(range)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
||||
doc.execCommand('delete', false)
|
||||
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
||||
doc.execCommand('insertText', false, text)
|
||||
}
|
||||
|
||||
// Dispatch change event (for good measure)
|
||||
element.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
|
||||
// Trigger blur for validation
|
||||
element.blur()
|
||||
|
||||
// Plan B: execCommand (deprecated but works better for some editors)
|
||||
// Works: LinkedIn, Quill, Slate.js, react contenteditable components
|
||||
//
|
||||
// document.execCommand('selectAll')
|
||||
// document.execCommand('delete')
|
||||
// document.execCommand('insertText', false, text)
|
||||
} else if (element instanceof HTMLTextAreaElement) {
|
||||
nativeTextAreaValueSetter.call(element, text)
|
||||
} else {
|
||||
nativeInputValueSetter.call(element, text)
|
||||
getNativeValueSetter(element as HTMLInputElement | HTMLTextAreaElement).call(element, text)
|
||||
}
|
||||
|
||||
// Only dispatch shared input event for non-contenteditable (contenteditable has its own)
|
||||
@@ -194,9 +201,10 @@ export async function inputTextElement(element: HTMLElement, text: string) {
|
||||
|
||||
/**
|
||||
* @todo browser-use version is very complex and supports menu tags, need to follow up
|
||||
* @private Internal method, subject to change at any time.
|
||||
*/
|
||||
export async function selectOptionElement(selectElement: HTMLSelectElement, optionText: string) {
|
||||
if (!(selectElement instanceof HTMLSelectElement)) {
|
||||
if (!isSelectElement(selectElement)) {
|
||||
throw new Error('Element is not a select element')
|
||||
}
|
||||
|
||||
@@ -213,11 +221,14 @@ export async function selectOptionElement(selectElement: HTMLSelectElement, opti
|
||||
await waitFor(0.1) // Wait to ensure change event processing completes
|
||||
}
|
||||
|
||||
interface ScrollableElement extends HTMLElement {
|
||||
interface ScrollableElement extends Element {
|
||||
scrollIntoViewIfNeeded?: (centerIfNeeded?: boolean) => void
|
||||
}
|
||||
|
||||
export async function scrollIntoViewIfNeeded(element: HTMLElement) {
|
||||
/**
|
||||
* @private Internal method, subject to change at any time.
|
||||
*/
|
||||
export async function scrollIntoViewIfNeeded(element: Element) {
|
||||
const el = element as ScrollableElement
|
||||
if (typeof el.scrollIntoViewIfNeeded === 'function') {
|
||||
el.scrollIntoViewIfNeeded()
|
||||
@@ -229,6 +240,9 @@ export async function scrollIntoViewIfNeeded(element: HTMLElement) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private Internal method, subject to change at any time.
|
||||
*/
|
||||
export async function scrollVertically(
|
||||
down: boolean,
|
||||
scroll_amount: number,
|
||||
@@ -357,6 +371,9 @@ export async function scrollVertically(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private Internal method, subject to change at any time.
|
||||
*/
|
||||
export async function scrollHorizontally(
|
||||
right: boolean,
|
||||
scroll_amount: number,
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// ======= type guards =======
|
||||
// @note instanceof fails for elements inside iframes
|
||||
|
||||
export function isHTMLElement(el: unknown): el is HTMLElement {
|
||||
// @todo either specify to HTMLElement or allow Element here.
|
||||
return !!el && (el as Node).nodeType === 1
|
||||
}
|
||||
|
||||
export function isInputElement(el: Element): el is HTMLInputElement {
|
||||
return el?.nodeType === 1 && el.tagName === 'INPUT'
|
||||
}
|
||||
|
||||
export function isTextAreaElement(el: Element): el is HTMLTextAreaElement {
|
||||
return el?.nodeType === 1 && el.tagName === 'TEXTAREA'
|
||||
}
|
||||
|
||||
export function isSelectElement(el: Element): el is HTMLSelectElement {
|
||||
return el?.nodeType === 1 && el.tagName === 'SELECT'
|
||||
}
|
||||
|
||||
export function isAnchorElement(el: Element): el is HTMLAnchorElement {
|
||||
return el?.nodeType === 1 && el.tagName === 'A'
|
||||
}
|
||||
|
||||
// ======= iframe helpers =======
|
||||
|
||||
/** Iframe offset for translating element coordinates to top-frame viewport. */
|
||||
export function getIframeOffset(element: HTMLElement): { x: number; y: number } {
|
||||
const frame = element.ownerDocument.defaultView?.frameElement as HTMLElement | null
|
||||
if (!frame) return { x: 0, y: 0 }
|
||||
const rect = frame.getBoundingClientRect()
|
||||
return { x: rect.left, y: rect.top }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get native value setter from the element's own prototype (iframe-safe).
|
||||
* @note for React
|
||||
*/
|
||||
export function getNativeValueSetter(element: HTMLInputElement | HTMLTextAreaElement) {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
return Object.getOwnPropertyDescriptor(Object.getPrototypeOf(element) as object, 'value')!
|
||||
.set as (v: string) => void
|
||||
}
|
||||
|
||||
// ======= general utils =======
|
||||
|
||||
export async function waitFor(seconds: number): Promise<void> {
|
||||
await new Promise((resolve) => setTimeout(resolve, seconds * 1000))
|
||||
}
|
||||
|
||||
// ======= dom utils =======
|
||||
|
||||
export async function movePointerToElement(element: HTMLElement) {
|
||||
const rect = element.getBoundingClientRect()
|
||||
const offset = getIframeOffset(element)
|
||||
const x = rect.left + rect.width / 2 + offset.x
|
||||
const y = rect.top + rect.height / 2 + offset.y
|
||||
|
||||
window.dispatchEvent(new CustomEvent('PageAgent::MovePointerTo', { detail: { x, y } }))
|
||||
|
||||
await waitFor(0.3)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@page-agent/ui",
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"type": "module",
|
||||
"main": "./dist/lib/page-agent-ui.js",
|
||||
"module": "./dist/lib/page-agent-ui.js",
|
||||
|
||||
@@ -369,6 +369,7 @@ export class Panel {
|
||||
}
|
||||
|
||||
#createWrapper(): HTMLElement {
|
||||
const taskInputMaxLength = 1000
|
||||
const wrapper = document.createElement('div')
|
||||
wrapper.id = 'page-agent-runtime_agent-panel'
|
||||
wrapper.className = styles.wrapper
|
||||
@@ -406,7 +407,7 @@ export class Panel {
|
||||
<input
|
||||
type="text"
|
||||
class="${styles.taskInput}"
|
||||
maxlength="200"
|
||||
maxlength="${taskInputMaxLength}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@page-agent/website",
|
||||
"private": true,
|
||||
"version": "1.5.10",
|
||||
"version": "1.6.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
@@ -19,13 +19,13 @@
|
||||
"@types/react-dom": "^19.2.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.577.0",
|
||||
"motion": "^12.37.0",
|
||||
"lucide-react": "^1.0.1",
|
||||
"motion": "^12.38.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"rough-notation": "^0.5.1",
|
||||
"simple-icons": "^16.12.0",
|
||||
"simple-icons": "^16.13.0",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss": "^4.1.14",
|
||||
|
||||
@@ -8,8 +8,8 @@ export default function LanguageSwitcher() {
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const languages = [
|
||||
{ code: 'zh-CN' as const, label: '中文' },
|
||||
{ code: 'en-US' as const, label: 'English' },
|
||||
{ code: 'zh-CN' as const, label: '中文' },
|
||||
]
|
||||
|
||||
const currentLanguage = languages.find((lang) => lang.code === language) || languages[0]
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Demo build (auto-init with demo LLM, for quick testing)
|
||||
export const CDN_DEMO_URL =
|
||||
'https://cdn.jsdelivr.net/npm/page-agent@1.5.10/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 =
|
||||
'https://registry.npmmirror.com/page-agent/1.5.10/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)
|
||||
export const DEMO_MODEL = 'qwen3.5-flash'
|
||||
export const DEMO_BASE_URL = 'https://page-ag-testing-ohftxirgbn.cn-shanghai.fcapp.run'
|
||||
export const DEMO_API_KEY = 'NA'
|
||||
// export const DEMO_API_KEY = ''
|
||||
|
||||
@@ -45,6 +45,7 @@ export default function DocsLayout({ children }: DocsLayoutProps) {
|
||||
{ title: isZh ? '知识注入' : 'Instructions', path: '/features/custom-instructions' },
|
||||
{ title: isZh ? '数据脱敏' : 'Data Masking', path: '/features/data-masking' },
|
||||
{ title: isZh ? 'Chrome 扩展' : 'Chrome Extension', path: '/features/chrome-extension' },
|
||||
{ title: 'MCP Server (Beta)', path: '/features/mcp-server' },
|
||||
{
|
||||
title: isZh ? '接入第三方 Agent' : 'Third-party Agent',
|
||||
path: '/features/third-party-agent',
|
||||
|
||||
@@ -129,12 +129,6 @@ const result = await agent.execute('Fill in the form with test data')`}
|
||||
? 'LLM API 的基础 URL(如 https://api.openai.com/v1)'
|
||||
: 'Base URL of the LLM API (e.g., https://api.openai.com/v1)',
|
||||
},
|
||||
{
|
||||
name: 'apiKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: isZh ? 'API 密钥' : 'API key for authentication',
|
||||
},
|
||||
{
|
||||
name: 'model',
|
||||
type: 'string',
|
||||
@@ -143,6 +137,12 @@ const result = await agent.execute('Fill in the form with test data')`}
|
||||
? '模型名称(如 gpt-5.2, anthropic/claude-4.5-haiku)'
|
||||
: 'Model name (e.g., gpt-5.2, anthropic/claude-4.5-haiku)',
|
||||
},
|
||||
{
|
||||
name: 'apiKey',
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'LLM AK',
|
||||
},
|
||||
{
|
||||
name: 'temperature',
|
||||
type: 'number',
|
||||
@@ -156,6 +156,14 @@ const result = await agent.execute('Fill in the form with test data')`}
|
||||
defaultValue: '3',
|
||||
description: isZh ? 'API 调用失败时的最大重试次数' : 'Maximum retries on API failure',
|
||||
},
|
||||
{
|
||||
name: 'disableNamedToolChoice',
|
||||
type: 'boolean',
|
||||
defaultValue: 'false',
|
||||
description: isZh
|
||||
? '禁用命名 tool_choice,始终使用 "required" 字符串。适用于不支持 tool_choice 对象格式的 LLM 服务。'
|
||||
: 'Disable named tool_choice, always use "required" string. For LLM services that don\'t support the object format of tool_choice.',
|
||||
},
|
||||
{
|
||||
name: 'customFetch',
|
||||
type: 'typeof fetch',
|
||||
|
||||
@@ -100,7 +100,7 @@ console.log(result.history) // Full execution history`}
|
||||
>
|
||||
AgentConfig
|
||||
</Link>{' '}
|
||||
和{' '}
|
||||
、 PanelConfig 和{' '}
|
||||
<Link
|
||||
href="/advanced/page-controller#configuration"
|
||||
className="text-blue-600 dark:text-blue-400 hover:underline"
|
||||
@@ -125,7 +125,7 @@ console.log(result.history) // Full execution history`}
|
||||
>
|
||||
AgentConfig
|
||||
</Link>{' '}
|
||||
and{' '}
|
||||
, PanelConfig, and{' '}
|
||||
<Link
|
||||
href="/advanced/page-controller#configuration"
|
||||
className="text-blue-600 dark:text-blue-400 hover:underline"
|
||||
|
||||
@@ -187,39 +187,7 @@ localStorage.setItem('PageAgentExtUserAuthToken', '<your-token-from-extension>')
|
||||
</p>
|
||||
|
||||
<CodeEditor
|
||||
code={
|
||||
isZh
|
||||
? `import type {
|
||||
AgentActivity,
|
||||
AgentStatus,
|
||||
ExecutionResult,
|
||||
HistoricalEvent
|
||||
} from '@page-agent/core'
|
||||
|
||||
interface ExecuteConfig {
|
||||
baseURL: string // LLM API 端点
|
||||
apiKey: string // API 密钥
|
||||
model: string // 模型名称
|
||||
|
||||
includeInitialTab?: boolean
|
||||
onStatusChange?: (status: AgentStatus) => void
|
||||
onActivity?: (activity: AgentActivity) => void
|
||||
onHistoryUpdate?: (history: HistoricalEvent[]) => void
|
||||
}
|
||||
|
||||
type Execute = (task: string, config: ExecuteConfig) => Promise<ExecutionResult>
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
PAGE_AGENT_EXT_VERSION?: string
|
||||
PAGE_AGENT_EXT?: {
|
||||
version: string
|
||||
execute: Execute
|
||||
stop: () => void
|
||||
}
|
||||
}
|
||||
}`
|
||||
: `import type {
|
||||
code={`import type {
|
||||
AgentActivity,
|
||||
AgentStatus,
|
||||
ExecutionResult,
|
||||
@@ -228,10 +196,11 @@ declare global {
|
||||
|
||||
interface ExecuteConfig {
|
||||
baseURL: string // LLM API endpoint
|
||||
apiKey: string // API key
|
||||
model: string // Model name
|
||||
apiKey?: string // LLM AK
|
||||
|
||||
includeInitialTab?: boolean
|
||||
experimentalIncludeAllTabs?: boolean // Control all unpinned tabs in the window
|
||||
onStatusChange?: (status: AgentStatus) => void
|
||||
onActivity?: (activity: AgentActivity) => void
|
||||
onHistoryUpdate?: (history: HistoricalEvent[]) => void
|
||||
@@ -248,8 +217,7 @@ declare global {
|
||||
stop: () => void
|
||||
}
|
||||
}
|
||||
}`
|
||||
}
|
||||
}`}
|
||||
language="typescript"
|
||||
/>
|
||||
|
||||
@@ -266,6 +234,7 @@ const result = await window.PAGE_AGENT_EXT.execute(
|
||||
apiKey: 'your-api-key',
|
||||
model: 'gpt-5.2',
|
||||
// includeInitialTab: false, // 设为 false 排除初始标签页
|
||||
// experimentalIncludeAllTabs: true, // 控制窗口内所有非固定标签页
|
||||
onStatusChange: status => console.log('状态变化:', status),
|
||||
onActivity: activity => console.log('活动:', activity),
|
||||
onHistoryUpdate: history => console.log('历史更新:', history)
|
||||
@@ -281,6 +250,7 @@ const result = await window.PAGE_AGENT_EXT.execute(
|
||||
apiKey: 'your-api-key',
|
||||
model: 'gpt-5.2',
|
||||
// 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),
|
||||
onActivity: activity => console.log('Activity:', activity),
|
||||
onHistoryUpdate: history => console.log('History update:', history)
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import BetaNotice from '@/components/BetaNotice'
|
||||
import CodeEditor from '@/components/CodeEditor'
|
||||
import { Heading } from '@/components/Heading'
|
||||
|
||||
export default function McpServerPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold mb-6">MCP Server (Beta)</h1>
|
||||
<BetaNotice />
|
||||
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
|
||||
Use the MCP server to let your local agent send natural-language browser tasks to Page Agent
|
||||
Ext.
|
||||
</p>
|
||||
|
||||
<section className="mb-10">
|
||||
<Heading id="quick-start" className="text-2xl font-bold mb-4">
|
||||
How to use
|
||||
</Heading>
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 bg-blue-50 dark:bg-blue-950/20 rounded-lg border border-blue-200 dark:border-blue-800">
|
||||
<p className="text-sm text-blue-900 dark:text-blue-200 leading-7">
|
||||
1. Install Page Agent Ext in Chrome.
|
||||
<br />
|
||||
2. Add the MCP server to your local agent client.
|
||||
<br />
|
||||
3. Start the client and approve the Hub connection in the browser when prompted.
|
||||
<br />
|
||||
4. Ask your agent to do something in the browser. The client will call execute_task
|
||||
for you.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<CodeEditor
|
||||
code={`{
|
||||
"mcpServers": {
|
||||
"page-agent": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@page-agent/mcp"],
|
||||
"env": {
|
||||
"LLM_BASE_URL": "https://api.openai.com/v1",
|
||||
"LLM_API_KEY": "sk-xxx",
|
||||
"LLM_MODEL_NAME": "gpt-5.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`}
|
||||
language="json"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-10">
|
||||
<Heading id="the-hub" className="text-2xl font-bold mb-4">
|
||||
The Hub
|
||||
</Heading>
|
||||
|
||||
<p className="text-gray-700 dark:text-gray-300 leading-relaxed">
|
||||
The Hub is the control center for communication between Page Agent Ext and external
|
||||
callers.
|
||||
</p>
|
||||
<p className="text-gray-700 dark:text-gray-300 leading-relaxed">
|
||||
When the MCP server starts, it opens a local launcher page. The launcher asks the
|
||||
extension to open the Hub tab, and the Hub receives tasks from your local agent. MCP uses
|
||||
this path, but the Hub itself is the extension's general external communication entry
|
||||
point.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -33,8 +33,8 @@ const MODEL_GROUPS: Record<string, string[]> = {
|
||||
'claude-haiku-4.5',
|
||||
'claude-sonnet-3.5',
|
||||
],
|
||||
xAI: ['grok-4.1-fast', 'grok-4', 'grok-code-fast'],
|
||||
MiniMax: ['MiniMax-M2.7', 'MiniMax-M2.7-highspeed', 'MiniMax-M2.5', 'MiniMax-M2.5-highspeed'],
|
||||
xAI: ['grok-4.1-fast', 'grok-4', 'grok-code-fast'],
|
||||
MoonshotAI: ['kimi-k2.5'],
|
||||
'Z.AI': ['glm-5', 'glm-4.7'],
|
||||
}
|
||||
@@ -129,10 +129,9 @@ const pageAgent = new PageAgent({
|
||||
model: 'MiniMax-M2.7'
|
||||
});
|
||||
|
||||
// Self-hosted models (e.g., Ollama)
|
||||
// Self-hosted models (e.g., Ollama) — no apiKey needed
|
||||
const pageAgent = new PageAgent({
|
||||
baseURL: 'http://localhost:11434/v1',
|
||||
apiKey: 'NA',
|
||||
model: 'qwen3:14b'
|
||||
});
|
||||
|
||||
@@ -280,7 +279,6 @@ LLM_MODEL_NAME="qwen3:14b"`}
|
||||
<CodeEditor
|
||||
code={`const agent = new PageAgent({
|
||||
baseURL: '/api/llm-proxy',
|
||||
apiKey: 'NA',
|
||||
model: 'gpt-5.1',
|
||||
customFetch: (url, init) =>
|
||||
fetch(url, { ...init, credentials: 'include' }),
|
||||
|
||||
@@ -13,6 +13,7 @@ import ChromeExtension from './features/chrome-extension/page'
|
||||
import Instructions from './features/custom-instructions/page'
|
||||
import CustomTools from './features/custom-tools/page'
|
||||
import DataMasking from './features/data-masking/page'
|
||||
import McpServerPage from './features/mcp-server/page'
|
||||
import Models from './features/models/page'
|
||||
import ThirdPartyAgent from './features/third-party-agent/page'
|
||||
import Limitations from './introduction/limitations/page'
|
||||
@@ -80,6 +81,11 @@ export default function DocsRouter() {
|
||||
<ChromeExtension />
|
||||
</DocsPage>
|
||||
</Route>
|
||||
<Route path="/features/mcp-server">
|
||||
<DocsPage>
|
||||
<McpServerPage />
|
||||
</DocsPage>
|
||||
</Route>
|
||||
<Route path="/features/third-party-agent">
|
||||
<DocsPage>
|
||||
<ThirdPartyAgent />
|
||||
|
||||
@@ -97,6 +97,7 @@ export default function LimitationsPage() {
|
||||
isZh ? '点击、文本输入、选择' : 'Click, text input, select',
|
||||
isZh ? '页面滚动(垂直 / 水平)' : 'Scroll (vertical / horizontal)',
|
||||
isZh ? '表单提交、焦点切换' : 'Form submit, focus',
|
||||
isZh ? '同源 iframe(仅单层)' : 'Same-origin iframe (single level only)',
|
||||
isZh ? '执行 JavaScript(可选)' : 'Execute JavaScript (opt-in)',
|
||||
].map((text) => (
|
||||
<li key={text} className="flex items-center text-gray-700 dark:text-gray-300">
|
||||
@@ -115,6 +116,7 @@ export default function LimitationsPage() {
|
||||
isZh ? '悬停、拖拽、右键菜单' : 'Hover, drag & drop, right-click',
|
||||
isZh ? '键盘快捷键' : 'Keyboard shortcuts',
|
||||
isZh ? '坐标定位操作' : 'Position-based control',
|
||||
isZh ? '嵌套 iframe、跨域 iframe' : 'Nested iframes, cross-origin iframes',
|
||||
isZh ? '绘图操作' : 'Drawing',
|
||||
isZh
|
||||
? 'Monaco、CodeMirror 等需要通过 JS 实例控制的编辑器'
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Particles } from '../../components/ui/particles'
|
||||
import {
|
||||
CDN_DEMO_CN_URL,
|
||||
CDN_DEMO_URL,
|
||||
DEMO_API_KEY,
|
||||
// DEMO_API_KEY,
|
||||
DEMO_BASE_URL,
|
||||
DEMO_MODEL,
|
||||
} from '../../constants'
|
||||
@@ -94,7 +94,7 @@ export default function HeroSection() {
|
||||
apiKey:
|
||||
import.meta.env.DEV && import.meta.env.LLM_API_KEY
|
||||
? import.meta.env.LLM_API_KEY
|
||||
: DEMO_API_KEY,
|
||||
: undefined,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -103,10 +103,7 @@ export default function HeroSection() {
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
className="relative px-6 pt-24 py-20 pb-18 lg:py-22 lg:pt-28 overflow-hidden"
|
||||
aria-labelledby="hero-heading"
|
||||
>
|
||||
<section className="relative px-6 pt-18 pb-14 lg:pb-20 lg:pt-24" aria-labelledby="hero-heading">
|
||||
<div className="max-w-7xl mx-auto text-center">
|
||||
{/* Background Pattern + Particles */}
|
||||
<div className="absolute inset-0 opacity-30" aria-hidden="true">
|
||||
@@ -122,7 +119,7 @@ export default function HeroSection() {
|
||||
/>
|
||||
|
||||
<div className="relative z-10">
|
||||
<div className="inline-flex items-center px-4 py-2 mb-8 text-sm font-medium bg-white/90 dark:bg-gray-800/90 rounded-full shadow-lg border border-gray-200 dark:border-gray-700">
|
||||
<div className="inline-flex items-center px-4 py-2 mb-4 text-sm font-medium bg-white/90 dark:bg-gray-800/90 rounded-full shadow-lg border border-gray-200 dark:border-gray-700">
|
||||
<span
|
||||
className="w-2 h-2 bg-blue-500 rounded-full mr-2 animate-pulse"
|
||||
aria-hidden="true"
|
||||
@@ -134,7 +131,7 @@ export default function HeroSection() {
|
||||
|
||||
<h1
|
||||
id="hero-heading"
|
||||
className="text-5xl lg:text-7xl font-bold mb-14 mt-8 bg-linear-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent pb-1"
|
||||
className="text-5xl lg:text-7xl font-bold mb-10 mt-8 bg-linear-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent pb-1"
|
||||
>
|
||||
{isZh ? (
|
||||
<>
|
||||
|
||||
@@ -58,6 +58,22 @@ export default function OneMoreThingSection() {
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="mb-10 rounded-2xl border border-blue-200/70 dark:border-blue-800/70 bg-linear-to-r from-blue-50 to-white dark:from-blue-950/30 dark:to-gray-900 px-5 py-4 max-w-3xl mx-auto text-left sm:text-center">
|
||||
<p className="text-sm text-gray-700 dark:text-gray-300 leading-7">
|
||||
{isZh
|
||||
? '从 Claude Desktop、Copilot 或其他本地 Agent 直接发起浏览器任务?'
|
||||
: 'Using Claude Desktop, Copilot, or another local agent? Connect it to the extension with the MCP server.'}
|
||||
</p>
|
||||
<p>
|
||||
<Link
|
||||
href="/docs/features/mcp-server"
|
||||
className="font-medium text-blue-700 dark:text-blue-300 underline underline-offset-4"
|
||||
>
|
||||
{isZh ? '查看 MCP 文档' : 'Read the MCP docs'}
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid sm:grid-cols-3 gap-5 text-left max-w-3xl mx-auto">
|
||||
{[
|
||||
{
|
||||
@@ -67,16 +83,16 @@ export default function OneMoreThingSection() {
|
||||
: 'Run tasks across multiple pages and tabs without being limited to a single page context',
|
||||
},
|
||||
{
|
||||
title: isZh ? '页面内发起控制' : 'Control from Your Page',
|
||||
title: isZh ? '从页面发起控制' : 'Control from a WebPage',
|
||||
desc: isZh
|
||||
? '在页面 JS 中发起任务,驱动整个浏览器完成跨标签操作'
|
||||
: 'Trigger tasks from page JS to drive the entire browser across tabs',
|
||||
: 'Trigger tasks from in-page JS to drive the entire browser across tabs',
|
||||
},
|
||||
{
|
||||
title: isZh ? '外部发起任务' : 'External Triggers',
|
||||
title: isZh ? '外部发起任务' : 'External Caller',
|
||||
desc: isZh
|
||||
? '页面 JS、本地 Agent 或云端 Agent 均可通过扩展发起任务'
|
||||
: 'Page JS, local agents, or cloud agents can trigger tasks through the extension',
|
||||
: 'Local agents and cloud agents can control user browser through the extension',
|
||||
},
|
||||
].map((item) => (
|
||||
<MagicCard
|
||||
|
||||
@@ -27,6 +27,7 @@ const SPA_ROUTES = [
|
||||
'docs/features/custom-instructions',
|
||||
'docs/features/models',
|
||||
'docs/features/chrome-extension',
|
||||
'docs/features/mcp-server',
|
||||
'docs/features/third-party-agent',
|
||||
'docs/advanced/page-agent',
|
||||
'docs/advanced/page-agent-core',
|
||||
|
||||
Reference in New Issue
Block a user