diff --git a/.github/workflows/deploy-demo.yml b/.github/workflows/deploy-demo.yml index c4e41a1..dc4ab55 100644 --- a/.github/workflows/deploy-demo.yml +++ b/.github/workflows/deploy-demo.yml @@ -28,7 +28,7 @@ jobs: run: npm run build:website - name: Setup Pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@v6 - name: Upload artifact uses: actions/upload-pages-artifact@v4 @@ -37,4 +37,4 @@ jobs: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3599e02..ca044ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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! 🎉 diff --git a/README.md b/README.md index 7a9dd89..05824f4 100644 --- a/README.md +++ b/README.md @@ -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.6.0/dist/iife/page-agent.demo.js | -| China | https://registry.npmmirror.com/page-agent/1.6.0/files/dist/iife/page-agent.demo.js | +| Global | https://cdn.jsdelivr.net/npm/page-agent@1.7.0/dist/iife/page-agent.demo.js | +| China | https://registry.npmmirror.com/page-agent/1.7.0/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!** - - - - - - Star History Chart - - diff --git a/docs/README-zh.md b/docs/README-zh.md index 9f6a166..1a025b8 100644 --- a/docs/README-zh.md +++ b/docs/README-zh.md @@ -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.6.0/dist/iife/page-agent.demo.js | -| China | https://registry.npmmirror.com/page-agent/1.6.0/files/dist/iife/page-agent.demo.js | +| Global | https://cdn.jsdelivr.net/npm/page-agent@1.7.0/dist/iife/page-agent.demo.js | +| China | https://registry.npmmirror.com/page-agent/1.7.0/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 有用或有趣,请给项目点个星!** - - - - - Star History Chart - - diff --git a/SECURITY.md b/docs/SECURITY.md similarity index 100% rename from SECURITY.md rename to docs/SECURITY.md diff --git a/docs/developer-guide.md b/docs/developer-guide.md new file mode 100644 index 0000000..8e34ce0 --- /dev/null +++ b/docs/developer-guide.md @@ -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. diff --git a/package-lock.json b/package-lock.json index 1ed40cf..3799cd9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "root", - "version": "1.6.0", + "version": "1.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "root", - "version": "1.6.0", + "version": "1.7.0", "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.58.0", "unplugin-dts": "^1.0.0-beta.6", "vite": "^7.3.1", "vite-bundle-analyzer": "^1.3.6", @@ -1819,9 +1819,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -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,20 +3893,20 @@ } }, "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.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", + "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", "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.58.0", + "@typescript-eslint/type-utils": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3916,9 +3916,9 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.57.1", + "@typescript-eslint/parser": "^8.58.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { @@ -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.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz", + "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", "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.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3" }, "engines": { @@ -3953,18 +3953,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "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.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", + "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.57.1", - "@typescript-eslint/types": "^8.57.1", + "@typescript-eslint/tsconfig-utils": "^8.58.0", + "@typescript-eslint/types": "^8.58.0", "debug": "^4.4.3" }, "engines": { @@ -3975,18 +3975,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "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.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", + "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.1", - "@typescript-eslint/visitor-keys": "8.57.1" + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0" }, "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.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", + "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", "dev": true, "license": "MIT", "engines": { @@ -4010,21 +4010,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "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.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", + "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", "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.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0", "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4035,13 +4035,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "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.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", + "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", "dev": true, "license": "MIT", "engines": { @@ -4053,21 +4053,21 @@ } }, "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.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", + "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", "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.58.0", + "@typescript-eslint/tsconfig-utils": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4077,20 +4077,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "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.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", + "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", "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.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4101,17 +4101,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "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.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", + "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.1", + "@typescript-eslint/types": "8.58.0", "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.7.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.7.0.tgz", + "integrity": "sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg==", "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.14.0", + "resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-16.14.0.tgz", + "integrity": "sha512-2Nvs3jJpCfMWQerD4zdv91g/MpnWn81a7uhyAC0reuhrjmS2MtSmwIKwewOJR6Xe97ZmfltDntCDqKJIBawQOw==", "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": { @@ -10549,9 +10549,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "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.58.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz", + "integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==", "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.58.0", + "@typescript-eslint/parser": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10703,7 +10703,7 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/ufo": { @@ -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.6.0", + "version": "1.7.0", "license": "MIT", "dependencies": { - "@page-agent/llms": "1.6.0", - "@page-agent/page-controller": "1.6.0", + "@page-agent/llms": "1.7.0", + "@page-agent/page-controller": "1.7.0", "chalk": "^5.6.2" }, "devDependencies": { @@ -11815,13 +11815,13 @@ }, "packages/extension": { "name": "@page-agent/ext", - "version": "1.6.0", + "version": "1.7.0", "hasInstallScript": true, "dependencies": { - "@page-agent/core": "1.6.0", - "@page-agent/llms": "1.6.0", - "@page-agent/page-controller": "1.6.0", - "@page-agent/ui": "1.6.0", + "@page-agent/core": "1.7.0", + "@page-agent/llms": "1.7.0", + "@page-agent/page-controller": "1.7.0", + "@page-agent/ui": "1.7.0", "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.7.0", + "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.14.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.6.0", + "version": "1.7.0", "license": "MIT", "dependencies": { "chalk": "^5.6.2" @@ -11872,11 +11872,11 @@ }, "packages/mcp": { "name": "@page-agent/mcp", - "version": "1.6.0", + "version": "1.7.0", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.27.1", - "ws": "^8.19.0", + "@modelcontextprotocol/sdk": "^1.29.0", + "ws": "^8.20.0", "zod": "^4.3.5" }, "bin": { @@ -11887,13 +11887,13 @@ } }, "packages/page-agent": { - "version": "1.6.0", + "version": "1.7.0", "license": "MIT", "dependencies": { - "@page-agent/core": "1.6.0", - "@page-agent/llms": "1.6.0", - "@page-agent/page-controller": "1.6.0", - "@page-agent/ui": "1.6.0", + "@page-agent/core": "1.7.0", + "@page-agent/llms": "1.7.0", + "@page-agent/page-controller": "1.7.0", + "@page-agent/ui": "1.7.0", "chalk": "^5.6.2" }, "devDependencies": { @@ -11905,7 +11905,7 @@ }, "packages/page-controller": { "name": "@page-agent/page-controller", - "version": "1.6.0", + "version": "1.7.0", "license": "MIT", "dependencies": { "ai-motion": "^0.4.8" @@ -11913,12 +11913,12 @@ }, "packages/ui": { "name": "@page-agent/ui", - "version": "1.6.0", + "version": "1.7.0", "license": "MIT" }, "packages/website": { "name": "@page-agent/website", - "version": "1.6.0", + "version": "1.7.0", "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.7.0", + "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.14.0", "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.1.14", diff --git a/package.json b/package.json index 21b827b..1aa2b4b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "root", "private": true, - "version": "1.6.0", + "version": "1.7.0", "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.58.0", "unplugin-dts": "^1.0.0-beta.6", "vite": "^7.3.1", "vite-plugin-css-injected-by-js": "^4.0.1", diff --git a/packages/core/package.json b/packages/core/package.json index bca9c1c..455f2c5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@page-agent/core", "private": false, - "version": "1.6.0", + "version": "1.7.0", "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.6.0", - "@page-agent/page-controller": "1.6.0" + "@page-agent/llms": "1.7.0", + "@page-agent/page-controller": "1.7.0" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" diff --git a/packages/extension/docs/extension_api.md b/packages/extension/docs/extension_api.md index d49272d..1586660 100644 --- a/packages/extension/docs/extension_api.md +++ b/packages/extension/docs/extension_api.md @@ -118,9 +118,18 @@ export interface ExecuteConfig { model: string apiKey?: string + // Global system-level instructions for the agent. + // Equivalent to AgentConfig.instructions.system. + systemInstruction?: 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 @@ -207,7 +216,11 @@ interface ExecuteConfig { baseURL: string model: string apiKey?: string + + systemInstruction?: string + includeInitialTab?: boolean + experimentalIncludeAllTabs?: boolean onStatusChange?: (status: AgentStatus) => void onActivity?: (activity: AgentActivity) => void onHistoryUpdate?: (history: HistoricalEvent[]) => void diff --git a/packages/extension/package.json b/packages/extension/package.json index d7e312e..1d129e3 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -1,7 +1,7 @@ { "name": "@page-agent/ext", "private": true, - "version": "1.6.0", + "version": "1.7.0", "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.7.0", + "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.14.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.6.0", - "@page-agent/llms": "1.6.0", - "@page-agent/page-controller": "1.6.0", - "@page-agent/ui": "1.6.0", + "@page-agent/core": "1.7.0", + "@page-agent/llms": "1.7.0", + "@page-agent/page-controller": "1.7.0", + "@page-agent/ui": "1.7.0", "ai-motion": "^0.4.8", "chalk": "^5.6.2" }, diff --git a/packages/extension/src/agent/MultiPageAgent.ts b/packages/extension/src/agent/MultiPageAgent.ts index f2e7b0a..aa768bc 100644 --- a/packages/extension/src/agent/MultiPageAgent.ts +++ b/packages/extension/src/agent/MultiPageAgent.ts @@ -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({ diff --git a/packages/extension/src/agent/RemotePageController.background.ts b/packages/extension/src/agent/RemotePageController.background.ts index b75c4cb..8fb89ae 100644 --- a/packages/extension/src/agent/RemotePageController.background.ts +++ b/packages/extension/src/agent/RemotePageController.background.ts @@ -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 diff --git a/packages/extension/src/agent/RemotePageController.ts b/packages/extension/src/agent/RemotePageController.ts index 3a35ddd..0c49f1b 100644 --- a/packages/extension/src/agent/RemotePageController.ts +++ b/packages/extension/src/agent/RemotePageController.ts @@ -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' diff --git a/packages/extension/src/agent/TabsController.background.ts b/packages/extension/src/agent/TabsController.background.ts index 39c628a..5b4baff 100644 --- a/packages/extension/src/agent/TabsController.background.ts +++ b/packages/extension/src/agent/TabsController.background.ts @@ -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() + +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 } }) }) } diff --git a/packages/extension/src/agent/TabsController.ts b/packages/extension/src/agent/TabsController.ts index 46fabf6..9d77ad5 100644 --- a/packages/extension/src/agent/TabsController.ts +++ b/packages/extension/src/agent/TabsController.ts @@ -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 { @@ -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 7 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 diff --git a/packages/extension/src/agent/useAgent.ts b/packages/extension/src/agent/useAgent.ts index f5596a5..c6a31bf 100644 --- a/packages/extension/src/agent/useAgent.ts +++ b/packages/extension/src/agent/useAgent.ts @@ -21,6 +21,7 @@ export interface AdvancedConfig { maxSteps?: number systemInstruction?: string experimentalLlmsTxt?: boolean + experimentalIncludeAllTabs?: boolean disableNamedToolChoice?: boolean } @@ -125,6 +126,7 @@ export function useAgent(): UseAgentResult { maxSteps, systemInstruction, experimentalLlmsTxt, + experimentalIncludeAllTabs, disableNamedToolChoice, ...llmConfig }: ExtConfig) => { @@ -138,6 +140,7 @@ export function useAgent(): UseAgentResult { maxSteps, systemInstruction, experimentalLlmsTxt, + experimentalIncludeAllTabs, disableNamedToolChoice, } await chrome.storage.local.set({ advancedConfig }) diff --git a/packages/extension/src/components/ConfigPanel.tsx b/packages/extension/src/components/ConfigPanel.tsx index f1dd250..d7a2d9f 100644 --- a/packages/extension/src/components/ConfigPanel.tsx +++ b/packages/extension/src/components/ConfigPanel.tsx @@ -31,17 +31,20 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) { const [model, setModel] = useState(config?.model || DEMO_MODEL) const [apiKey, setApiKey] = useState(config?.apiKey) const [language, setLanguage] = useState(config?.language) - const [maxSteps, setMaxSteps] = useState(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('') + const [userAuthToken, setUserAuthToken] = useState('') const [copied, setCopied] = useState(false) const [showToken, setShowToken] = useState(false) const [showApiKey, setShowApiKey] = useState(false) @@ -54,6 +57,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) { setMaxSteps(config?.maxSteps) setSystemInstruction(config?.systemInstruction ?? '') setExperimentalLlmsTxt(config?.experimentalLlmsTxt ?? false) + setExperimentalIncludeAllTabs(config?.experimentalIncludeAllTabs ?? false) setDisableNamedToolChoice(config?.disableNamedToolChoice ?? false) }, [config]) @@ -100,6 +104,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) { maxSteps: maxSteps || undefined, systemInstruction: systemInstruction || undefined, experimentalLlmsTxt, + experimentalIncludeAllTabs, disableNamedToolChoice, }) } finally { @@ -285,6 +290,14 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) { Experimental llms.txt support + + )} diff --git a/packages/extension/src/components/misc.tsx b/packages/extension/src/components/misc.tsx index 6de0f19..66bfa48 100644 --- a/packages/extension/src/components/misc.tsx +++ b/packages/extension/src/components/misc.tsx @@ -111,6 +111,7 @@ export function EmptyState() { ]} cursorStyle="underscore" loop + startOnView={false} typeSpeed={20} deleteSpeed={10} pauseDelay={3000} diff --git a/packages/extension/src/entrypoints/background.ts b/packages/extension/src/entrypoints/background.ts index 9c26f96..a83fde3 100644 --- a/packages/extension/src/entrypoints/background.ts +++ b/packages/extension/src/entrypoints/background.ts @@ -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 diff --git a/packages/extension/src/entrypoints/content.ts b/packages/extension/src/entrypoints/content.ts index e06dd1f..1bcfc8a 100644 --- a/packages/extension/src/entrypoints/content.ts +++ b/packages/extension/src/entrypoints/content.ts @@ -70,11 +70,15 @@ async function exposeAgentToPage() { try { const { task, config } = payload + const { systemInstruction, ...agentConfig } = config // Dispose old instance before creating new one multiPageAgent?.dispose() - multiPageAgent = new MultiPageAgent(config) + multiPageAgent = new MultiPageAgent({ + ...agentConfig, + instructions: systemInstruction ? { system: systemInstruction } : undefined, + }) // events diff --git a/packages/extension/src/entrypoints/main-world.ts b/packages/extension/src/entrypoints/main-world.ts index fd93e68..b401beb 100644 --- a/packages/extension/src/entrypoints/main-world.ts +++ b/packages/extension/src/entrypoints/main-world.ts @@ -7,12 +7,21 @@ export interface ExecuteConfig { model: string apiKey?: string + /** + * Global system-level instructions for the agent. + * Equivalent to `AgentConfig.instructions.system`. + */ + systemInstruction?: string + /** * Whether to include the initial tab (that holds this main world script) in the task. * @default true */ 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 @@ -86,7 +95,9 @@ export default defineUnlistedScript(() => { baseURL: config.baseURL, model: config.model, apiKey: config.apiKey, + systemInstruction: config.systemInstruction, includeInitialTab: config.includeInitialTab, + experimentalIncludeAllTabs: config.experimentalIncludeAllTabs, }, }, }, diff --git a/packages/llms/package.json b/packages/llms/package.json index bcf6c4c..befe627 100644 --- a/packages/llms/package.json +++ b/packages/llms/package.json @@ -1,6 +1,6 @@ { "name": "@page-agent/llms", - "version": "1.6.0", + "version": "1.7.0", "type": "module", "main": "./dist/lib/page-agent-llms.js", "module": "./dist/lib/page-agent-llms.js", diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 23d018c..c184c9a 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,7 +1,7 @@ { "name": "@page-agent/mcp", "private": false, - "version": "1.6.0", + "version": "1.7.0", "type": "module", "bin": { "page-agent-mcp": "src/index.js" @@ -28,8 +28,8 @@ "node": ">=20" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.27.1", - "ws": "^8.19.0", + "@modelcontextprotocol/sdk": "^1.29.0", + "ws": "^8.20.0", "zod": "^4.3.5" } } diff --git a/packages/mcp/src/index.js b/packages/mcp/src/index.js index d88211e..2661b6c 100755 --- a/packages/mcp/src/index.js +++ b/packages/mcp/src/index.js @@ -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: [ diff --git a/packages/page-agent/package.json b/packages/page-agent/package.json index 4414a28..fb40751 100644 --- a/packages/page-agent/package.json +++ b/packages/page-agent/package.json @@ -1,7 +1,7 @@ { "name": "page-agent", "private": false, - "version": "1.6.0", + "version": "1.7.0", "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.6.0", - "@page-agent/llms": "1.6.0", - "@page-agent/page-controller": "1.6.0", - "@page-agent/ui": "1.6.0", + "@page-agent/core": "1.7.0", + "@page-agent/llms": "1.7.0", + "@page-agent/page-controller": "1.7.0", + "@page-agent/ui": "1.7.0", "chalk": "^5.6.2" }, "peerDependencies": { diff --git a/packages/page-agent/src/PageAgent.ts b/packages/page-agent/src/PageAgent.ts index f6e5e48..486396b 100644 --- a/packages/page-agent/src/PageAgent.ts +++ b/packages/page-agent/src/PageAgent.ts @@ -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 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, }) } } diff --git a/packages/page-agent/src/demo.ts b/packages/page-agent/src/demo.ts index af6d668..516dd6f 100644 --- a/packages/page-agent/src/demo.ts +++ b/packages/page-agent/src/demo.ts @@ -17,9 +17,10 @@ const DEMO_MODEL = 'qwen3.5-plus' const DEMO_BASE_URL = 'https://page-ag-testing-ohftxirgbn.cn-shanghai.fcapp.run' const DEMO_API_KEY = 'NA' +const currentScript = document.currentScript as HTMLScriptElement | null + // in case document.x is not ready yet setTimeout(() => { - const currentScript = document.currentScript as HTMLScriptElement | null let config: PageAgentConfig if (currentScript) { diff --git a/packages/page-controller/package.json b/packages/page-controller/package.json index 928a859..2b1c31f 100644 --- a/packages/page-controller/package.json +++ b/packages/page-controller/package.json @@ -1,6 +1,6 @@ { "name": "@page-agent/page-controller", - "version": "1.6.0", + "version": "1.7.0", "type": "module", "main": "./dist/lib/page-controller.js", "module": "./dist/lib/page-controller.js", diff --git a/packages/page-controller/src/PageController.ts b/packages/page-controller/src/PageController.ts index ac83b15..53b827d 100644 --- a/packages/page-controller/src/PageController.ts +++ b/packages/page-controller/src/PageController.ts @@ -218,6 +218,7 @@ export class PageController extends EventTarget { * Clean up all element highlights */ async cleanUpHighlights(): Promise { + console.log('[PageController] cleanUpHighlights') dom.cleanUpHighlights() } @@ -424,3 +425,5 @@ export class PageController extends EventTarget { this.mask = null } } + +export * from './actions' diff --git a/packages/page-controller/src/actions.ts b/packages/page-controller/src/actions.ts index dcf6ab4..a218599 100644 --- a/packages/page-controller/src/actions.ts +++ b/packages/page-controller/src/actions.ts @@ -4,6 +4,9 @@ */ import type { InteractiveElementDomNode } from './dom/dom_tree/type' import { + clickPointer, + disablePassThrough, + enablePassThrough, getNativeValueSetter, isHTMLElement, isInputElement, @@ -15,6 +18,7 @@ import { /** * Get the HTMLElement by index from a selectorMap. + * @private Internal method, subject to change at any time. */ export function getElementByIndex( selectorMap: Map, @@ -41,19 +45,21 @@ let lastClickedElement: HTMLElement | null = null function blurLastClickedElement() { if (lastClickedElement) { + lastClickedElement.dispatchEvent(new PointerEvent('pointerout', { bubbles: true })) + lastClickedElement.dispatchEvent(new PointerEvent('pointerleave', { bubbles: false })) + lastClickedElement.dispatchEvent(new MouseEvent('mouseout', { bubbles: true })) + lastClickedElement.dispatchEvent(new MouseEvent('mouseleave', { bubbles: false })) lastClickedElement.blur() - 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 + * Simulate a full click following W3C Pointer Events + UI Events spec order: + * pointerover/enter → mouseover/enter → pointerdown → mousedown → [focus] → + * pointerup → mouseup → click + * + * @private Internal method, subject to change at any time. */ export async function clickElement(element: HTMLElement) { blurLastClickedElement() @@ -61,34 +67,67 @@ export async function clickElement(element: HTMLElement) { 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')) + const rect = element.getBoundingClientRect() + const x = rect.left + rect.width / 2 + const y = rect.top + rect.height / 2 + + await movePointerToElement(element, x, y) + await clickPointer() await waitFor(0.1) - // hover it - element.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true, cancelable: true })) - element.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, cancelable: true })) + // Hit-test to find the deepest element at click coordinates, matching + // real browser behavior where events target the innermost element. + // @note This may hit a element in the blacklist + // TODO: This is a temporary workaround. Should have been handled during dom extraction. + const doc = element.ownerDocument + await enablePassThrough() + const hitTarget = doc.elementFromPoint(x, y) + await disablePassThrough() + const target = + hitTarget instanceof HTMLElement && element.contains(hitTarget) ? hitTarget : element - // dispatch a sequence of events to ensure all listeners are triggered - element.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true })) + const pointerOpts = { + bubbles: true, + cancelable: true, + clientX: x, + clientY: y, + pointerType: 'mouse', + } + const mouseOpts = { bubbles: true, cancelable: true, clientX: x, clientY: y, button: 0 } - // focus it to ensure it gets the click event - element.focus() + // Hover — pointer events first, then mouse events (spec order) + target.dispatchEvent(new PointerEvent('pointerover', pointerOpts)) + target.dispatchEvent(new PointerEvent('pointerenter', { ...pointerOpts, bubbles: false })) + target.dispatchEvent(new MouseEvent('mouseover', mouseOpts)) + target.dispatchEvent(new MouseEvent('mouseenter', { ...mouseOpts, bubbles: false })) - element.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true })) - element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })) + // Press + target.dispatchEvent(new PointerEvent('pointerdown', pointerOpts)) + target.dispatchEvent(new MouseEvent('mousedown', mouseOpts)) - // dispatch a click event - // element.click() + // Focus is not part of the standard pointer/mouse event sequence + // "undefined and varies between user agents". + // We focus the original element (nearest focusable ancestor), not the hit-test target, matching browser behavior. + element.focus({ preventScroll: true }) - await waitFor(0.2) // Wait to ensure click event processing completes + // Release + target.dispatchEvent(new PointerEvent('pointerup', pointerOpts)) + target.dispatchEvent(new MouseEvent('mouseup', mouseOpts)) + + // Click — activation behavior (navigation, form submit, etc.) triggers + // via bubbling from target up to the interactive ancestor. + target.click() + + await waitFor(0.2) } +/** + * @private Internal method, subject to change at any time. + */ export async function inputTextElement(element: HTMLElement, text: string) { const isContentEditable = element.isContentEditable if (!isInputElement(element) && !isTextAreaElement(element) && !isContentEditable) { @@ -196,6 +235,7 @@ 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 (!isSelectElement(selectElement)) { @@ -219,6 +259,9 @@ interface ScrollableElement extends Element { scrollIntoViewIfNeeded?: (centerIfNeeded?: boolean) => void } +/** + * @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') { diff --git a/packages/page-controller/src/dom/dom_tree/index.js b/packages/page-controller/src/dom/dom_tree/index.js index 782ad58..5f57ff7 100644 --- a/packages/page-controller/src/dom/dom_tree/index.js +++ b/packages/page-controller/src/dom/dom_tree/index.js @@ -18,6 +18,7 @@ * @edit improve `sampleRect`, filter out rects with 0 area * @edit exclude aria-hidden elements * @edit make sure attributes exist for interactive candidates. + * @edit fix "aria-*" attributes check */ export default ( @@ -1143,6 +1144,31 @@ export default ( * @param {HTMLElement} element - The element to check. * @returns {boolean} Whether the element is an interactive candidate. */ + + // @edit fix "aria-*" attributes check + const INTERACTIVE_ARIA_ATTRS = [ + 'aria-expanded', + 'aria-checked', + 'aria-selected', + 'aria-pressed', + 'aria-haspopup', + 'aria-controls', + 'aria-owns', + 'aria-activedescendant', + 'aria-valuenow', + 'aria-valuetext', + 'aria-valuemax', + 'aria-valuemin', + 'aria-autocomplete', + ] + + function hasInteractiveAria(el) { + for (let i = 0; i < INTERACTIVE_ARIA_ATTRS.length; i++) { + if (el.hasAttribute(INTERACTIVE_ARIA_ATTRS[i])) return true + } + return false + } + function isInteractiveCandidate(element) { if (!element || element.nodeType !== Node.ELEMENT_NODE) return false @@ -1167,7 +1193,7 @@ export default ( element.hasAttribute('onclick') || element.hasAttribute('role') || element.hasAttribute('tabindex') || - element.hasAttribute('aria-') || + hasInteractiveAria(element) || element.hasAttribute('data-action') || element.getAttribute('contenteditable') === 'true' diff --git a/packages/page-controller/src/mask/SimulatorMask.ts b/packages/page-controller/src/mask/SimulatorMask.ts index eb13eb6..41cbdb4 100644 --- a/packages/page-controller/src/mask/SimulatorMask.ts +++ b/packages/page-controller/src/mask/SimulatorMask.ts @@ -5,7 +5,7 @@ import { isPageDark } from './checkDarkMode' import styles from './SimulatorMask.module.css' import cursorStyles from './cursor.module.css' -export class SimulatorMask { +export class SimulatorMask extends EventTarget { shown: boolean = false wrapper = document.createElement('div') motion: Motion | null = null @@ -19,6 +19,8 @@ export class SimulatorMask { #targetCursorY = 0 constructor() { + super() + this.wrapper.id = 'page-agent-runtime_simulator-mask' this.wrapper.className = styles.wrapper this.wrapper.setAttribute('data-browser-use-ignore', 'true') @@ -74,13 +76,34 @@ export class SimulatorMask { this.#moveCursorToTarget() - window.addEventListener('PageAgent::MovePointerTo', (event: Event) => { + // global events + // @note Mask should be isolated from the rest of the code. + // Global events are easier to manage and cleanup. + + const movePointerToListener = (event: Event) => { const { x, y } = (event as CustomEvent).detail this.setCursorPosition(x, y) - }) - - window.addEventListener('PageAgent::ClickPointer', (event: Event) => { + } + const clickPointerListener = () => { this.triggerClickAnimation() + } + const enablePassThroughListener = () => { + this.wrapper.style.pointerEvents = 'none' + } + const disablePassThroughListener = () => { + this.wrapper.style.pointerEvents = 'auto' + } + + window.addEventListener('PageAgent::MovePointerTo', movePointerToListener) + window.addEventListener('PageAgent::ClickPointer', clickPointerListener) + window.addEventListener('PageAgent::EnablePassThrough', enablePassThroughListener) + window.addEventListener('PageAgent::DisablePassThrough', disablePassThroughListener) + + this.addEventListener('dispose', () => { + window.removeEventListener('PageAgent::MovePointerTo', movePointerToListener) + window.removeEventListener('PageAgent::ClickPointer', clickPointerListener) + window.removeEventListener('PageAgent::EnablePassThrough', enablePassThroughListener) + window.removeEventListener('PageAgent::DisablePassThrough', disablePassThroughListener) }) } @@ -177,7 +200,9 @@ export class SimulatorMask { } dispose() { + console.log('dispose SimulatorMask') this.motion?.dispose() this.wrapper.remove() + this.dispatchEvent(new Event('dispose')) } } diff --git a/packages/page-controller/src/utils/index.ts b/packages/page-controller/src/utils/index.ts index 7f651a2..885357d 100644 --- a/packages/page-controller/src/utils/index.ts +++ b/packages/page-controller/src/utils/index.ts @@ -48,15 +48,33 @@ export async function waitFor(seconds: number): Promise { await new Promise((resolve) => setTimeout(resolve, seconds * 1000)) } -// ======= dom utils ======= +// ======= mask events ======= -export async function movePointerToElement(element: HTMLElement) { - const rect = element.getBoundingClientRect() +/** + * Move the visual pointer to a position within an element. + * @param x - x coordinate in the element's document viewport + * @param y - y coordinate in the element's document viewport + */ +export async function movePointerToElement(element: HTMLElement, x: number, y: number) { 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 } })) + window.dispatchEvent( + new CustomEvent('PageAgent::MovePointerTo', { + detail: { x: x + offset.x, y: y + offset.y }, + }) + ) await waitFor(0.3) } + +export async function clickPointer() { + window.dispatchEvent(new CustomEvent('PageAgent::ClickPointer')) +} + +export async function enablePassThrough() { + window.dispatchEvent(new CustomEvent('PageAgent::EnablePassThrough')) +} + +export async function disablePassThrough() { + window.dispatchEvent(new CustomEvent('PageAgent::DisablePassThrough')) +} diff --git a/packages/ui/package.json b/packages/ui/package.json index 491ce15..3a3af26 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@page-agent/ui", - "version": "1.6.0", + "version": "1.7.0", "type": "module", "main": "./dist/lib/page-agent-ui.js", "module": "./dist/lib/page-agent-ui.js", diff --git a/packages/ui/src/panel/Panel.ts b/packages/ui/src/panel/Panel.ts index ab5c430..ae15473 100644 --- a/packages/ui/src/panel/Panel.ts +++ b/packages/ui/src/panel/Panel.ts @@ -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 { diff --git a/packages/website/package.json b/packages/website/package.json index bcb8f46..37e99b1 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -1,7 +1,7 @@ { "name": "@page-agent/website", "private": true, - "version": "1.6.0", + "version": "1.7.0", "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.7.0", + "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.14.0", "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.1.14", diff --git a/packages/website/src/components/LanguageSwitcher.tsx b/packages/website/src/components/LanguageSwitcher.tsx index e5af95d..8c73eff 100644 --- a/packages/website/src/components/LanguageSwitcher.tsx +++ b/packages/website/src/components/LanguageSwitcher.tsx @@ -8,8 +8,8 @@ export default function LanguageSwitcher() { const dropdownRef = useRef(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] diff --git a/packages/website/src/constants.ts b/packages/website/src/constants.ts index 4d7c5f1..d050458 100644 --- a/packages/website/src/constants.ts +++ b/packages/website/src/constants.ts @@ -1,8 +1,8 @@ // Demo build (auto-init with demo LLM, for quick testing) export const CDN_DEMO_URL = - 'https://cdn.jsdelivr.net/npm/page-agent@1.6.0/dist/iife/page-agent.demo.js' + 'https://cdn.jsdelivr.net/npm/page-agent@1.7.0/dist/iife/page-agent.demo.js' export const CDN_DEMO_CN_URL = - 'https://registry.npmmirror.com/page-agent/1.6.0/files/dist/iife/page-agent.demo.js' + 'https://registry.npmmirror.com/page-agent/1.7.0/files/dist/iife/page-agent.demo.js' // Demo LLM for website testing (homepage quick trial uses flash) export const DEMO_MODEL = 'qwen3.5-flash' diff --git a/packages/website/src/pages/docs/Layout.tsx b/packages/website/src/pages/docs/Layout.tsx index e9abee8..44292e3 100644 --- a/packages/website/src/pages/docs/Layout.tsx +++ b/packages/website/src/pages/docs/Layout.tsx @@ -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', diff --git a/packages/website/src/pages/docs/advanced/page-agent/page.tsx b/packages/website/src/pages/docs/advanced/page-agent/page.tsx index 7428223..a0e2471 100644 --- a/packages/website/src/pages/docs/advanced/page-agent/page.tsx +++ b/packages/website/src/pages/docs/advanced/page-agent/page.tsx @@ -100,7 +100,7 @@ console.log(result.history) // Full execution history`} > AgentConfig {' '} - 和{' '} + 、 PanelConfig 和{' '} AgentConfig {' '} - and{' '} + , PanelConfig, and{' '} void onActivity?: (activity: AgentActivity) => void onHistoryUpdate?: (history: HistoricalEvent[]) => void @@ -233,6 +235,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) @@ -248,6 +251,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) diff --git a/packages/website/src/pages/docs/features/mcp-server/page.tsx b/packages/website/src/pages/docs/features/mcp-server/page.tsx new file mode 100644 index 0000000..b31454f --- /dev/null +++ b/packages/website/src/pages/docs/features/mcp-server/page.tsx @@ -0,0 +1,70 @@ +import BetaNotice from '@/components/BetaNotice' +import CodeEditor from '@/components/CodeEditor' +import { Heading } from '@/components/Heading' + +export default function McpServerPage() { + return ( +
+

MCP Server (Beta)

+ +

+ Use the MCP server to let your local agent send natural-language browser tasks to Page Agent + Ext. +

+ +
+ + How to use + +
+
+

+ 1. Install Page Agent Ext in Chrome. +
+ 2. Add the MCP server to your local agent client. +
+ 3. Start the client and approve the Hub connection in the browser when prompted. +
+ 4. Ask your agent to do something in the browser. The client will call execute_task + for you. +

+
+ + +
+
+ +
+ + The Hub + + +

+ The Hub is the control center for communication between Page Agent Ext and external + callers. +

+

+ 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. +

+
+
+ ) +} diff --git a/packages/website/src/pages/docs/features/models/page.tsx b/packages/website/src/pages/docs/features/models/page.tsx index 2dc0c37..53242ec 100644 --- a/packages/website/src/pages/docs/features/models/page.tsx +++ b/packages/website/src/pages/docs/features/models/page.tsx @@ -9,6 +9,7 @@ const BASELINE = new Set([ 'claude-haiku-4.5', 'gemini-3-flash', 'deepseek-3.2', + 'qwen3.6-plus', 'qwen3.5-plus', 'qwen3.5-flash', ]) @@ -16,6 +17,7 @@ const BASELINE = new Set([ // Models grouped by brand, newest first const MODEL_GROUPS: Record = { Qwen: [ + 'qwen3.6-plus', 'qwen3.5-plus', 'qwen3.5-flash', 'qwen3-coder-next', @@ -33,8 +35,8 @@ const MODEL_GROUPS: Record = { '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'], } @@ -181,7 +183,7 @@ const pageAgent = new PageAgent({

+ + + + + diff --git a/packages/website/src/pages/home/OneMoreThingSection.tsx b/packages/website/src/pages/home/OneMoreThingSection.tsx index 10e338b..767de48 100644 --- a/packages/website/src/pages/home/OneMoreThingSection.tsx +++ b/packages/website/src/pages/home/OneMoreThingSection.tsx @@ -58,6 +58,22 @@ export default function OneMoreThingSection() { +
+

+ {isZh + ? '从 Claude Desktop、Copilot 或其他本地 Agent 直接发起浏览器任务?' + : 'Using Claude Desktop, Copilot, or another local agent? Connect it to the extension with the MCP server.'} +

+

+ + {isZh ? '查看 MCP 文档' : 'Read the MCP docs'} + +

+
+
{[ { @@ -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) => (