Compare commits

..

44 Commits

Author SHA1 Message Date
Simon 088f002583 chore(ext): bump version 2026-03-07 03:16:47 +08:00
Simon 27e9e78636 chore(version): bump version to 1.5.2 2026-03-07 03:08:33 +08:00
Simon a0c979602e feat: do not throw for webgl2 fail 2026-03-07 03:07:21 +08:00
Simon 3dc9edb8b5 fix(ext): ts types 2026-03-06 17:39:09 +08:00
Simon 32b68ab7c8 fix(docs): broken demo video 2026-03-06 16:07:22 +08:00
Simon 75a61c05c1 docs(terms): wording
Clarify the note regarding data processing location for the demo.
2026-03-06 04:33:03 +08:00
Simon bdfa98358b fix: requestIdleCallback on safari 2026-03-06 02:35:01 +08:00
Simon dc8b38ccb9 chore(ext): bump extension version to 0.1.11 2026-03-05 20:48:00 +08:00
Simon e1c288aaeb docs(changelog): 1.5.1 2026-03-05 20:44:26 +08:00
Simon 7a97de2a37 feat(ext): expose more config 2026-03-05 20:34:55 +08:00
Simon 19b03b83ea chore(version): bump version to 1.5.1 2026-03-05 19:28:19 +08:00
Simon 598b144d06 feat: change maxSteps to 40 2026-03-05 19:26:56 +08:00
Simon 1cd8a8de60 Merge pull request #158 from alibaba/fix/zod-v3
feat: import zod properly
2026-03-05 19:23:47 +08:00
Simon 9beffca985 docs: update doc for Zod v4 support 2026-03-05 19:15:03 +08:00
Simon 53db2069ce feat: zod support v3/4 2026-03-05 19:13:18 +08:00
Simon 121104e13d feat: truncate llms.txt results 2026-03-05 16:59:08 +08:00
Simon 0dacbda9da chore: improve logging 2026-03-05 16:54:41 +08:00
Simon 8bc27414a4 chore(version): bump version to 1.4.4 2026-03-05 16:25:16 +08:00
Simon 76509d93f0 feat: replace data-browser-use-ignore with data-page-agent-ignore 2026-03-05 16:24:50 +08:00
Simon ff31b0c03e chore: adjust website structure 2026-03-05 16:21:58 +08:00
Simon 6b2ab73d65 docs: model recommendation 2026-03-05 16:10:03 +08:00
Simon 2754023a80 chore(version): bump version to 1.4.3 2026-03-04 21:18:51 +08:00
Simon fa5c65db53 docs: update custom-tool docs 2026-03-04 21:17:32 +08:00
Simon 53c2c19d69 docs: update advanced docs; rm best-practice 2026-03-04 21:13:10 +08:00
Simon e5437b445a fix(core): Core and PageAgent use different config types; improve code structure 2026-03-04 20:05:48 +08:00
Simon 09bdf9ddaf feat(core): experimental support for llms.txt (#157)
* feat(core): experimental support for llms.txt

* docs: experimentalLlmsTxt
2026-03-04 18:53:24 +08:00
Simon b58d2a09ef Merge pull request #152 from alibaba/dependabot/npm_and_yarn/development-dependencies-7e2b43fd30
chore(deps-dev): bump the development-dependencies group with 7 updates
2026-03-03 22:04:35 +08:00
Simon ce442742ba Merge pull request #153 from alibaba/dependabot/npm_and_yarn/vite-plugin-css-injected-by-js-4.0.1
chore(deps-dev): bump vite-plugin-css-injected-by-js from 3.5.2 to 4.0.1
2026-03-03 22:04:17 +08:00
Simon 632f22962e docs: add recommended startup parameters for Ollama 2026-03-03 21:59:02 +08:00
dependabot[bot] 72c3b12fb4 chore(deps-dev): bump the development-dependencies group with 7 updates
Bumps the development-dependencies group with 7 updates:

| Package | From | To |
| --- | --- | --- |
| [@microsoft/api-extractor](https://github.com/microsoft/rushstack/tree/HEAD/apps/api-extractor) | `7.57.3` | `7.57.6` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.3.0` | `25.3.3` |
| [globals](https://github.com/sindresorhus/globals) | `17.3.0` | `17.4.0` |
| [lint-staged](https://github.com/lint-staged/lint-staged) | `16.2.7` | `16.3.1` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `0.575.0` | `0.576.0` |
| [motion](https://github.com/motiondivision/motion) | `12.34.3` | `12.34.4` |
| [simple-icons](https://github.com/simple-icons/simple-icons) | `16.9.0` | `16.10.0` |


Updates `@microsoft/api-extractor` from 7.57.3 to 7.57.6
- [Changelog](https://github.com/microsoft/rushstack/blob/main/apps/api-extractor/CHANGELOG.md)
- [Commits](https://github.com/microsoft/rushstack/commits/@microsoft/api-extractor_v7.57.6/apps/api-extractor)

Updates `@types/node` from 25.3.0 to 25.3.3
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `globals` from 17.3.0 to 17.4.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.3.0...v17.4.0)

Updates `lint-staged` from 16.2.7 to 16.3.1
- [Release notes](https://github.com/lint-staged/lint-staged/releases)
- [Changelog](https://github.com/lint-staged/lint-staged/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lint-staged/lint-staged/compare/v16.2.7...v16.3.1)

Updates `lucide-react` from 0.575.0 to 0.576.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/0.576.0/packages/lucide-react)

Updates `motion` from 12.34.3 to 12.34.4
- [Changelog](https://github.com/motiondivision/motion/blob/main/CHANGELOG.md)
- [Commits](https://github.com/motiondivision/motion/compare/v12.34.3...v12.34.4)

Updates `simple-icons` from 16.9.0 to 16.10.0
- [Release notes](https://github.com/simple-icons/simple-icons/releases)
- [Commits](https://github.com/simple-icons/simple-icons/compare/16.9.0...16.10.0)

---
updated-dependencies:
- dependency-name: "@microsoft/api-extractor"
  dependency-version: 7.57.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@types/node"
  dependency-version: 25.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: globals
  dependency-version: 17.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: lint-staged
  dependency-version: 16.3.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: lucide-react
  dependency-version: 0.576.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: motion
  dependency-version: 12.34.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: simple-icons
  dependency-version: 16.10.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-03 13:58:04 +00:00
Simon 4807d550a3 chore(version): bump version to 1.4.2 2026-03-03 21:54:41 +08:00
Simon f9722f0619 Merge branch 'main' of https://github.com/alibaba/page-agent 2026-03-03 21:53:35 +08:00
Simon e1557a5d2e Merge pull request #156 from hobostay/main
fix(actions): remove debug console.log statements
2026-03-03 21:53:05 +08:00
Simon 06280c2ba5 chore(core): add wait time between steps for the page to react 2026-03-03 21:51:10 +08:00
Simon 645a7ceb52 feat(website): loading time optimize 2026-03-03 21:38:29 +08:00
hobostay a47c72ce84 fix(actions): remove debug console.log statements
Remove [SCROLL DEBUG] console.log statements that were left over
from development in scrollVertically and scrollHorizontally
functions. These debug statements should not be in production code.
2026-03-03 21:36:59 +08:00
Simon dcc6cd1cf3 feat(website): reduce white screen time 2026-03-03 21:05:16 +08:00
Simon b4b9f6ce2d chore(version): bump version to 1.4.1 2026-03-03 20:56:24 +08:00
Simon 14e31e6ec4 Merge pull request #155 from alibaba/fix/panel-time
fix(panel): remove timestamp (not well defined)
2026-03-03 20:55:26 +08:00
Simon 69f2478fd7 fix(panel): remove timestamp (not well defined) 2026-03-03 20:52:55 +08:00
dependabot[bot] 5fec6846b0 chore(deps-dev): bump vite-plugin-css-injected-by-js from 3.5.2 to 4.0.1
Bumps [vite-plugin-css-injected-by-js](https://github.com/marco-prontera/vite-plugin-css-injected-by-js) from 3.5.2 to 4.0.1.
- [Release notes](https://github.com/marco-prontera/vite-plugin-css-injected-by-js/releases)
- [Commits](https://github.com/marco-prontera/vite-plugin-css-injected-by-js/compare/v3.5.2...v4.0.1)

---
updated-dependencies:
- dependency-name: vite-plugin-css-injected-by-js
  dependency-version: 4.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 21:00:45 +00:00
Simon fe792f1ceb chore(docs): remove description files 2026-03-02 17:36:10 +08:00
Simon d1d27a76de style: homepage misc 2026-02-27 22:24:55 +08:00
Simon 3e7e99420a docs: changelog 2026-02-27 22:05:55 +08:00
64 changed files with 1319 additions and 1042 deletions
+3 -3
View File
@@ -13,7 +13,7 @@ The GUI Agent Living in Your Webpage. Control web interfaces with natural langua
👉 <a href="https://alibaba.github.io/page-agent/" target="_blank"><b>🚀 Demo</b></a> | <a href="https://alibaba.github.io/page-agent/docs/introduction/overview" target="_blank"><b>📖 Documentation</b></a>
<video id="demo-video" src="https://github.com/user-attachments/assets/11aed429-b69c-49d5-8982-fa99b4a0e9a8" controls crossorigin muted></video>
<video id="demo-video" src="https://github.com/user-attachments/assets/a1f2eae2-13fb-4aae-98cf-a3fc1620a6c2" controls crossorigin muted></video>
---
@@ -49,8 +49,8 @@ Fastest way to try PageAgent with our free Demo LLM:
| Mirrors | URL |
| ------- | ---------------------------------------------------------------------------------- |
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.4.0/dist/iife/page-agent.demo.js |
| China | https://registry.npmmirror.com/page-agent/1.4.0/files/dist/iife/page-agent.demo.js |
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.5.2/dist/iife/page-agent.demo.js |
| China | https://registry.npmmirror.com/page-agent/1.5.2/files/dist/iife/page-agent.demo.js |
> **⚠️ For technical evaluation only.** This demo CDN uses our free [testing LLM API](https://alibaba.github.io/page-agent/docs/features/models#free-testing-api). By using it, you agree to its [terms](https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md).
+59 -1
View File
@@ -5,6 +5,63 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.5.1] - 2026-03-05
### Breaking Changes
- **`data-browser-use-ignore``data-page-agent-ignore`** - DOM ignore attribute renamed to match the project identity
- **Config types restructured** - `PageAgentConfig` split into `AgentConfig` + `PageAgentCoreConfig`; config definitions moved from `config/index.ts` to `types.ts`
- **Zod v3/v4 dual support** - Libraries now accept both `zod@^3.25` and `zod@^4.0` as peer dependencies
### Features
- **Experimental `llms.txt` support** - Agent can fetch and include a site's `llms.txt` in context. Enable via `experimentalLlmsTxt: true`
### Improvements
- Default `maxSteps` changed from 20 to 40 for better for complex tasks out of the box
- Added 400ms wait between agent steps for page reactions
- Increased click wait time (100ms → 200ms) for more reliable interactions
- Removed debug `console.log` statements from scroll actions
- Reset observations on new task start
- Improved logging across packages
### Extension v0.1.9
> PageAgent 1.5.1
- **Advanced config panel** - New collapsible section exposing Max Steps, System Instruction, and experimental `llms.txt` toggle
- Streamlined User Auth Token description
- Moved testing API notice below auth token section
---
## [1.4.0] - 2026-02-27
### Features
- Update Terms of Use and Privacy Policy
- **Robust tool-call validation** - Action inputs are now validated against tool schemas individually, producing clear error messages (e.g. `Invalid input for action "click_element_by_index"`) instead of unreadable union parse errors
- **Primitive action input coercion** - Small models that output `{"click_element_by_index": 2}` instead of `{"click_element_by_index": {"index": 2}}` are now auto-corrected using tool schemas
- **Qwen model updates** - Added `qwen3.5-plus` as the default free testing model; disabled `enable_thinking` for Qwen models to avoid incompatible responses
- **Updated default LLM endpoint** - Migrated demo and extension to a new testing endpoint with legacy endpoint auto-migration
### Improvements
- Unified zod imports (`* as z`) across all packages for consistency
- Better Zod error formatting with `z.prettifyError()` in LLM client
- Exported `InvokeError` and `InvokeErrorType` as values (not just types) from `@page-agent/llms`
- Exported `SupportedLanguage` type from `@page-agent/core`
### Extension v0.1.8
- **Language setting** - Added language selector (System / English / 中文) in config panel
- **UI makeover** - New empty state with breathing glow and typing animation; ai-motion glow overlay while running; refined focus styles
- **Testing endpoint notice** - Shows terms of use notice when using the free testing API
- **Legacy endpoint migration** - Auto-migrates old Supabase testing endpoint to new endpoint on startup
---
## [1.3.0] - 2026-02-13
### Breaking Changes
@@ -96,9 +153,10 @@ PageAgent is now ready for production use. The API is stable and breaking change
- **Ask User Tool** - Agent can ask users for clarification
- **i18n Support** - English and Chinese localization
### Configuration
### Configuration
```typescript
// Version 1.0.0
interface PageAgentConfig {
// LLM Configuration (required)
baseURL: string
+3 -3
View File
@@ -13,7 +13,7 @@
👉 <a href="https://alibaba.github.io/page-agent/" target="_blank"><b>🚀 Demo</b></a> | <a href="https://alibaba.github.io/page-agent/docs/introduction/overview" target="_blank"><b>📖 Documentation</b></a>
<video id="demo-video" src="https://github.com/user-attachments/assets/11aed429-b69c-49d5-8982-fa99b4a0e9a8" controls crossorigin muted></video>
<video id="demo-video" src="https://github.com/user-attachments/assets/a1f2eae2-13fb-4aae-98cf-a3fc1620a6c2" controls crossorigin muted></video>
---
@@ -49,8 +49,8 @@
| Mirrors | URL |
| ------- | ---------------------------------------------------------------------------------- |
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.4.0/dist/iife/page-agent.demo.js |
| China | https://registry.npmmirror.com/page-agent/1.4.0/files/dist/iife/page-agent.demo.js |
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.5.2/dist/iife/page-agent.demo.js |
| China | https://registry.npmmirror.com/page-agent/1.5.2/files/dist/iife/page-agent.demo.js |
> **⚠️ 仅用于技术评估。** 该 Demo CDN 使用了免费的[测试 LLM API](https://alibaba.github.io/page-agent/docs/features/models#free-testing-api),使用即表示您同意其[条款](https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md)。
+1 -1
View File
@@ -36,7 +36,7 @@ This free testing API is provided **strictly for technical evaluation and R&D pu
**Recommendation for Real Usage**: For secure and continuous usage, we strongly advise using the default BYOK mode with your own legally compliant commercial LLM API keys, or connecting to local, offline models (e.g., Ollama).
**Note**: This service processes data via servers located in Mainland China. If you are located in a region with strict data localization laws (such as the EU/EEA), please do not use this demo.
**Note**: This testing LLM api processes data via servers located in Mainland China. If you are located in a region with strict data localization laws (such as the EU/EEA), please do not use this demo.
---
+120 -151
View File
@@ -1,12 +1,12 @@
{
"name": "root",
"version": "1.4.0",
"version": "1.5.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "root",
"version": "1.4.0",
"version": "1.5.2",
"license": "MIT",
"workspaces": [
"packages/page-controller",
@@ -21,10 +21,10 @@
"@commitlint/cli": "^20.4.2",
"@commitlint/config-conventional": "^20.4.2",
"@eslint/js": "^9.39.2",
"@microsoft/api-extractor": "^7.57.3",
"@microsoft/api-extractor": "^7.57.6",
"@tailwindcss/vite": "^4.2.1",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
"@types/node": "^25.3.0",
"@types/node": "^25.3.3",
"@vitejs/plugin-react-swc": "^4.1.0",
"chalk": "^5.6.2",
"concurrently": "^9.2.1",
@@ -35,16 +35,16 @@
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"eslint-plugin-react-x": "^2.13.0",
"globals": "^17.0.0",
"globals": "^17.4.0",
"husky": "^9.1.7",
"lint-staged": "^16.2.4",
"lint-staged": "^16.3.1",
"prettier": "^3.8.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.56.1",
"unplugin-dts": "^1.0.0-beta.6",
"vite": "^7.3.1",
"vite-bundle-analyzer": "^1.3.6",
"vite-plugin-css-injected-by-js": "^3.5.2"
"vite-plugin-css-injected-by-js": "^4.0.1"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
@@ -1685,19 +1685,19 @@
}
},
"node_modules/@microsoft/api-extractor": {
"version": "7.57.3",
"resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.57.3.tgz",
"integrity": "sha512-2+k2FNp+6zug/VmpK0wZFPQu7cKMihjHBP1iUuZs6Ep5P9uR1hD4dR5Ss7z/MiBkwVmKnUm6Pojhkz/c431SMw==",
"version": "7.57.6",
"resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.57.6.tgz",
"integrity": "sha512-0rFv/D8Grzw1Mjs2+8NGUR+o4h9LVm5zKRtMeWnpdB5IMJF4TeHCL1zR5LMCIudkOvyvjbhMG5Wjs0B5nqsrRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@microsoft/api-extractor-model": "7.33.1",
"@microsoft/api-extractor-model": "7.33.4",
"@microsoft/tsdoc": "~0.16.0",
"@microsoft/tsdoc-config": "~0.18.0",
"@rushstack/node-core-library": "5.20.1",
"@rushstack/rig-package": "0.7.1",
"@rushstack/terminal": "0.22.1",
"@rushstack/ts-command-line": "5.3.1",
"@microsoft/tsdoc-config": "~0.18.1",
"@rushstack/node-core-library": "5.20.3",
"@rushstack/rig-package": "0.7.2",
"@rushstack/terminal": "0.22.3",
"@rushstack/ts-command-line": "5.3.3",
"diff": "~8.0.2",
"lodash": "~4.17.23",
"minimatch": "10.2.1",
@@ -1711,15 +1711,15 @@
}
},
"node_modules/@microsoft/api-extractor-model": {
"version": "7.33.1",
"resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.33.1.tgz",
"integrity": "sha512-KX0LI6xzI0gcBOXXmr5mnnbdhsK2W93pqvJo8OgJgWvRRh+wMEp0Ccj38h1XKeJ29E1tuAZKSUOfHUQ1WA8fZg==",
"version": "7.33.4",
"resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.33.4.tgz",
"integrity": "sha512-u1LTaNTikZAQ9uK6KG1Ms7nvNedsnODnspq/gH2dcyETWvH4hVNGNDvRAEutH66kAmxA4/necElqGNs1FggC8w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@microsoft/tsdoc": "~0.16.0",
"@microsoft/tsdoc-config": "~0.18.0",
"@rushstack/node-core-library": "5.20.1"
"@microsoft/tsdoc-config": "~0.18.1",
"@rushstack/node-core-library": "5.20.3"
}
},
"node_modules/@microsoft/api-extractor/node_modules/lru-cache": {
@@ -1766,35 +1766,18 @@
"license": "MIT"
},
"node_modules/@microsoft/tsdoc-config": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.18.0.tgz",
"integrity": "sha512-8N/vClYyfOH+l4fLkkr9+myAoR6M7akc8ntBJ4DJdWH2b09uVfr71+LTMpNyG19fNqWDg8KEDZhx5wxuqHyGjw==",
"version": "0.18.1",
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.18.1.tgz",
"integrity": "sha512-9brPoVdfN9k9g0dcWkFeA7IH9bbcttzDJlXvkf8b2OBzd5MueR1V2wkKBL0abn0otvmkHJC6aapBOTJDDeMCZg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@microsoft/tsdoc": "0.16.0",
"ajv": "~8.12.0",
"ajv": "~8.18.0",
"jju": "~1.4.0",
"resolve": "~1.22.2"
}
},
"node_modules/@microsoft/tsdoc-config/node_modules/ajv": {
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -2948,13 +2931,13 @@
]
},
"node_modules/@rushstack/node-core-library": {
"version": "5.20.1",
"resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.20.1.tgz",
"integrity": "sha512-QvxZyh+RsTJ77JpQkS9K9lJujh6lj5WyMxieT0bdACtwqxEkGB9zCuSMX5UlXRweaIgSpu1ztdHmhV07fKUpMg==",
"version": "5.20.3",
"resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.20.3.tgz",
"integrity": "sha512-95JgEPq2k7tHxhF9/OJnnyHDXfC9cLhhta0An/6MlkDsX2A6dTzDrTUG18vx4vjc280V0fi0xDH9iQczpSuWsw==",
"dev": true,
"license": "MIT",
"dependencies": {
"ajv": "~8.13.0",
"ajv": "~8.18.0",
"ajv-draft-04": "~1.0.0",
"ajv-formats": "~3.0.1",
"fs-extra": "~11.3.0",
@@ -2972,23 +2955,6 @@
}
}
},
"node_modules/@rushstack/node-core-library/node_modules/ajv": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz",
"integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.4.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@rushstack/node-core-library/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -3041,9 +3007,9 @@
}
},
"node_modules/@rushstack/rig-package": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.7.1.tgz",
"integrity": "sha512-hLwDnp4yMcAd/gcUol8NPWNctpIXzVOgMyhZ8DagnEJls9TOZd0xF//5hS+YTiX7/+4rLfBra+NoB3rtFxjDdA==",
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.7.2.tgz",
"integrity": "sha512-9XbFWuqMYcHUso4mnETfhGVUSaADBRj6HUAAEYk50nMPn8WRICmBuCphycQGNB3duIR6EEZX3Xj3SYc2XiP+9A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3052,13 +3018,13 @@
}
},
"node_modules/@rushstack/terminal": {
"version": "0.22.1",
"resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.22.1.tgz",
"integrity": "sha512-Mdtu0VN7v31O5Zcno8ZZH5kQHF13Ez7WN9Aio7nFJVcR36i4bkERionYrWgBDQJ0JdVPLKGecZER/xRU5IvGLw==",
"version": "0.22.3",
"resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.22.3.tgz",
"integrity": "sha512-gHC9pIMrUPzAbBiI4VZMU7Q+rsCzb8hJl36lFIulIzoceKotyKL3Rd76AZ2CryCTKEg+0bnTj406HE5YY5OQvw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rushstack/node-core-library": "5.20.1",
"@rushstack/node-core-library": "5.20.3",
"@rushstack/problem-matcher": "0.2.1",
"supports-color": "~8.1.1"
},
@@ -3072,13 +3038,13 @@
}
},
"node_modules/@rushstack/ts-command-line": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.3.1.tgz",
"integrity": "sha512-mid/JIZSJafwy3x9e4v0wVLuAqSSYYErEHV0HXPALYLSBN13YNkR5caOk0hf97lSRKrxhtvQjGaDKSEelR3sMg==",
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.3.3.tgz",
"integrity": "sha512-c+ltdcvC7ym+10lhwR/vWiOhsrm/bP3By2VsFcs5qTKv+6tTmxgbVrtJ5NdNjANiV5TcmOZgUN+5KYQ4llsvEw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rushstack/terminal": "0.22.1",
"@rushstack/terminal": "0.22.3",
"@types/argparse": "1.0.38",
"argparse": "~1.0.9",
"string-argv": "~0.3.1"
@@ -3822,9 +3788,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "25.3.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz",
"integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==",
"version": "25.3.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz",
"integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4309,9 +4275,9 @@
"license": "MIT"
},
"node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6436,13 +6402,13 @@
}
},
"node_modules/framer-motion": {
"version": "12.34.3",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.34.3.tgz",
"integrity": "sha512-v81ecyZKYO/DfpTwHivqkxSUBzvceOpoI+wLfgCgoUIKxlFKEXdg0oR9imxwXumT4SFy8vRk9xzJ5l3/Du/55Q==",
"version": "12.34.5",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.34.5.tgz",
"integrity": "sha512-Z2dQ+o7BsfpJI3+u0SQUNCrN+ajCKJen1blC4rCHx1Ta2EOHs+xKJegLT2aaD9iSMbU3OoX+WabQXkloUbZmJQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"motion-dom": "^12.34.3",
"motion-dom": "^12.34.5",
"motion-utils": "^12.29.2",
"tslib": "^2.4.0"
},
@@ -6688,9 +6654,9 @@
}
},
"node_modules/globals": {
"version": "17.3.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz",
"integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==",
"version": "17.4.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz",
"integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -7775,19 +7741,18 @@
}
},
"node_modules/lint-staged": {
"version": "16.2.7",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz",
"integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==",
"version": "16.3.1",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.3.1.tgz",
"integrity": "sha512-bqvvquXzFBAlSbluugR4KXAe4XnO/QZcKVszpkBtqLWa2KEiVy8n6Xp38OeUbv/gOJOX4Vo9u5pFt/ADvbm42Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"commander": "^14.0.2",
"commander": "^14.0.3",
"listr2": "^9.0.5",
"micromatch": "^4.0.8",
"nano-spawn": "^2.0.0",
"pidtree": "^0.6.0",
"string-argv": "^0.3.2",
"yaml": "^2.8.1"
"tinyexec": "^1.0.2",
"yaml": "^2.8.2"
},
"bin": {
"lint-staged": "bin/lint-staged.js"
@@ -7962,9 +7927,9 @@
}
},
"node_modules/lucide-react": {
"version": "0.575.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.575.0.tgz",
"integrity": "sha512-VuXgKZrk0uiDlWjGGXmKV6MSk9Yy4l10qgVvzGn2AWBx1Ylt0iBexKOAoA6I7JO3m+M9oeovJd3yYENfkUbOeg==",
"version": "0.576.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.576.0.tgz",
"integrity": "sha512-koNxU14BXrxUfZQ9cUaP0ES1uyPZKYDjk31FQZB6dQ/x+tXk979sVAn9ppZ/pVeJJyOxVM8j1E+8QEuSc02Vug==",
"dev": true,
"license": "ISC",
"peerDependencies": {
@@ -8156,13 +8121,13 @@
}
},
"node_modules/motion": {
"version": "12.34.3",
"resolved": "https://registry.npmjs.org/motion/-/motion-12.34.3.tgz",
"integrity": "sha512-xZIkBGO7v/Uvm+EyaqYd+9IpXu0sZqLywVlGdCFrrMiaO9JI4Kx51mO9KlHSWwll+gZUVY5OJsWgYI5FywJ/tw==",
"version": "12.34.5",
"resolved": "https://registry.npmjs.org/motion/-/motion-12.34.5.tgz",
"integrity": "sha512-N06NLJ9IeBHeielRqIvYvjPfXuRdyTxa+9++BgpGa+hY2D7TcMkI6QzV3jaRuv0aZRXgMa7cPy9YcBUBisPzAQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"framer-motion": "^12.34.3",
"framer-motion": "^12.34.5",
"tslib": "^2.4.0"
},
"peerDependencies": {
@@ -8183,9 +8148,9 @@
}
},
"node_modules/motion-dom": {
"version": "12.34.3",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.34.3.tgz",
"integrity": "sha512-sYgFe+pR9aIM7o4fhs2aXtOI+oqlUd33N9Yoxcgo1Fv7M20sRkHtCmzE/VRNIcq7uNJ+qio+Xubt1FXH3pQ+eQ==",
"version": "12.34.5",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.34.5.tgz",
"integrity": "sha512-k33CsnxO2K3gBRMUZT+vPmc4Utlb5menKdG0RyVNLtlqRaaJPRWlE9fXl8NTtfZ5z3G8TDvqSu0MENLqSTaHZA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8710,19 +8675,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pidtree": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz",
"integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==",
"dev": true,
"license": "MIT",
"bin": {
"pidtree": "bin/pidtree.js"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/pino": {
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/pino/-/pino-9.7.0.tgz",
@@ -9560,9 +9512,9 @@
}
},
"node_modules/simple-icons": {
"version": "16.9.0",
"resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-16.9.0.tgz",
"integrity": "sha512-aKst2C7cLkFyaiQ/Crlwxt9xYOpGPk05XuJZ0ZTJNNCzHCKYrGWz2ebJSi5dG8CmTCxUF/BGs6A8uyJn/EQxqw==",
"version": "16.10.0",
"resolved": "https://registry.npmjs.org/simple-icons/-/simple-icons-16.10.0.tgz",
"integrity": "sha512-62kuxaG3pE+cFNerudUtwb9BLmudzayHrlHkvU9gf8Nxcj7VYOm9dh3WNbGaFk60aQtfnRyzViZaouFG2B45kg==",
"dev": true,
"funding": [
{
@@ -10554,9 +10506,9 @@
}
},
"node_modules/vite-plugin-css-injected-by-js": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.5.2.tgz",
"integrity": "sha512-2MpU/Y+SCZyWUB6ua3HbJCrgnF0KACAsmzOQt1UvRVJCGF6S8xdA3ZUhWcWdM9ivG4I5az8PnQmwwrkC2CAQrQ==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-4.0.1.tgz",
"integrity": "sha512-WfyRojojQyAO/KzWf+efcXpTPv6zJPXaRmr9EYq5a4v5I3PWCR7kR01hiri2lW6+rHm3a57kpwsf+iahIJi1Qw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
@@ -11161,27 +11113,31 @@
},
"packages/core": {
"name": "@page-agent/core",
"version": "1.4.0",
"version": "1.5.2",
"license": "MIT",
"dependencies": {
"@page-agent/llms": "1.4.0",
"@page-agent/page-controller": "1.4.0",
"chalk": "^5.6.2",
"@page-agent/llms": "1.5.2",
"@page-agent/page-controller": "1.5.2",
"chalk": "^5.6.2"
},
"devDependencies": {
"zod": "^4.3.5"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
},
"packages/extension": {
"name": "@page-agent/ext",
"version": "0.1.8",
"version": "0.1.12",
"hasInstallScript": true,
"dependencies": {
"@page-agent/core": "1.4.0",
"@page-agent/llms": "1.4.0",
"@page-agent/page-controller": "1.4.0",
"@page-agent/ui": "1.4.0",
"@page-agent/core": "1.5.2",
"@page-agent/llms": "1.5.2",
"@page-agent/page-controller": "1.5.2",
"@page-agent/ui": "1.5.2",
"ai-motion": "^0.4.8",
"chalk": "^5.6.2",
"zod": "^4.3.5"
"chalk": "^5.6.2"
},
"devDependencies": {
"@radix-ui/react-hover-card": "^1.1.15",
@@ -11198,44 +11154,57 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"idb": "^8.0.3",
"lucide-react": "^0.575.0",
"motion": "^12.34.3",
"lucide-react": "^0.576.0",
"motion": "^12.34.5",
"next-themes": "^0.4.6",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"rough-notation": "^0.5.1",
"simple-icons": "^16.9.0",
"simple-icons": "^16.10.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.1.14",
"tw-animate-css": "^1.4.0",
"wxt": "^0.20.18"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
},
"packages/llms": {
"name": "@page-agent/llms",
"version": "1.4.0",
"version": "1.5.2",
"license": "MIT",
"dependencies": {
"chalk": "^5.6.2",
"chalk": "^5.6.2"
},
"devDependencies": {
"zod": "^4.3.5"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
},
"packages/page-agent": {
"version": "1.4.0",
"version": "1.5.2",
"license": "MIT",
"dependencies": {
"@page-agent/core": "1.4.0",
"@page-agent/llms": "1.4.0",
"@page-agent/page-controller": "1.4.0",
"@page-agent/ui": "1.4.0",
"chalk": "^5.6.2",
"@page-agent/core": "1.5.2",
"@page-agent/llms": "1.5.2",
"@page-agent/page-controller": "1.5.2",
"@page-agent/ui": "1.5.2",
"chalk": "^5.6.2"
},
"devDependencies": {
"zod": "^4.3.5"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
},
"packages/page-controller": {
"name": "@page-agent/page-controller",
"version": "1.4.0",
"version": "1.5.2",
"license": "MIT",
"dependencies": {
"ai-motion": "^0.4.8"
@@ -11243,12 +11212,12 @@
},
"packages/ui": {
"name": "@page-agent/ui",
"version": "1.4.0",
"version": "1.5.2",
"license": "MIT"
},
"packages/website": {
"name": "@page-agent/website",
"version": "1.4.0",
"version": "1.5.2",
"devDependencies": {
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-separator": "^1.1.8",
@@ -11259,13 +11228,13 @@
"@types/react-dom": "^19.2.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.575.0",
"motion": "^12.34.3",
"lucide-react": "^0.576.0",
"motion": "^12.34.5",
"next-themes": "^0.4.6",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"rough-notation": "^0.5.1",
"simple-icons": "^16.9.0",
"simple-icons": "^16.10.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.1.14",
+6 -6
View File
@@ -1,7 +1,7 @@
{
"name": "root",
"private": true,
"version": "1.4.0",
"version": "1.5.2",
"type": "module",
"workspaces": [
"packages/page-controller",
@@ -38,10 +38,10 @@
"@commitlint/cli": "^20.4.2",
"@commitlint/config-conventional": "^20.4.2",
"@eslint/js": "^9.39.2",
"@microsoft/api-extractor": "^7.57.3",
"@microsoft/api-extractor": "^7.57.6",
"@tailwindcss/vite": "^4.2.1",
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
"@types/node": "^25.3.0",
"@types/node": "^25.3.3",
"@vitejs/plugin-react-swc": "^4.1.0",
"chalk": "^5.6.2",
"concurrently": "^9.2.1",
@@ -52,15 +52,15 @@
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"eslint-plugin-react-x": "^2.13.0",
"globals": "^17.0.0",
"globals": "^17.4.0",
"husky": "^9.1.7",
"lint-staged": "^16.2.4",
"lint-staged": "^16.3.1",
"prettier": "^3.8.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.56.1",
"unplugin-dts": "^1.0.0-beta.6",
"vite": "^7.3.1",
"vite-plugin-css-injected-by-js": "^3.5.2",
"vite-plugin-css-injected-by-js": "^4.0.1",
"vite-bundle-analyzer": "^1.3.6"
},
"overrides": {
+9 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@page-agent/core",
"private": false,
"version": "1.4.0",
"version": "1.5.2",
"type": "module",
"main": "./dist/esm/page-agent-core.js",
"module": "./dist/esm/page-agent-core.js",
@@ -44,8 +44,13 @@
},
"dependencies": {
"chalk": "^5.6.2",
"zod": "^4.3.5",
"@page-agent/llms": "1.4.0",
"@page-agent/page-controller": "1.4.0"
"@page-agent/llms": "1.5.2",
"@page-agent/page-controller": "1.5.2"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
},
"devDependencies": {
"zod": "^4.3.5"
}
}
+22 -14
View File
@@ -5,14 +5,13 @@
import { InvokeError, LLM, type Tool } from '@page-agent/llms'
import type { BrowserState, PageController } from '@page-agent/page-controller'
import chalk from 'chalk'
import * as z from 'zod'
import * as z from 'zod/v4'
import { type PageAgentConfig, type SupportedLanguage } from './config'
import { DEFAULT_MAX_STEPS } from './config/constants'
import SYSTEM_PROMPT from './prompts/system_prompt.md?raw'
import { tools } from './tools'
import type {
AgentActivity,
AgentConfig,
AgentReflection,
AgentStatus,
AgentStepEvent,
@@ -21,13 +20,13 @@ import type {
MacroToolInput,
MacroToolResult,
} from './types'
import { assert, normalizeResponse, uid, waitFor } from './utils'
import { assert, fetchLlmsTxt, normalizeResponse, uid, waitFor } from './utils'
export { type PageAgentConfig }
export type { SupportedLanguage }
export { tool, type PageAgentTool } from './tools'
export type * from './types'
export type PageAgentCoreConfig = AgentConfig & { pageController: PageController }
/**
* AI agent for browser automation.
*
@@ -60,7 +59,7 @@ export type * from './types'
*/
export class PageAgentCore extends EventTarget {
readonly id = uid()
readonly config: PageAgentConfig & { maxSteps: number }
readonly config: PageAgentCoreConfig & { maxSteps: number }
readonly tools: typeof tools
/** PageController for DOM operations */
readonly pageController: PageController
@@ -94,10 +93,10 @@ export class PageAgentCore extends EventTarget {
browserState: null as BrowserState | null,
}
constructor(config: PageAgentConfig & { pageController: PageController }) {
constructor(config: PageAgentCoreConfig) {
super()
this.config = { ...config, maxSteps: config.maxSteps || DEFAULT_MAX_STEPS }
this.config = { ...config, maxSteps: config.maxSteps ?? 40 }
this.#llm = new LLM(this.config)
this.tools = new Map(tools)
@@ -222,6 +221,7 @@ export class PageAgentCore extends EventTarget {
this.history = []
this.#setStatus('running')
this.#emitHistoryChange()
this.#observations = []
// Reset internal states
this.#states = { totalWaitTime: 0, lastURL: '', browserState: null }
@@ -342,6 +342,8 @@ export class PageAgentCore extends EventTarget {
await onAfterTask?.(this, result)
return result
}
await waitFor(0.4) // @TODO: configurable
}
}
@@ -460,14 +462,13 @@ export class PageAgentCore extends EventTarget {
* Get instructions from config
*/
async #getInstructions(): Promise<string> {
const { instructions } = this.config
if (!instructions) return ''
const { instructions, experimentalLlmsTxt } = this.config
const systemInstructions = instructions.system?.trim()
const systemInstructions = instructions?.system?.trim()
let pageInstructions: string | undefined
const url = this.#states.browserState?.url || ''
if (instructions.getPageInstructions && url) {
if (instructions?.getPageInstructions && url) {
try {
pageInstructions = instructions.getPageInstructions(url)?.trim()
} catch (error) {
@@ -477,7 +478,10 @@ export class PageAgentCore extends EventTarget {
)
}
}
if (!systemInstructions && !pageInstructions) return ''
const llmsTxt = experimentalLlmsTxt && url ? await fetchLlmsTxt(url) : undefined
if (!systemInstructions && !pageInstructions && !llmsTxt) return ''
let result = '<instructions>\n'
@@ -489,6 +493,10 @@ export class PageAgentCore extends EventTarget {
result += `<page_instructions>\n${pageInstructions}\n</page_instructions>\n`
}
if (llmsTxt) {
result += `<llms_txt>\n${llmsTxt}\n</llms_txt>\n`
}
result += '</instructions>\n\n'
return result
-1
View File
@@ -1 +0,0 @@
export const DEFAULT_MAX_STEPS = 20
-152
View File
@@ -1,152 +0,0 @@
import type { LLMConfig } from '@page-agent/llms'
import type { PageControllerConfig } from '@page-agent/page-controller'
import type { PageAgentCore } from '../PageAgentCore'
import type { PageAgentTool } from '../tools'
import type { ExecutionResult, HistoricalEvent } from '../types'
export type { LLMConfig }
/** Supported UI languages */
export type SupportedLanguage = 'en-US' | 'zh-CN'
export interface AgentConfig {
// theme?: 'light' | 'dark'
language?: SupportedLanguage
/**
* Maximum number of steps the agent can take per task.
* @default 20
*/
maxSteps?: number
/**
* Custom tools to extend PageAgent capabilities
* @experimental
* @note You can also override or remove internal tools by using the same name.
* @see PageAgentTool
*
* @example
* // override internal tool
* import { tool } from 'page-agent'
* const customTools = {
* ask_user: tool({
* description:
* 'Ask the user or parent model a question and wait for their answer. Use this if you need more information or clarification.',
* inputSchema: zod.object({
* question: zod.string(),
* }),
* execute: async function (this: PageAgent, input) {
* const answer = await do_some_thing(input.question)
* return "✅ Received user answer: " + answer
* },
* })
* }
*
* @example
* // remove internal tool
* const customTools = {
* ask_user: null // never ask user questions
* }
*/
customTools?: Record<string, PageAgentTool | null>
/**
* Instructions to guide the agent's behavior
*/
instructions?: {
/**
* Global system-level instructions, applied to all tasks
*/
system?: string
/**
* Dynamic page-level instructions callback
* Called before each step to get instructions for the current page
* @param url - Current page URL (window.location.href)
* @returns Instructions string, or undefined/null to skip
*/
getPageInstructions?: (url: string) => string | undefined | null
}
/**
* Lifecycle hooks for task execution.
* @experimental API may change in future versions.
*
* All hooks receive the agent instance as first parameter.
*/
/**
* Called before each step execution.
* @experimental
* @param agent - The PageAgentCore instance
* @param stepCount - Current step number (0-indexed)
*/
onBeforeStep?: (agent: PageAgentCore, stepCount: number) => Promise<void> | void
/**
* Called after each step execution.
* @experimental
* @param agent - The PageAgentCore instance
* @param history - Current history of events
*/
onAfterStep?: (agent: PageAgentCore, history: HistoricalEvent[]) => Promise<void> | void
/**
* Called before task execution starts.
* @experimental
* @param agent - The PageAgentCore instance
*/
onBeforeTask?: (agent: PageAgentCore) => Promise<void> | void
/**
* Called after task execution completes (success or failure).
* @experimental
* @param agent - The PageAgentCore instance
* @param result - The execution result
*/
onAfterTask?: (agent: PageAgentCore, result: ExecutionResult) => Promise<void> | void
/**
* Called when the agent is disposed.
* @experimental
* @note This hook can block the disposal process if it's async.
* @param agent - The PageAgentCore instance
* @param reason - Optional reason for disposal
*/
onDispose?: (agent: PageAgentCore, reason?: string) => void
// page behavior hooks
/**
* @experimental
* Enable the experimental script execution tool that allows executing generated JavaScript code on the page.
* @note Can cause unpredictable side effects.
* @note May bypass some safe guards and data-masking mechanisms.
*/
experimentalScriptExecutionTool?: boolean
/**
* Transform page content before sending to LLM.
* Called after DOM extraction and simplification, before LLM invocation.
* Use cases: inspect extraction results, modify page info, mask sensitive data.
*
* @param content - Simplified page content that will be sent to LLM
* @returns Transformed content
*
* @example
* // Mask phone numbers
* transformPageContent: async (content) => {
* return content.replace(/1[3-9]\d{9}/g, '***********')
* }
*/
transformPageContent?: (content: string) => Promise<string> | string
/**
* Completely override the default system prompt.
* @experimental Use with caution - incorrect prompts may break agent behavior.
*/
customSystemPrompt?: string
}
export type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig
+1 -1
View File
@@ -2,7 +2,7 @@
* Internal tools for PageAgent.
* @note Adapted from browser-use
*/
import * as z from 'zod'
import * as z from 'zod/v4'
import type { PageAgentCore } from '../PageAgentCore'
import { waitFor } from '../utils'
+156
View File
@@ -1,3 +1,159 @@
import type { LLMConfig } from '@page-agent/llms'
// @note circular dependency but okay
import type { PageAgentCore } from './PageAgentCore'
import type { PageAgentTool } from './tools'
/** Supported UI languages */
export type SupportedLanguage = 'en-US' | 'zh-CN'
export interface AgentConfig extends LLMConfig {
language?: SupportedLanguage
/**
* Maximum number of steps the agent can take per task.
* @default 40
*/
maxSteps?: number
/**
* Custom tools to extend PageAgent capabilities
* @experimental
* @note You can also override or remove internal tools by using the same name.
* @see PageAgentTool
*
* @example
* // override internal tool
* import { z } from 'zod/v4'
* import { tool } from 'page-agent'
* const customTools = {
* ask_user: tool({
* description:
* 'Ask the user or parent model a question and wait for their answer. Use this if you need more information or clarification.',
* inputSchema: z.object({
* question: z.string(),
* }),
* execute: async function (this: PageAgent, input) {
* const answer = await do_some_thing(input.question)
* return "✅ Received user answer: " + answer
* },
* })
* }
*
* @example
* // remove internal tool
* const customTools = {
* ask_user: null // never ask user questions
* }
*/
customTools?: Record<string, PageAgentTool | null>
/**
* Instructions to guide the agent's behavior
*/
instructions?: {
/**
* Global system-level instructions, applied to all tasks
*/
system?: string
/**
* Dynamic page-level instructions callback
* Called before each step to get instructions for the current page
* @param url - Current page URL (window.location.href)
* @returns Instructions string, or undefined/null to skip
*/
getPageInstructions?: (url: string) => string | undefined | null
}
/**
* Lifecycle hooks for task execution.
* @experimental API may change in future versions.
*
* All hooks receive the agent instance as first parameter.
*/
/**
* Called before each step execution.
* @experimental
* @param agent - The PageAgentCore instance
* @param stepCount - Current step number (0-indexed)
*/
onBeforeStep?: (agent: PageAgentCore, stepCount: number) => Promise<void> | void
/**
* Called after each step execution.
* @experimental
* @param agent - The PageAgentCore instance
* @param history - Current history of events
*/
onAfterStep?: (agent: PageAgentCore, history: HistoricalEvent[]) => Promise<void> | void
/**
* Called before task execution starts.
* @experimental
* @param agent - The PageAgentCore instance
*/
onBeforeTask?: (agent: PageAgentCore) => Promise<void> | void
/**
* Called after task execution completes (success or failure).
* @experimental
* @param agent - The PageAgentCore instance
* @param result - The execution result
*/
onAfterTask?: (agent: PageAgentCore, result: ExecutionResult) => Promise<void> | void
/**
* Called when the agent is disposed.
* @experimental
* @note This hook can block the disposal process if it's async.
* @param agent - The PageAgentCore instance
* @param reason - Optional reason for disposal
*/
onDispose?: (agent: PageAgentCore, reason?: string) => void
// page behavior hooks
/**
* @experimental
* Enable the experimental script execution tool that allows executing generated JavaScript code on the page.
* @note Can cause unpredictable side effects.
* @note May bypass some safe guards and data-masking mechanisms.
*/
experimentalScriptExecutionTool?: boolean
/**
* @experimental
* Fetch /llms.txt from current site origin and include as context.
* Only fetched once per origin per task.
* @default false
*/
experimentalLlmsTxt?: boolean
/**
* Transform page content before sending to LLM.
* Called after DOM extraction and simplification, before LLM invocation.
* Use cases: inspect extraction results, modify page info, mask sensitive data.
*
* @param content - Simplified page content that will be sent to LLM
* @returns Transformed content
*
* @example
* // Mask phone numbers
* transformPageContent: async (content) => {
* return content.replace(/1[3-9]\d{9}/g, '***********')
* }
*/
transformPageContent?: (content: string) => Promise<string> | string
/**
* Completely override the default system prompt.
* @experimental Use with caution - incorrect prompts may break agent behavior.
*/
customSystemPrompt?: string
}
/**
* Agent reflection state - the reflection-before-action model
*
+9 -9
View File
@@ -1,9 +1,11 @@
import { InvokeError, InvokeErrorType } from '@page-agent/llms'
import chalk from 'chalk'
import * as z from 'zod'
import * as z from 'zod/v4'
import type { PageAgentTool } from '../tools'
const log = console.log.bind(console, chalk.yellow('[autoFixer]'))
/**
* Normalize LLM response and fix common format issues.
*
@@ -34,7 +36,7 @@ export function normalizeResponse(response: any, tools?: Map<string, PageAgentTo
// case: sometimes the model only returns the action level
if (toolCall.function.name && toolCall.function.name !== 'AgentOutput') {
console.log(chalk.yellow(`[normalizeResponse] #1: fixing tool_call`))
log(`#1: fixing tool_call`)
resolvedArguments = { action: safeJsonParse(resolvedArguments) }
}
} else {
@@ -47,13 +49,13 @@ export function normalizeResponse(response: any, tools?: Map<string, PageAgentTo
// case: sometimes the content json includes upper level wrapper
if (resolvedArguments?.name === 'AgentOutput') {
console.log(chalk.yellow(`[normalizeResponse] #2: fixing tool_call`))
log(`#2: fixing tool_call`)
resolvedArguments = safeJsonParse(resolvedArguments.arguments)
}
// case: sometimes even 2-levels of wrapping
if (resolvedArguments?.type === 'function') {
console.log(chalk.yellow(`[normalizeResponse] #3: fixing tool_call`))
log(`#3: fixing tool_call`)
resolvedArguments = safeJsonParse(resolvedArguments.function.arguments)
}
@@ -66,7 +68,7 @@ export function normalizeResponse(response: any, tools?: Map<string, PageAgentTo
!resolvedArguments?.next_goal &&
!resolvedArguments?.thinking
) {
console.log(chalk.yellow(`[normalizeResponse] #4: fixing tool_call`))
log(`#4: fixing tool_call`)
resolvedArguments = { action: safeJsonParse(resolvedArguments) }
}
} else {
@@ -90,7 +92,7 @@ export function normalizeResponse(response: any, tools?: Map<string, PageAgentTo
// fix incomplete formats
if (!resolvedArguments.action) {
console.log(chalk.yellow(`[normalizeResponse] #5: fixing tool_call`))
log(`#5: fixing tool_call`)
resolvedArguments.action = { name: 'wait', input: { seconds: 1 } }
}
@@ -149,9 +151,7 @@ function validateAction(action: any, tools: Map<string, PageAgentTool>): any {
(k) => !(schema.shape as Record<string, z.ZodType>)[k].safeParse(undefined).success
)
if (requiredKey) {
console.log(
chalk.yellow(`[normalizeResponse] coercing primitive action input for "${toolName}"`)
)
log(`coercing primitive action input for "${toolName}"`)
value = { [requiredKey]: value }
}
}
+30 -1
View File
@@ -1,6 +1,6 @@
import chalk from 'chalk'
export { normalizeResponse } from './autoFixer'
export * from './autoFixer'
export async function waitFor(seconds: number): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, seconds * 1000))
@@ -57,6 +57,35 @@ export function uid() {
return id
}
const llmsTxtCache = new Map<string, string | null>()
/** Fetch /llms.txt for a URL's origin. Cached per origin, `null` = tried and not found. */
export async function fetchLlmsTxt(url: string): Promise<string | null> {
const origin = new URL(url).origin
if (llmsTxtCache.has(origin)) return llmsTxtCache.get(origin)!
const endpoint = `${origin}/llms.txt`
let result: string | null = null
try {
console.log(chalk.gray(`[llms.txt] Fetching ${endpoint}`))
const res = await fetch(endpoint, { signal: AbortSignal.timeout(3000) })
if (res.ok) {
result = await res.text()
console.log(chalk.green(`[llms.txt] Found (${result.length} chars)`))
if (result.length > 1000) {
console.log(chalk.yellow(`[llms.txt] Truncating to 1000 chars`))
result = truncate(result, 1000)
}
} else {
console.debug(chalk.gray(`[llms.txt] ${res.status} for ${endpoint}`))
}
} catch (e) {
console.debug(chalk.gray(`[llms.txt] not found for ${endpoint}`), e)
}
llmsTxtCache.set(origin, result)
return result
}
/**
* Simple assertion function that throws an error if the condition is falsy
* @param condition - The condition to assert
+1
View File
@@ -30,6 +30,7 @@ export default defineConfig({
external: [
'chalk',
'zod',
'zod/v4',
// all the internal packages
/^@page-agent\//,
],
-30
View File
@@ -1,30 +0,0 @@
AI-powered browser automation. Control web pages with natural language.
Page Agent Ext — AI-Powered Browser Automation
🌟 What is Page Agent Ext?
Page Agent Ext brings AI-powered automation to your browser. Built on the open-source Page Agent framework, it lets you control web pages across multiple tabs using natural language — no scripting required.
🌟 Key Features:
- Natural Language Control — Command your browser in plain language, no code needed
- Cross-Tab Automation — Seamlessly operate across multiple tabs and pages
- Smart HTML Cleaning — Intelligently extracts and simplifies page structure for accurate AI understanding
- Bring Your Own LLM — Use OpenAI, Anthropic, or any compatible API with full data control
- Privacy-First — Zero data collection; all data flows directly to your chosen LLM provider
- Open Source — MIT licensed, built on the Page Agent framework with full transparency
🌟 How It Works & Privacy:
Page Agent Ext performs DOM analysis locally in your browser. When you initiate a task, sanitized page structure is sent to the LLM API you configure. Your data is never collected or stored by us.
- Your API Key — Configure your own LLM API (OpenAI, Anthropic, etc.). Data goes directly to your provider
- Test API — A free test endpoint is available for evaluation; we recommend your own key for regular use
Terms of Use & Privacy: https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md
🌟 Open Source:
This project is MIT licensed. Review the code, verify privacy claims, or extend it for your needs:
https://github.com/alibaba/page-agent
@@ -1,30 +0,0 @@
AI 驱动的浏览器自动化助手,用自然语言控制网页。
Page Agent Ext — AI 驱动的浏览器自动化助手
🌟 什么是 Page Agent Ext
Page Agent Ext 为浏览器带来 AI 自动化能力。基于开源的 Page Agent 框架,你可以用自然语言跨标签页控制网页,无需编写任何脚本。
🌟 核心特性:
- 自然语言控制 — 用日常语言指挥浏览器,无需编程
- 跨标签页操作 — 在多个标签页之间无缝切换和操控
- HTML 智能清洗 — 智能提取和精简页面结构,让 AI 准确理解网页内容
- 使用你自己的模型 — 支持 OpenAI、Anthropic 或任何兼容 API,数据完全自主可控
- 隐私优先 — 零数据收集,所有数据直接发送到你配置的 LLM 服务商
- 开源透明 — MIT 协议,基于 Page Agent 开源框架,代码完全公开
🌟 工作原理与隐私:
Page Agent Ext 在浏览器本地进行 DOM 分析。当你发起任务时,经清洗的页面结构会发送到你配置的 LLM API。我们不会收集或存储你的任何数据。
- 你的 API Key — 配置你自己的 LLM APIOpenAI、Anthropic 等),数据直接发送到你的服务商
- 测试 API — 提供免费测试端点供体验,日常使用建议配置自己的 Key
使用条款与隐私:https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md
🌟 开源项目:
MIT 协议开源。查看源码、验证隐私承诺,或按需扩展:
https://github.com/alibaba/page-agent
+12 -10
View File
@@ -1,7 +1,7 @@
{
"name": "@page-agent/ext",
"private": true,
"version": "0.1.8",
"version": "0.1.12",
"type": "module",
"scripts": {
"dev": "wxt",
@@ -24,13 +24,13 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"idb": "^8.0.3",
"lucide-react": "^0.575.0",
"motion": "^12.34.3",
"lucide-react": "^0.576.0",
"motion": "^12.34.5",
"next-themes": "^0.4.6",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"rough-notation": "^0.5.1",
"simple-icons": "^16.9.0",
"simple-icons": "^16.10.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.1.14",
@@ -38,12 +38,14 @@
"wxt": "^0.20.18"
},
"dependencies": {
"@page-agent/core": "1.4.0",
"@page-agent/llms": "1.4.0",
"@page-agent/page-controller": "1.4.0",
"@page-agent/ui": "1.4.0",
"@page-agent/core": "1.5.2",
"@page-agent/llms": "1.5.2",
"@page-agent/page-controller": "1.5.2",
"@page-agent/ui": "1.5.2",
"ai-motion": "^0.4.8",
"chalk": "^5.6.2",
"zod": "^4.3.5"
"chalk": "^5.6.2"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
}
@@ -1,4 +1,4 @@
import { type PageAgentConfig, PageAgentCore } from '@page-agent/core'
import { type AgentConfig, PageAgentCore } from '@page-agent/core'
import { RemotePageController } from './RemotePageController'
import { TabsController } from './TabsController'
@@ -17,7 +17,7 @@ function detectLanguage(): 'en-US' | 'zh-CN' {
* - can be used from a side panel or a content script
*/
export class MultiPageAgent extends PageAgentCore {
constructor(config: Omit<PageAgentConfig, 'pageController'> & { includeInitialTab?: boolean }) {
constructor(config: AgentConfig & { includeInitialTab?: boolean }) {
// multi page controller
const tabsController = new TabsController()
const pageController = new RemotePageController(tabsController)
+1 -1
View File
@@ -6,7 +6,7 @@
* - switch_to_tab: Switch to an existing tab
* - close_tab: Close a tab (optionally switch to another)
*/
import * as z from 'zod'
import * as z from 'zod/v4'
import type { TabsController } from './TabsController'
+35 -13
View File
@@ -16,7 +16,13 @@ import { DEMO_CONFIG, migrateLegacyEndpoint } from './constants'
/** Language preference: undefined means follow system */
export type LanguagePreference = SupportedLanguage | undefined
export interface ExtConfig extends LLMConfig {
export interface AdvancedConfig {
maxSteps?: number
systemInstruction?: string
experimentalLlmsTxt?: boolean
}
export interface ExtConfig extends LLMConfig, AdvancedConfig {
language?: LanguagePreference
}
@@ -40,9 +46,10 @@ export function useAgent(): UseAgentResult {
const [config, setConfig] = useState<ExtConfig | null>(null)
useEffect(() => {
chrome.storage.local.get(['llmConfig', 'language']).then((result) => {
chrome.storage.local.get(['llmConfig', 'language', 'advancedConfig']).then((result) => {
let llmConfig = (result.llmConfig as LLMConfig) ?? DEMO_CONFIG
const language = (result.language as SupportedLanguage) || undefined
const advancedConfig = (result.advancedConfig as AdvancedConfig) ?? {}
// Auto-migrate legacy testing endpoints
const migrated = migrateLegacyEndpoint(llmConfig)
@@ -53,14 +60,18 @@ export function useAgent(): UseAgentResult {
chrome.storage.local.set({ llmConfig: DEMO_CONFIG })
}
setConfig({ ...llmConfig, language })
setConfig({ ...llmConfig, ...advancedConfig, language })
})
}, [])
useEffect(() => {
if (!config) return
const agent = new MultiPageAgent(config)
const { systemInstruction, ...agentConfig } = config
const agent = new MultiPageAgent({
...agentConfig,
instructions: systemInstruction ? { system: systemInstruction } : undefined,
})
agentRef.current = agent
const handleStatusChange = (e: Event) => {
@@ -106,15 +117,26 @@ export function useAgent(): UseAgentResult {
agentRef.current?.stop()
}, [])
const configure = useCallback(async ({ language, ...llmConfig }: ExtConfig) => {
await chrome.storage.local.set({ llmConfig })
if (language) {
await chrome.storage.local.set({ language })
} else {
await chrome.storage.local.remove('language')
}
setConfig({ ...llmConfig, language })
}, [])
const configure = useCallback(
async ({
language,
maxSteps,
systemInstruction,
experimentalLlmsTxt,
...llmConfig
}: ExtConfig) => {
await chrome.storage.local.set({ llmConfig })
if (language) {
await chrome.storage.local.set({ language })
} else {
await chrome.storage.local.remove('language')
}
const advancedConfig: AdvancedConfig = { maxSteps, systemInstruction, experimentalLlmsTxt }
await chrome.storage.local.set({ advancedConfig })
setConfig({ ...llmConfig, ...advancedConfig, language })
},
[]
)
return {
status,
@@ -1,4 +1,14 @@
import { Copy, CornerUpLeft, Eye, EyeOff, HatGlasses, Home, Loader2, Scale } from 'lucide-react'
import {
ChevronDown,
Copy,
CornerUpLeft,
Eye,
EyeOff,
HatGlasses,
Home,
Loader2,
Scale,
} from 'lucide-react'
import { useEffect, useState } from 'react'
import { siGithub } from 'simple-icons'
@@ -6,6 +16,7 @@ import { DEMO_API_KEY, DEMO_BASE_URL, DEMO_MODEL, isTestingEndpoint } from '@/ag
import type { ExtConfig, LanguagePreference } from '@/agent/useAgent'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Switch } from '@/components/ui/switch'
interface ConfigPanelProps {
config: ExtConfig | null
@@ -18,18 +29,26 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
const [baseURL, setBaseURL] = useState(config?.baseURL || DEMO_BASE_URL)
const [model, setModel] = useState(config?.model || DEMO_MODEL)
const [language, setLanguage] = useState<LanguagePreference>(config?.language)
const [maxSteps, setMaxSteps] = useState<number | undefined>(config?.maxSteps)
const [systemInstruction, setSystemInstruction] = useState(config?.systemInstruction ?? '')
const [experimentalLlmsTxt, setExperimentalLlmsTxt] = useState(
config?.experimentalLlmsTxt ?? false
)
const [advancedOpen, setAdvancedOpen] = useState(false)
const [saving, setSaving] = useState(false)
const [userAuthToken, setUserAuthToken] = useState<string>('')
const [copied, setCopied] = useState(false)
const [showToken, setShowToken] = useState(false)
const [showApiKey, setShowApiKey] = useState(false)
// Update local state when config prop changes
useEffect(() => {
setApiKey(config?.apiKey || DEMO_API_KEY)
setBaseURL(config?.baseURL || DEMO_BASE_URL)
setModel(config?.model || DEMO_MODEL)
setLanguage(config?.language)
setMaxSteps(config?.maxSteps)
setSystemInstruction(config?.systemInstruction ?? '')
setExperimentalLlmsTxt(config?.experimentalLlmsTxt ?? false)
}, [config])
// Poll for user auth token every second until found
@@ -67,7 +86,15 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
const handleSave = async () => {
setSaving(true)
try {
await onSave({ apiKey, baseURL, model, language })
await onSave({
apiKey,
baseURL,
model,
language,
maxSteps: maxSteps || undefined,
systemInstruction: systemInstruction || undefined,
experimentalLlmsTxt,
})
} finally {
setSaving(false)
}
@@ -87,28 +114,11 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
</Button>
</div>
{/* Testing API notice */}
{isTestingEndpoint(baseURL) && (
<div className="p-2.5 rounded-md border border-amber-500/30 bg-amber-500/5 text-[10px] text-muted-foreground leading-relaxed">
<Scale className="size-3 inline-block mr-1 -mt-0.5 text-amber-600" />
You are using the free testing API. By using this service you agree to the{' '}
<a
href="https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-foreground"
>
Terms of Use & Privacy Policy
</a>
. No sensitive data. No guaranteed availability.
</div>
)}
{/* User Auth Token Section */}
<div className="flex flex-col gap-1.5 p-3 bg-muted/50 rounded-md border">
<label className="text-xs font-medium text-muted-foreground">User Auth Token</label>
<p className="text-[10px] text-muted-foreground mb-1">
Add this token to a website's localStorage to give it authorization to call this extension
Give a website the ability to call this extension.
</p>
<div className="flex gap-2 items-center">
<Input
@@ -153,6 +163,23 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
/>
</div>
{/* Testing API notice */}
{isTestingEndpoint(baseURL) && (
<div className="p-2.5 rounded-md border border-amber-500/30 bg-amber-500/5 text-[11px] text-muted-foreground leading-relaxed">
<Scale className="size-3 inline-block mr-1 -mt-0.5 text-amber-600" />
You are using the free testing API. By using this service you agree to the{' '}
<a
href="https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-foreground"
>
Terms of Use & Privacy Policy
</a>
. No sensitive data. No guaranteed availability.
</div>
)}
<div className="flex flex-col gap-1.5">
<label className="text-xs text-muted-foreground">Model</label>
<Input
@@ -197,6 +224,52 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
</select>
</div>
{/* Advanced Config */}
<button
type="button"
onClick={() => setAdvancedOpen(!advancedOpen)}
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground cursor-pointer mt-1 font-bold"
>
Advanced
<ChevronDown
className="size-3 transition-transform"
style={{ transform: advancedOpen ? 'rotate(0deg)' : 'rotate(90deg)' }}
/>
</button>
{advancedOpen && (
<>
<div className="flex flex-col gap-1.5">
<label className="text-xs text-muted-foreground">Max Steps</label>
<Input
type="number"
placeholder="40"
min={1}
max={200}
value={maxSteps ?? ''}
onChange={(e) => setMaxSteps(e.target.value ? Number(e.target.value) : undefined)}
className="text-xs h-8 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none [-moz-appearance:textfield]"
/>
</div>
<div className="flex flex-col gap-1.5">
<label className="text-xs text-muted-foreground">System Instruction</label>
<textarea
placeholder="Additional instructions for the agent..."
value={systemInstruction}
onChange={(e) => setSystemInstruction(e.target.value)}
rows={3}
className="text-xs rounded-md border border-input bg-background px-3 py-2 resize-y min-h-[60px]"
/>
</div>
<label className="flex items-center justify-between cursor-pointer">
<span className="text-xs text-muted-foreground">Experimental llms.txt support</span>
<Switch checked={experimentalLlmsTxt} onCheckedChange={setExperimentalLlmsTxt} />
</label>
</>
)}
<div className="flex gap-2 mt-2">
<Button variant="outline" onClick={onClose} className="flex-1 h-8 text-xs cursor-pointer">
Cancel
@@ -43,20 +43,24 @@ export function MotionOverlay({ active }: { active: boolean }) {
const motionRef = useRef<Motion | null>(null)
useEffect(() => {
const mode = document.documentElement.classList.contains('dark') ? 'dark' : 'light'
const motion = new Motion({
mode,
borderWidth: 4,
borderRadius: 14,
glowWidth: mode === 'dark' ? 120 : 60,
styles: { position: 'absolute', inset: '0' },
})
motionRef.current = motion
containerRef.current!.appendChild(motion.element)
motion.autoResize(containerRef.current!)
try {
const mode = document.documentElement.classList.contains('dark') ? 'dark' : 'light'
const motion = new Motion({
mode,
borderWidth: 4,
borderRadius: 14,
glowWidth: mode === 'dark' ? 120 : 60,
styles: { position: 'absolute', inset: '0' },
})
motionRef.current = motion
containerRef.current!.appendChild(motion.element)
motion.autoResize(containerRef.current!)
} catch (e) {
console.warn('[MotionOverlay] Motion unavailable:', e)
}
return () => {
motion.dispose()
motionRef.current?.dispose()
motionRef.current = null
}
}, [])
+7 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@page-agent/llms",
"version": "1.4.0",
"version": "1.5.2",
"type": "module",
"main": "./dist/lib/page-agent-llms.js",
"module": "./dist/lib/page-agent-llms.js",
@@ -37,7 +37,12 @@
"postpublish": "node -e \"['LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
},
"dependencies": {
"chalk": "^5.6.2",
"chalk": "^5.6.2"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
},
"devDependencies": {
"zod": "^4.3.5"
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* OpenAI Client implementation
*/
import * as z from 'zod'
import * as z from 'zod/v4'
import { InvokeError, InvokeErrorType } from './errors'
import type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types'
+1 -1
View File
@@ -1,7 +1,7 @@
/**
* Core types for LLM integration
*/
import type * as z from 'zod'
import type * as z from 'zod/v4'
/**
* Message format - OpenAI standard (industry standard)
+2 -4
View File
@@ -2,13 +2,11 @@
* Utility functions for LLM integration
*/
import chalk from 'chalk'
import * as z from 'zod'
import * as z from 'zod/v4'
import type { Tool } from './types'
function debug(message: string) {
console.debug(chalk.gray('[LLM]'), message)
}
const debug = console.debug.bind(console, chalk.gray('[LLM]'))
/**
* Convert Zod schema to OpenAI tool format
+1 -1
View File
@@ -25,7 +25,7 @@ export default defineConfig({
},
outDir: resolve(__dirname, 'dist', 'lib'),
rollupOptions: {
external: ['chalk', 'zod'],
external: ['chalk', 'zod', 'zod/v4'],
},
minify: false,
sourcemap: true,
+11 -6
View File
@@ -1,7 +1,7 @@
{
"name": "page-agent",
"private": false,
"version": "1.4.0",
"version": "1.5.2",
"type": "module",
"main": "./dist/esm/page-agent.js",
"module": "./dist/esm/page-agent.js",
@@ -44,11 +44,16 @@
"postpublish": "node -e \"['README.md','LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
},
"dependencies": {
"@page-agent/core": "1.4.0",
"@page-agent/llms": "1.4.0",
"@page-agent/page-controller": "1.4.0",
"@page-agent/ui": "1.4.0",
"chalk": "^5.6.2",
"@page-agent/core": "1.5.2",
"@page-agent/llms": "1.5.2",
"@page-agent/page-controller": "1.5.2",
"@page-agent/ui": "1.5.2",
"chalk": "^5.6.2"
},
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
},
"devDependencies": {
"zod": "^4.3.5"
}
}
+5 -4
View File
@@ -2,12 +2,13 @@
* Copyright (C) 2025 Alibaba Group Holding Limited
* All rights reserved.
*/
import { type PageAgentConfig, PageAgentCore, type PageAgentTool, tool } from '@page-agent/core'
import { PageController } from '@page-agent/page-controller'
import { type AgentConfig, PageAgentCore } from '@page-agent/core'
import { PageController, type PageControllerConfig } from '@page-agent/page-controller'
import { Panel } from '@page-agent/ui'
export type { PageAgentConfig, PageAgentTool }
export { tool }
export * from '@page-agent/core'
export type PageAgentConfig = AgentConfig & PageControllerConfig
export class PageAgent extends PageAgentCore {
panel: Panel
+1
View File
@@ -30,6 +30,7 @@ export default defineConfig({
external: [
'chalk',
'zod',
'zod/v4',
// all the internal packages
/^@page-agent\//,
],
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@page-agent/page-controller",
"version": "1.4.0",
"version": "1.5.2",
"type": "module",
"main": "./dist/lib/page-controller.js",
"module": "./dist/lib/page-controller.js",
+1 -71
View File
@@ -86,7 +86,7 @@ export async function clickElement(element: HTMLElement) {
// dispatch a click event
// element.click()
await waitFor(0.1) // Wait to ensure click event processing completes
await waitFor(0.2) // Wait to ensure click event processing completes
}
// eslint-disable-next-line @typescript-eslint/unbound-method
@@ -181,11 +181,6 @@ export async function scrollVertically(
// Element-specific scrolling if element is provided
if (element) {
const targetElement = element
console.log(
'[SCROLL DEBUG] Starting direct container scroll for element:',
targetElement.tagName
)
let currentElement = targetElement as HTMLElement | null
let scrollSuccess = false
let scrolledElement: HTMLElement | null = null
@@ -198,19 +193,6 @@ export async function scrollVertically(
const hasScrollableY = /(auto|scroll|overlay)/.test(computedStyle.overflowY)
const canScrollVertically = currentElement.scrollHeight > currentElement.clientHeight
console.log(
'[SCROLL DEBUG] Checking element:',
currentElement.tagName,
'hasScrollableY:',
hasScrollableY,
'canScrollVertically:',
canScrollVertically,
'scrollHeight:',
currentElement.scrollHeight,
'clientHeight:',
currentElement.clientHeight
)
if (hasScrollableY && canScrollVertically) {
const beforeScroll = currentElement.scrollTop
const maxScroll = currentElement.scrollHeight - currentElement.clientHeight
@@ -228,27 +210,10 @@ export async function scrollVertically(
const afterScroll = currentElement.scrollTop
const actualScrollDelta = afterScroll - beforeScroll
console.log(
'[SCROLL DEBUG] Scroll attempt:',
currentElement.tagName,
'before:',
beforeScroll,
'after:',
afterScroll,
'delta:',
actualScrollDelta
)
if (Math.abs(actualScrollDelta) > 0.5) {
scrollSuccess = true
scrolledElement = currentElement
scrollDelta = actualScrollDelta
console.log(
'[SCROLL DEBUG] Successfully scrolled container:',
currentElement.tagName,
'delta:',
actualScrollDelta
)
break
}
}
@@ -344,11 +309,6 @@ export async function scrollHorizontally(
// Element-specific scrolling if element is provided
if (element) {
const targetElement = element
console.log(
'[SCROLL DEBUG] Starting direct container scroll for element:',
targetElement.tagName
)
let currentElement = targetElement as HTMLElement | null
let scrollSuccess = false
let scrolledElement: HTMLElement | null = null
@@ -361,19 +321,6 @@ export async function scrollHorizontally(
const hasScrollableX = /(auto|scroll|overlay)/.test(computedStyle.overflowX)
const canScrollHorizontally = currentElement.scrollWidth > currentElement.clientWidth
console.log(
'[SCROLL DEBUG] Checking element:',
currentElement.tagName,
'hasScrollableX:',
hasScrollableX,
'canScrollHorizontally:',
canScrollHorizontally,
'scrollWidth:',
currentElement.scrollWidth,
'clientWidth:',
currentElement.clientWidth
)
if (hasScrollableX && canScrollHorizontally) {
const beforeScroll = currentElement.scrollLeft
const maxScroll = currentElement.scrollWidth - currentElement.clientWidth
@@ -391,27 +338,10 @@ export async function scrollHorizontally(
const afterScroll = currentElement.scrollLeft
const actualScrollDelta = afterScroll - beforeScroll
console.log(
'[SCROLL DEBUG] Scroll attempt:',
currentElement.tagName,
'before:',
beforeScroll,
'after:',
afterScroll,
'delta:',
actualScrollDelta
)
if (Math.abs(actualScrollDelta) > 0.5) {
scrollSuccess = true
scrolledElement = currentElement
scrollDelta = actualScrollDelta
console.log(
'[SCROLL DEBUG] Successfully scrolled container:',
currentElement.tagName,
'delta:',
actualScrollDelta
)
break
}
}
@@ -1446,7 +1446,7 @@ export default (
/**
* @edit add `data-browser-use-ignore` attribute
*/
if (node.dataset?.browserUseIgnore === 'true') {
if (node.dataset?.browserUseIgnore === 'true' || node.dataset?.pageAgentIgnore === 'true') {
return null // Skip this node and its children
}
@@ -8,13 +8,7 @@ import cursorStyles from './cursor.module.css'
export class SimulatorMask {
shown: boolean = false
wrapper = document.createElement('div')
motion = new Motion({
mode: isPageDark() ? 'dark' : 'light',
styles: {
position: 'absolute',
inset: '0',
},
})
motion: Motion | null = null
#cursor = document.createElement('div')
@@ -28,9 +22,19 @@ export class SimulatorMask {
this.wrapper.id = 'page-agent-runtime_simulator-mask'
this.wrapper.className = styles.wrapper
this.wrapper.setAttribute('data-browser-use-ignore', 'true')
this.wrapper.setAttribute('data-page-agent-ignore', 'true')
this.wrapper.appendChild(this.motion.element)
this.motion.autoResize(this.wrapper)
try {
const motion = new Motion({
mode: isPageDark() ? 'dark' : 'light',
styles: { position: 'absolute', inset: '0' },
})
this.motion = motion
this.wrapper.appendChild(motion.element)
motion.autoResize(this.wrapper)
} catch (e) {
console.warn('[SimulatorMask] Motion overlay unavailable:', e)
}
// Capture all mouse, keyboard, and wheel events
this.wrapper.addEventListener('click', (e) => {
@@ -144,8 +148,8 @@ export class SimulatorMask {
if (this.shown) return
this.shown = true
this.motion.start()
this.motion.fadeIn()
this.motion?.start()
this.motion?.fadeIn()
this.wrapper.style.display = 'block'
@@ -162,8 +166,8 @@ export class SimulatorMask {
if (!this.shown) return
this.shown = false
this.motion.fadeOut()
this.motion.pause()
this.motion?.fadeOut()
this.motion?.pause()
this.#cursor.classList.remove(cursorStyles.clicking)
@@ -173,7 +177,7 @@ export class SimulatorMask {
}
dispose() {
this.motion.dispose()
this.motion?.dispose()
this.wrapper.remove()
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@page-agent/ui",
"version": "1.4.0",
"version": "1.5.2",
"type": "module",
"main": "./dist/lib/page-agent-ui.js",
"module": "./dist/lib/page-agent-ui.js",
+2 -2
View File
@@ -15,7 +15,7 @@ const enUS = {
close: 'Close',
expand: 'Expand history',
collapse: 'Collapse history',
step: 'Step {{number}} · {{time}}{{duration}}',
step: 'Step {{number}}',
},
tools: {
clicking: 'Clicking element [{{index}}]...',
@@ -63,7 +63,7 @@ const zhCN = {
close: '关闭',
expand: '展开历史',
collapse: '收起历史',
step: '步骤 {{number}} · {{time}}{{duration}}',
step: '步骤 {{number}}',
},
tools: {
clicking: '正在点击元素 [{{index}}]...',
+4 -7
View File
@@ -1,6 +1,6 @@
import { I18n, type SupportedLanguage } from '../i18n'
import { truncate } from '../utils'
import { createCard, createReflectionLines, formatTime } from './cards'
import { createCard, createReflectionLines } from './cards'
import type { AgentActivity, PanelAgentAdapter } from './types'
import styles from './Panel.module.css'
@@ -187,7 +187,6 @@ export class Panel {
tempCard.innerHTML = createCard({
icon: '❓',
content: `Question: ${question}`,
meta: formatTime(this.#config.language ?? 'en-US'),
type: 'question',
})
const cardElement = tempCard.firstElementChild as HTMLElement
@@ -374,6 +373,7 @@ export class Panel {
wrapper.id = 'page-agent-runtime_agent-panel'
wrapper.className = styles.wrapper
wrapper.setAttribute('data-browser-use-ignore', 'true')
wrapper.setAttribute('data-page-agent-ignore', 'true')
wrapper.innerHTML = `
<div class="${styles.background}"></div>
@@ -591,15 +591,12 @@ export class Panel {
/** Create cards for a history event */
#createHistoryCards(event: PanelAgentAdapter['history'][number]): string[] {
const cards: string[] = []
const time = formatTime(this.#config.language ?? 'en-US')
const meta =
event.type === 'step' && event.stepIndex !== undefined
? this.#i18n.t('ui.panel.step', {
number: (event.stepIndex + 1).toString(),
time,
duration: '',
})
: time
: undefined
if (event.type === 'step') {
// Reflection card
@@ -636,7 +633,7 @@ export class Panel {
/** Create cards for an action */
#createActionCards(
action: { name: string; input: unknown; output: string },
meta: string
meta?: string
): string[] {
const cards: string[] = []
-10
View File
@@ -32,16 +32,6 @@ export function createCard({ icon, content, meta, type }: CardOptions): string {
`
}
/** Format timestamp for cards */
export function formatTime(locale: string = 'en-US'): string {
return new Date().toLocaleTimeString(locale, {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})
}
/** Create reflection lines from reflection object */
export function createReflectionLines(reflection: {
evaluation_previous_goal?: string
+1 -1
View File
@@ -62,7 +62,7 @@ Located in `src/components/ui/`:
src/
├── pages/
│ ├── home/
│ │ ├── HomeContent.tsx # Homepage sections
│ │ ├── index.tsx # Homepage
│ │ └── ...Section.tsx
│ └── docs/
│ ├── index.tsx # Docs route switch
+13 -2
View File
@@ -45,13 +45,24 @@
</script>
</head>
<body>
<div id="root"></div>
<div id="root">
<div id="sk">
<p class="sk-text" id="sk-text">Loading...</p>
<style>
#sk{display:flex;align-items:center;justify-content:center;min-height:100vh;background:linear-gradient(135deg,#eff6ff,#f5f3ff)}
@media(prefers-color-scheme:dark){#sk{background:linear-gradient(135deg,#111827,#1f2937)}}
.sk-text{font:400 14px/1 system-ui,sans-serif;color:#94a3b8;animation:skf 2s ease-in-out infinite}
@keyframes skf{0%,100%{opacity:.6}50%{opacity:.3}}
</style>
</div>
</div>
<script type="module" src="./src/main.tsx"></script>
<script>
// Dynamically update html lang attribute based on i18n detection
const updateHtmlLang = () => {
const lang = localStorage.getItem('i18nextLng') || navigator.language || 'zh-CN'
document.documentElement.lang = lang
const el = document.getElementById('sk-text')
if (el) el.textContent = lang.startsWith('zh') ? '加载中...' : 'Loading...'
}
updateHtmlLang()
window.addEventListener('storage', updateHtmlLang)
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@page-agent/website",
"private": true,
"version": "1.4.0",
"version": "1.5.2",
"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.575.0",
"motion": "^12.34.3",
"lucide-react": "^0.576.0",
"motion": "^12.34.5",
"next-themes": "^0.4.6",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"rough-notation": "^0.5.1",
"simple-icons": "^16.9.0",
"simple-icons": "^16.10.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.1.14",
@@ -5,10 +5,9 @@
*/
import * as React from 'react'
import { Badge } from '@/components/ui/badge'
import { cn } from '@/lib/utils'
import { Badge } from './badge'
// ============================================================================
// Types
// ============================================================================
@@ -35,6 +34,8 @@ export interface APIReferenceProps {
description?: React.ReactNode
/** Property definitions */
properties: PropDefinition[]
/** Display variant: 'properties' for fields, 'methods' for methods */
variant?: 'properties' | 'methods'
/** Additional CSS classes */
className?: string
}
@@ -43,7 +44,14 @@ export interface APIReferenceProps {
// Component
// ============================================================================
export function APIReference({ title, description, properties, className }: APIReferenceProps) {
export function APIReference({
title,
description,
properties,
variant = 'properties',
className,
}: APIReferenceProps) {
const isMethodsVariant = variant === 'methods'
return (
<div className={cn('my-6', className)}>
{title && (
@@ -58,10 +66,10 @@ export function APIReference({ title, description, properties, className }: APIR
<thead>
<tr className="bg-gray-50 dark:bg-gray-800/50">
<th className="px-4 py-3 text-left font-medium text-gray-600 dark:text-gray-300">
Property
{isMethodsVariant ? 'Method' : 'Property'}
</th>
<th className="px-4 py-3 text-left font-medium text-gray-600 dark:text-gray-300">
Type
{isMethodsVariant ? 'Return Type' : 'Type'}
</th>
<th className="px-4 py-3 text-left font-medium text-gray-600 dark:text-gray-300 hidden md:table-cell">
Default
@@ -120,7 +128,7 @@ function PropRow({ name, type, required, defaultValue, description, status }: Pr
{/* Type */}
<td className="px-4 py-3 align-top">
<code className="font-mono text-xs text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded whitespace-nowrap">
<code className="font-mono text-xs text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded wrap-break-word">
{type}
</code>
</td>
@@ -42,7 +42,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
return (
<div
className={`group relative ${containerClasses} rounded-xl border shadow-2xl overflow-hidden ${className}`}
className={`group relative ${containerClasses} rounded-xl border shadow-2xl my-4 overflow-hidden ${className}`}
>
{/* 编辑器顶部栏 */}
{showHeader && (
+2 -2
View File
@@ -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.4.0/dist/iife/page-agent.demo.js'
'https://cdn.jsdelivr.net/npm/page-agent@1.5.2/dist/iife/page-agent.demo.js'
export const CDN_DEMO_CN_URL =
'https://registry.npmmirror.com/page-agent/1.4.0/files/dist/iife/page-agent.demo.js'
'https://registry.npmmirror.com/page-agent/1.5.2/files/dist/iife/page-agent.demo.js'
// Demo LLM for website testing (homepage quick trial uses flash)
export const DEMO_MODEL = 'qwen3.5-flash'
-15
View File
@@ -1,15 +0,0 @@
import FeaturesSection from './home/FeaturesSection'
import HeroSection from './home/HeroSection'
import OneMoreThingSection from './home/OneMoreThingSection'
import ScenariosSection from './home/ScenariosSection'
export default function HomePage() {
return (
<>
<HeroSection />
<FeaturesSection />
<ScenariosSection />
<OneMoreThingSection />
</>
)
}
+3 -6
View File
@@ -34,10 +34,6 @@ export default function DocsLayout({ children }: DocsLayoutProps) {
title: isZh ? '故障排查' : 'Troubleshooting',
path: '/introduction/troubleshooting',
},
{
title: '🚧 ' + (isZh ? '最佳实践' : 'Best Practices'),
path: '/integration/best-practices',
},
],
},
{
@@ -50,7 +46,7 @@ export default function DocsLayout({ children }: DocsLayoutProps) {
{ title: isZh ? 'Chrome 扩展' : 'Chrome Extension', path: '/features/chrome-extension' },
{
title: isZh ? '接入第三方 Agent' : 'Third-party Agent',
path: '/integration/third-party-agent',
path: '/features/third-party-agent',
},
],
},
@@ -59,10 +55,11 @@ export default function DocsLayout({ children }: DocsLayoutProps) {
items: [
{ title: 'PageAgent', path: '/advanced/page-agent' },
{ title: 'PageAgentCore', path: '/advanced/page-agent-core' },
{ title: 'PageController', path: '/advanced/page-controller' },
{ title: isZh ? '自定义 UI' : 'Custom UI', path: '/advanced/custom-ui' },
{
title: '🚧 ' + (isZh ? '安全与权限' : 'Security & Permissions'),
path: '/integration/security-permissions',
path: '/advanced/security-permissions',
},
],
},
@@ -1,6 +1,6 @@
import { APIDivider, APIReference } from '@/components/APIReference'
import CodeEditor from '@/components/CodeEditor'
import { Heading } from '@/components/Heading'
import { APIDivider, APIReference } from '@/components/ui/api-reference'
import { useLanguage } from '@/i18n/context'
export default function CustomUIDocs() {
@@ -1,6 +1,8 @@
import { Link } from 'wouter'
import { APIDivider, APIReference, TypeRef } from '@/components/APIReference'
import CodeEditor from '@/components/CodeEditor'
import { Heading } from '@/components/Heading'
import { APIDivider, APIReference, TypeRef } from '@/components/ui/api-reference'
import { useLanguage } from '@/i18n/context'
export default function PageAgentCoreDocs() {
@@ -50,19 +52,13 @@ const agent = new PageAgentCore({
baseURL: 'https://api.openai.com/v1',
apiKey: 'your-api-key',
model: 'gpt-5.2',
language: 'en-US',
})
// Listen to events for UI display
agent.addEventListener('statuschange', () => {
console.log('Status:', agent.status)
})
agent.addEventListener('historychange', () => {
console.log('History:', agent.history)
})
agent.addEventListener('activity', (e) => {
const activity = (e as CustomEvent).detail
console.log('Activity:', activity.type)
@@ -75,14 +71,54 @@ const result = await agent.execute('Fill in the form with test data')`}
<APIDivider title={isZh ? '配置' : 'Configuration'} />
{/* LLM Configuration */}
{/* Configuration */}
<section className="mb-10">
<Heading id="llmconfig">LLMConfig</Heading>
<Heading id="configuration">PageAgentCoreConfig</Heading>
<p className="text-gray-600 dark:text-gray-400 mb-4">
{isZh
? '配置与大语言模型的连接参数。支持 OpenAI 兼容的 API。'
: 'Configure connection parameters for the language model. Supports OpenAI-compatible APIs.'}
? 'PageAgentCoreConfig = AgentConfig & { pageController: PageController }。AgentConfig 包含以下配置项:'
: 'PageAgentCoreConfig = AgentConfig & { pageController: PageController }. AgentConfig contains the following options:'}
</p>
{/* PageController */}
<h3 className="text-lg font-semibold mt-6 mb-3 text-gray-800 dark:text-gray-200">
PageController
</h3>
<APIReference
properties={[
{
name: 'pageController',
type: 'PageController',
required: true,
description: isZh ? (
<>
<Link
href="/advanced/page-controller"
className="text-blue-600 dark:text-blue-400 hover:underline"
>
PageController
</Link>{' '}
DOM
</>
) : (
<>
<Link
href="/advanced/page-controller"
className="text-blue-600 dark:text-blue-400 hover:underline"
>
PageController
</Link>{' '}
instance for DOM operations and element interaction.
</>
),
},
]}
/>
{/* LLM Config */}
<h3 className="text-lg font-semibold mt-6 mb-3 text-gray-800 dark:text-gray-200">
{isZh ? 'LLM 配置' : 'LLM Config'}
</h3>
<APIReference
properties={[
{
@@ -110,7 +146,6 @@ const result = await agent.execute('Fill in the form with test data')`}
{
name: 'temperature',
type: 'number',
defaultValue: '0',
description: isZh
? '模型温度参数,控制输出随机性'
: 'Model temperature, controls output randomness',
@@ -130,16 +165,11 @@ const result = await agent.execute('Fill in the form with test data')`}
},
]}
/>
</section>
{/* Agent Configuration */}
<section className="mb-10">
<Heading id="agentconfig">AgentConfig</Heading>
<p className="text-gray-600 dark:text-gray-400 mb-4">
{isZh
? '配置 Agent 的行为、生命周期钩子和扩展能力。'
: 'Configure agent behavior, lifecycle hooks, and extension capabilities.'}
</p>
{/* Agent Config */}
<h3 className="text-lg font-semibold mt-6 mb-3 text-gray-800 dark:text-gray-200">
{isZh ? 'Agent 配置' : 'Agent Config'}
</h3>
<APIReference
properties={[
{
@@ -151,7 +181,7 @@ const result = await agent.execute('Fill in the form with test data')`}
{
name: 'maxSteps',
type: 'number',
defaultValue: '20',
defaultValue: '40',
description: isZh ? '每个任务的最大步骤数' : 'Maximum number of steps per task',
},
{
@@ -166,8 +196,8 @@ const result = await agent.execute('Fill in the form with test data')`}
name: 'instructions',
type: 'InstructionsConfig',
description: isZh
? '指导 Agent 行为的指令配置'
: 'Instructions to guide agent behavior',
? '指导 Agent 行为的指令配置,见下方类型定义'
: 'Instructions to guide agent behavior, see type definition below',
},
{
name: 'transformPageContent',
@@ -177,7 +207,15 @@ const result = await agent.execute('Fill in the form with test data')`}
: 'Transform page content before sending to LLM, useful for data masking',
},
{
name: 'experimentalScriptExecutionTool',
name: 'customSystemPrompt',
type: 'string',
status: 'experimental',
description: isZh
? '完全覆盖默认系统提示词。谨慎使用。'
: 'Completely override the default system prompt. Use with caution.',
},
{
name: 'experimentalScript\nExecutionTool',
type: 'boolean',
defaultValue: 'false',
status: 'experimental',
@@ -185,134 +223,58 @@ const result = await agent.execute('Fill in the form with test data')`}
? '启用实验性 JavaScript 执行工具'
: 'Enable experimental JavaScript execution tool',
},
]}
/>
<h3 className="text-lg font-semibold mt-6 mb-3">InstructionsConfig</h3>
<APIReference
properties={[
{
name: 'system',
type: 'string',
name: 'experimentalLlmsTxt',
type: 'boolean',
defaultValue: 'false',
status: 'experimental',
description: isZh
? '全局系统级指令,应用于所有任务'
: 'Global system-level instructions, applied to all tasks',
},
{
name: 'getPageInstructions',
type: '(url: string) => string | undefined | null',
description: isZh
? '动态页面级指令回调,在每个步骤前调用'
: 'Dynamic page-level instructions callback, called before each step',
? '从当前站点根目录获取 /llms.txt 并作为上下文提供给 LLM'
: 'Fetch /llms.txt from site origin and include as LLM context',
},
]}
/>
</section>
{/* Lifecycle Hooks */}
<section className="mb-10">
<Heading id="lifecycle-hooks">{isZh ? '生命周期钩子' : 'Lifecycle Hooks'}</Heading>
{/* Lifecycle Hooks */}
<h3 className="text-lg font-semibold mt-6 mb-3 text-gray-800 dark:text-gray-200">
{isZh ? '生命周期钩子' : 'Lifecycle Hooks'}
<span className="ml-2 text-xs font-normal text-amber-600 dark:text-amber-400">
experimental
</span>
</h3>
<div className="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg p-4 mb-4">
<p className="text-amber-800 dark:text-amber-200 text-sm">
<strong> {isZh ? '警告' : 'Warning'}:</strong>{' '}
{isZh
? '这些接口高度实验性,可能在未来版本中发生变化。建议优先使用事件系统(Events)来监听 Agent 状态。'
: 'These APIs are highly experimental and may change in future versions. Prefer using the Events system for monitoring agent state.'}
? '这些接口高度实验性,可能在未来版本中发生变化。'
: 'These APIs are highly experimental and may change in future versions. '}
</p>
</div>
<p className="text-gray-600 dark:text-gray-400 mb-4">
{isZh
? '所有生命周期钩子都接收 agent 实例作为第一个参数,便于在回调中访问 Agent 状态和方法。'
: 'All lifecycle hooks receive the agent instance as first parameter, making it easy to access agent state and methods in callbacks.'}
</p>
<APIReference
properties={[
{
name: 'onBeforeStep',
type: '(agent: PageAgentCore, stepCount: number) => void | Promise<void>',
type: '(agent, stepCount) => void | Promise<void>',
description: isZh ? '每个步骤执行前调用' : 'Called before each step execution',
status: 'experimental',
},
{
name: 'onAfterStep',
type: '(agent: PageAgentCore, history: HistoricalEvent[]) => void | Promise<void>',
type: '(agent, history) => void | Promise<void>',
description: isZh ? '每个步骤执行后调用' : 'Called after each step execution',
status: 'experimental',
},
{
name: 'onBeforeTask',
type: '(agent: PageAgentCore) => void | Promise<void>',
type: '(agent) => void | Promise<void>',
description: isZh ? '任务开始前调用' : 'Called before task starts',
status: 'experimental',
},
{
name: 'onAfterTask',
type: '(agent: PageAgentCore, result: ExecutionResult) => void | Promise<void>',
type: '(agent, result) => void | Promise<void>',
description: isZh ? '任务结束后调用' : 'Called after task ends',
status: 'experimental',
},
{
name: 'onDispose',
type: '(agent: PageAgentCore, reason?: string) => void',
type: '(agent, reason?) => void',
description: isZh ? 'Agent 销毁时调用' : 'Called when agent is disposed',
status: 'experimental',
},
]}
/>
</section>
{/* PageController Configuration */}
<section className="mb-10">
<Heading id="pagecontrollerconfig">PageControllerConfig</Heading>
<p className="text-gray-600 dark:text-gray-400 mb-4">
{isZh
? '配置 DOM 提取、元素交互和视觉反馈。'
: 'Configure DOM extraction, element interaction, and visual feedback.'}
</p>
<APIReference
properties={[
{
name: 'pageController',
type: 'PageController',
status: 'experimental',
description: isZh
? '自定义 PageController 实例。如不提供,将创建默认实例。'
: 'Custom PageController instance. If not provided, a default one will be created.',
},
{
name: 'enableMask',
type: 'boolean',
defaultValue: 'true',
description: isZh
? '启用视觉遮罩覆盖层,阻止用户在自动化期间操作'
: 'Enable visual mask overlay that blocks user interaction during automation',
},
{
name: 'viewportExpansion',
type: 'number',
defaultValue: '0',
description: isZh
? '视口扩展像素数,-1 表示提取整个页面'
: 'Viewport expansion in pixels, -1 means extract entire page',
},
{
name: 'interactiveBlacklist',
type: '(Element | (() => Element))[]',
description: isZh ? '要排除的交互元素列表' : 'Elements to exclude from interaction',
},
{
name: 'interactiveWhitelist',
type: '(Element | (() => Element))[]',
description: isZh
? '要强制包含的交互元素列表'
: 'Elements to force include for interaction',
},
{
name: 'includeAttributes',
type: 'string[]',
description: isZh
? '在 DOM 提取中包含的额外属性'
: 'Additional attributes to include in DOM extraction',
},
]}
/>
@@ -369,16 +331,24 @@ const result = await agent.execute('Fill in the form with test data')`}
<section className="mb-10">
<Heading id="methods">{isZh ? '方法' : 'Methods'}</Heading>
<APIReference
variant="methods"
properties={[
{
name: 'execute(task: string)',
name: 'execute(task)',
type: 'Promise<ExecutionResult>',
description: isZh
? '执行任务并返回结果。包含 success、data 和 history 字段。'
: 'Execute a task and return result. Contains success, data, and history fields.',
},
{
name: 'dispose(reason?: string)',
name: 'stop()',
type: 'void',
description: isZh
? '停止当前任务。Agent 仍可复用。'
: 'Stop the current task. Agent remains reusable.',
},
{
name: 'dispose()',
type: 'void',
description: isZh
? '销毁 Agent 并清理资源'
@@ -443,11 +413,8 @@ const result = await agent.execute('Fill in the form with test data')`}
<CodeEditor
language="typescript"
code={`interface ExecutionResult {
/** Whether the task completed successfully */
success: boolean
/** Result description from the agent */
data: string
/** Full execution history */
history: HistoricalEvent[]
}`}
/>
@@ -467,50 +434,21 @@ const result = await agent.execute('Fill in the form with test data')`}
/>
</section>
<APIDivider title={isZh ? '无头模式' : 'Headless Mode'} />
{/* Headless Usage */}
{/* InstructionsConfig */}
<section className="mb-10">
<Heading id="headless-mode">{isZh ? '无头模式' : 'Headless Mode'}</Heading>
<p className="text-gray-600 dark:text-gray-400 mb-4">
{isZh
? '在非 DOM 环境中,你必须实现自定义的 PageController(例如远程操作页面或 Puppeteer)。'
: 'In non-DOM environments, you must implement a custom PageController (e.g., remote page control or Puppeteer).'}
</p>
<Heading id="instructionsconfig">InstructionsConfig</Heading>
<CodeEditor
language="typescript"
code={`import { PageAgentCore } from '@page-agent/core'
import type { PageController } from '@page-agent/page-controller'
code={`interface InstructionsConfig {
/** Global system-level instructions, applied to all tasks */
system?: string
class MyRemotePageController implements PageController {
// Implement required methods for DOM extraction and interaction
}
const agent = new PageAgentCore({
pageController: new MyRemotePageController(),
baseURL: 'https://api.openai.com/v1',
apiKey: 'your-api-key',
model: 'gpt-5.2',
language: 'en-US',
})
// Listen to events for UI display
agent.addEventListener('statuschange', () => {
console.log('Status:', agent.status)
})
agent.addEventListener('historychange', () => {
console.log('History:', agent.history)
})
agent.addEventListener('activity', (e) => {
const activity = (e as CustomEvent).detail
console.log('Activity:', activity.type)
})
// Execute task
const result = await agent.execute('Fill in the form with test data')`}
/**
* Dynamic page-level instructions callback.
* Called before each step to get instructions for the current page.
*/
getPageInstructions?: (url: string) => string | undefined
}`}
/>
</section>
</div>
@@ -2,7 +2,6 @@ import { Link } from 'wouter'
import CodeEditor from '@/components/CodeEditor'
import { Heading } from '@/components/Heading'
import { APIReference, TypeRef } from '@/components/ui/api-reference'
import { useLanguage } from '@/i18n/context'
export default function PageAgentDocs() {
@@ -14,8 +13,8 @@ export default function PageAgentDocs() {
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
{isZh
? 'PageAgent 是带有内置 UI 面板的完整 Agent 类。它继承自 PageAgentCore,并自动创建交互面板。'
: 'PageAgent is the complete Agent class with built-in UI panel. It extends PageAgentCore and automatically creates an interactive panel.'}
? 'PageAgent 是带有内置 UI 面板的完整 Agent 类。它继承自 PageAgentCore,并自动创建交互面板和 PageController。'
: 'PageAgent is the complete Agent class with built-in UI panel. It extends PageAgentCore and automatically creates an interactive panel and PageController.'}
</p>
{/* When to use */}
@@ -29,6 +28,11 @@ export default function PageAgentDocs() {
: 'In most cases, you should use PageAgent. It provides a complete out-of-the-box experience:'}
</p>
<ul className="list-disc list-inside text-gray-600 dark:text-gray-400 space-y-2 mb-6">
<li>
{isZh
? '自动创建 PageController,处理 DOM 提取和元素操作'
: 'Automatically creates PageController for DOM extraction and element actions'}
</li>
<li>
{isZh
? '内置 UI 面板,显示任务进度、Agent 思考过程和操作结果'
@@ -74,9 +78,8 @@ console.log(result.history) // Full execution history`}
<CodeEditor
language="typescript"
code={`class PageAgent extends PageAgentCore {
/** The UI panel instance */
panel: Panel
pageController: PageController
constructor(config: PageAgentConfig)
}`}
/>
@@ -90,7 +93,21 @@ console.log(result.history) // Full execution history`}
>
PageAgentCore
</Link>
API PageAgentCore
{' '}
<Link
href="/advanced/page-agent-core#configuration"
className="text-blue-600 dark:text-blue-400 hover:underline"
>
AgentConfig
</Link>{' '}
{' '}
<Link
href="/advanced/page-controller#configuration"
className="text-blue-600 dark:text-blue-400 hover:underline"
>
PageControllerConfig
</Link>
</>
) : (
<>
@@ -101,71 +118,34 @@ console.log(result.history) // Full execution history`}
>
PageAgentCore
</Link>
. All core methods and events are available. See PageAgentCore docs for detailed API
reference.
</>
)}
</p>
</section>
{/* Configuration */}
<section className="mb-10">
<Heading id="configuration">{isZh ? '配置' : 'Configuration'}</Heading>
<p className="text-gray-600 dark:text-gray-400 mb-4">
{isZh
? 'PageAgent 使用与 PageAgentCore 相同的配置接口。'
: 'PageAgent uses the same configuration interface as PageAgentCore.'}
</p>
<p className="text-gray-600 dark:text-gray-400 mb-4">
{isZh ? (
<>
{' '}
. All core methods and events are available. Config merges{' '}
<Link
href="/advanced/page-agent-core"
href="/advanced/page-agent-core#configuration"
className="text-blue-600 dark:text-blue-400 hover:underline"
>
PageAgentCore
</Link>
</>
) : (
<>
See{' '}
<Link
href="/advanced/page-agent-core"
className="text-blue-600 dark:text-blue-400 hover:underline"
>
PageAgentCore configuration docs
AgentConfig
</Link>{' '}
for complete reference.
and{' '}
<Link
href="/advanced/page-controller#configuration"
className="text-blue-600 dark:text-blue-400 hover:underline"
>
PageControllerConfig
</Link>
.
</>
)}
</p>
</section>
{/* Panel Property */}
{/* Panel */}
<section className="mb-10">
<Heading id="panel-property">{isZh ? 'Panel 属性' : 'Panel Property'}</Heading>
<Heading id="panel">{isZh ? 'UI 面板' : 'UI Panel'}</Heading>
<p className="text-gray-600 dark:text-gray-400 mb-4">
{isZh
? 'PageAgent 自动创建一个 Panel 实例。你可以通过 panel 属性访问它来控制 UI'
: 'PageAgent automatically creates a Panel instance. You can access it via the panel property to control the UI:'}
? 'PageAgent 自动创建一个 Panel 实例。你可以通过 panel 属性控制 UI'
: 'PageAgent automatically creates a Panel instance. You can control the UI via the panel property:'}
</p>
<APIReference
properties={[
{
name: 'panel',
type: 'Panel',
required: true,
description: isZh
? '内置的 UI 面板实例,用于显示任务进度和接收用户输入。'
: 'The built-in UI panel instance for displaying task progress and receiving user input.',
},
]}
/>
<h3 className="text-lg font-semibold mt-6 mb-3">{isZh ? 'Panel 方法' : 'Panel Methods'}</h3>
<CodeEditor
language="typescript"
code={`// Show/hide the panel
@@ -212,16 +192,16 @@ agent.panel.dispose()`}
</tr>
<tr className="bg-white dark:bg-gray-900">
<td className="px-4 py-3 text-gray-600 dark:text-gray-400">
{isZh ? 'Headless 模式' : 'Headless Mode'}
{isZh ? '自动创建 PageController' : 'Auto-creates PageController'}
</td>
<td className="px-4 py-3 text-center text-gray-400 dark:text-gray-600">-</td>
<td className="px-4 py-3 text-center text-green-600 dark:text-green-400"></td>
<td className="px-4 py-3 text-center text-gray-400 dark:text-gray-600">-</td>
</tr>
<tr className="bg-white dark:bg-gray-900">
<td className="px-4 py-3 text-gray-600 dark:text-gray-400">
{isZh ? '自定义 PageController' : 'Custom PageController'}
{isZh ? 'Headless 模式' : 'Headless Mode'}
</td>
<td className="px-4 py-3 text-center text-green-600 dark:text-green-400"></td>
<td className="px-4 py-3 text-center text-gray-400 dark:text-gray-600">-</td>
<td className="px-4 py-3 text-center text-green-600 dark:text-green-400"></td>
</tr>
<tr className="bg-white dark:bg-gray-900">
@@ -0,0 +1,286 @@
import { Link } from 'wouter'
import { APIDivider, APIReference } from '@/components/APIReference'
import CodeEditor from '@/components/CodeEditor'
import { Heading } from '@/components/Heading'
import { useLanguage } from '@/i18n/context'
export default function PageControllerDocs() {
const { isZh } = useLanguage()
return (
<div>
<h1 className="text-4xl font-bold mb-6">PageController</h1>
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
{isZh
? 'PageController 负责 DOM 提取和元素交互,独立于 LLM。它将页面状态结构化为 LLM 可消费的格式,并执行元素级操作。'
: 'PageController handles DOM extraction and element interaction, independent of LLM. It structures page state into LLM-consumable format and executes element-level actions.'}
</p>
{/* Basic Usage */}
<section className="mb-10">
<Heading id="basic-usage">{isZh ? '基本用法' : 'Basic Usage'}</Heading>
<p className="text-gray-600 dark:text-gray-400 mb-4">
{isZh
? 'PageAgent 接受 PageController 配置项:'
: 'PageAgent accepts PageController options:'}
</p>
<CodeEditor
language="typescript"
code={`import { PageAgent } from 'page-agent'
const agent = new PageAgent({
baseURL: 'https://api.openai.com/v1',
apiKey: 'your-api-key',
model: 'gpt-5.2',
// PageController options
enableMask: true,
viewportExpansion: 0,
})`}
/>
<p className="text-gray-600 dark:text-gray-400 mt-4">
{isZh
? 'PageAgentCore 接受 PageController 实例:'
: 'PageAgentCore accepts a PageController instance:'}
</p>
<CodeEditor
language="typescript"
code={`import { PageAgentCore } from '@page-agent/core'
import { PageController } from '@page-agent/page-controller'
const pageController = new PageController({
enableMask: true,
viewportExpansion: -1, // extract full page
})
const agent = new PageAgentCore({
pageController,
baseURL: 'https://api.openai.com/v1',
apiKey: 'your-api-key',
model: 'gpt-5.2',
})`}
/>
</section>
<APIDivider title={isZh ? '配置' : 'Configuration'} />
{/* Configuration */}
<section className="mb-10">
<Heading id="configuration">PageControllerConfig</Heading>
<APIReference
properties={[
{
name: 'enableMask',
type: 'boolean',
defaultValue: 'false',
description: isZh
? '启用视觉遮罩覆盖层,在自动化期间阻止用户操作页面。通过 PageAgent 创建时默认为 true。'
: 'Enable visual mask overlay that blocks user interaction during automation. Defaults to true when created via PageAgent.',
},
{
name: 'viewportExpansion',
type: 'number',
defaultValue: '0',
description: isZh
? '向视口外扩展提取的像素数。设为 -1 表示提取整个页面。'
: 'Pixels to expand extraction beyond viewport. Set to -1 to extract the entire page.',
},
{
name: 'interactiveBlacklist',
type: '(Element | (() => Element))[]',
description: isZh
? '要排除的交互元素列表。支持元素引用或返回元素的函数(延迟求值)。'
: 'Elements to exclude from interaction. Supports element references or functions returning elements (lazy evaluation).',
},
{
name: 'interactiveWhitelist',
type: '(Element | (() => Element))[]',
description: isZh
? '要强制包含的交互元素列表。支持元素引用或返回元素的函数。'
: 'Elements to force include for interaction. Supports element references or functions returning elements.',
},
{
name: 'includeAttributes',
type: 'string[]',
description: isZh
? '在 DOM 提取中包含的额外 HTML 属性(如 data-testid)。默认已包含常见属性如 role, aria-label 等。'
: 'Additional HTML attributes to include in DOM extraction (e.g. data-testid). Common attributes like role, aria-label are included by default.',
},
]}
/>
</section>
<APIDivider title={isZh ? '方法' : 'Methods'} />
{/* Methods */}
<section className="mb-10">
<Heading id="methods">{isZh ? '方法' : 'Methods'}</Heading>
<h3 className="text-lg font-semibold mt-6 mb-3">{isZh ? '状态查询' : 'State Queries'}</h3>
<APIReference
variant="methods"
properties={[
{
name: 'getBrowserState()',
type: 'Promise<BrowserState>',
description: isZh
? '获取结构化的浏览器状态(URL、标题、简化 HTML 等),自动调用 updateTree() 刷新 DOM。这是 Agent 在每步使用的主要方法。'
: 'Get structured browser state (URL, title, simplified HTML, etc.), automatically calls updateTree() to refresh DOM. This is the primary method the agent uses each step.',
},
{
name: 'updateTree()',
type: 'Promise<string>',
description: isZh
? '刷新 DOM 树并返回简化 HTML。通常不需要手动调用 —— getBrowserState() 会自动调用。'
: 'Refresh DOM tree and return simplified HTML. Usually not needed manually — getBrowserState() calls it automatically.',
},
{
name: 'getCurrentUrl()',
type: 'Promise<string>',
description: isZh ? '获取当前页面 URL。' : 'Get current page URL.',
},
]}
/>
<h3 className="text-lg font-semibold mt-6 mb-3">{isZh ? '元素操作' : 'Element Actions'}</h3>
<APIReference
variant="methods"
properties={[
{
name: 'clickElement(index)',
type: 'Promise<ActionResult>',
description: isZh
? '按索引点击元素。索引来自简化 HTML 中的 [N] 标记。'
: 'Click element by index. Index comes from [N] markers in simplified HTML.',
},
{
name: 'inputText(index, text)',
type: 'Promise<ActionResult>',
description: isZh ? '向输入框元素填入文本。' : 'Input text into a form element.',
},
{
name: 'selectOption(index, optionText)',
type: 'Promise<ActionResult>',
description: isZh ? '在下拉框中选择选项。' : 'Select option in a dropdown element.',
},
{
name: 'scroll(options)',
type: 'Promise<ActionResult>',
description: isZh
? '垂直滚动页面或指定元素。'
: 'Scroll page or specific element vertically.',
},
{
name: 'scrollHorizontally(options)',
type: 'Promise<ActionResult>',
description: isZh
? '水平滚动页面或指定元素。'
: 'Scroll page or specific element horizontally.',
},
]}
/>
<h3 className="text-lg font-semibold mt-6 mb-3">{isZh ? '遮罩控制' : 'Mask Control'}</h3>
<APIReference
variant="methods"
properties={[
{
name: 'showMask()',
type: 'Promise<void>',
description: isZh
? '显示视觉遮罩。需要 enableMask: true。'
: 'Show visual mask overlay. Requires enableMask: true.',
},
{
name: 'hideMask()',
type: 'Promise<void>',
description: isZh ? '隐藏视觉遮罩。' : 'Hide visual mask overlay.',
},
]}
/>
<h3 className="text-lg font-semibold mt-6 mb-3">{isZh ? '生命周期' : 'Lifecycle'}</h3>
<APIReference
variant="methods"
properties={[
{
name: 'dispose()',
type: 'void',
description: isZh
? '清理所有资源(DOM 高亮、遮罩等)。Agent 销毁时自动调用。'
: 'Clean up all resources (DOM highlights, mask, etc.). Called automatically when agent disposes.',
},
]}
/>
</section>
<APIDivider title={isZh ? '类型定义' : 'Type Definitions'} />
{/* BrowserState */}
<section className="mb-10">
<Heading id="browser-state">BrowserState</Heading>
<p className="text-gray-600 dark:text-gray-400 mb-4">
{isZh
? 'getBrowserState() 返回的结构化浏览器状态,直接用于构建 LLM prompt。'
: 'Structured browser state returned by getBrowserState(), used directly to build LLM prompts.'}
</p>
<CodeEditor
language="typescript"
code={`interface BrowserState {
url: string
title: string
header: string // page info + scroll position
content: string // simplified HTML of interactive elements
footer: string // scroll hint
}`}
/>
</section>
{/* ActionResult */}
<section className="mb-10">
<Heading id="action-result">ActionResult</Heading>
<CodeEditor
language="typescript"
code={`interface ActionResult {
success: boolean
message: string
}`}
/>
</section>
{/* Custom PageController */}
<section className="mb-10">
<Heading id="custom-implementation">
{isZh ? '自定义实现' : 'Custom Implementation'}
</Heading>
<p className="text-gray-600 dark:text-gray-400 mb-4">
{isZh
? '在非浏览器环境(如 Puppeteer、Playwright),你可以实现自定义 PageController。需要实现 Agent 使用的核心方法:'
: 'In non-browser environments (e.g. Puppeteer, Playwright), you can implement a custom PageController. Implement the core methods used by the agent:'}
</p>
<CodeEditor
language="typescript"
code={`import { PageAgentCore } from '@page-agent/core'
import type { PageController } from '@page-agent/page-controller'
class PuppeteerPageController implements PageController {
async getBrowserState() { /* ... */ }
async clickElement(index: number) { /* ... */ }
async inputText(index: number, text: string) { /* ... */ }
async scroll(options: { down: boolean; numPages: number }) { /* ... */ }
// ... other methods
}
const agent = new PageAgentCore({
pageController: new PuppeteerPageController(),
baseURL: 'https://api.openai.com/v1',
apiKey: 'your-api-key',
model: 'gpt-5.2',
})`}
/>
</section>
</div>
)
}
@@ -1,4 +1,3 @@
import BetaNotice from '@/components/BetaNotice'
import CodeEditor from '@/components/CodeEditor'
import { Heading } from '@/components/Heading'
import { useLanguage } from '@/i18n/context'
@@ -12,115 +11,100 @@ export default function CustomTools() {
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
{isZh
? '通过注册自定义工具,扩展 AI Agent 的能力边界。使用 Zod 定义严格的输入接口,让 AI 安全调用你的业务逻辑。'
: 'Extend AI Agent capabilities by registering custom tools. Use Zod to define strict input schemas for safe business logic calls.'}
? '通过注册自定义工具,扩展 AI Agent 的能力边界。使用 Zod 定义输入接口,让 AI 安全调用你的业务逻辑。'
: 'Extend AI Agent capabilities by registering custom tools. Define input schemas with Zod for safe business logic invocation.'}
</p>
<div className="space-y-8">
<section>
<Heading id="tool-registration" className="text-2xl font-bold mb-4">
{isZh ? '工具注册' : 'Tool Registration'}
<Heading id="zod-version" className="text-2xl font-bold mb-4">
{isZh ? 'Zod 版本' : 'Zod Version'}
</Heading>
<p className="text-gray-600 dark:text-gray-300 mb-4">
{isZh
? '每个自定义工具需要定义四个核心属性:name、description、input schema 和 execute 函数。'
: 'Each custom tool requires four core properties: name, description, input schema, and execute function.'}
? 'Page Agent 使用 Zod 定义工具的输入 schema。支持 Zod 3 (>=3.25.0) 和 Zod 4,请从 zod/v4 子路径导入。不支持 Zod Mini。'
: 'Page Agent uses Zod for tool input schemas. Both Zod 3 (>=3.25.0) and Zod 4 are supported. Always import from the zod/v4 subpath. Zod Mini is not supported.'}
</p>
<CodeEditor
code={`import * as zod from 'zod'
import { PageAgent, tool } from 'page-agent'
// override internal tool
const customTools = {
ask_user: tool({
description:
'Ask the user or parent model a question and wait for their answer. Use this if you need more information or clarification.',
inputSchema: zod.object({
question: zod.string(),
}),
execute: async function (this: PageAgent, input) {
const answer = await do_some_thing(input.question)
return "✅ Received user answer: " + answer
},
})
}
// remove internal tool
const customTools = {
scroll: null, // never scroll
}
const pageAgent = new PageAgent({customTools})
`}
code={`// Zod 3 (>=3.25.0) or Zod 4
import { z } from 'zod/v4'`}
language="javascript"
/>
</section>
<section>
<Heading id="page-filter" className="text-2xl font-bold mb-4">
{isZh ? '页面过滤器' : 'Page Filter'}
<Heading id="define-tools" className="text-2xl font-bold mb-4">
{isZh ? '定义工具' : 'Define Tools'}
</Heading>
<BetaNotice />
<p className="text-gray-600 dark:text-gray-300 mb-4">
{isZh
? '通过 pageFilter 属性控制工具在哪些页面可见,提升安全性和用户体验。'
: 'Control tool visibility on specific pages via the pageFilter property to enhance security and UX.'}
? '使用 tool() 辅助函数定义自定义工具,每个工具包含 description、inputSchema 和 execute 三个属性。'
: 'Use the tool() helper to define custom tools with description, inputSchema, and execute.'}
</p>
<CodeEditor
code={`pageAgent.registerTool({
name: 'approveOrder',
description: '审批订单',
input: z.object({
orderId: z.string(),
approved: z.boolean()
}),
execute: async (params) => {
// 审批逻辑
code={`import { z } from 'zod/v4'
import { PageAgent, tool } from 'page-agent'
const pageAgent = new PageAgent({
customTools: {
//
add_to_cart: tool({
description: 'Add a product to the shopping cart by its product ID.',
inputSchema: z.object({
productId: z.string(),
quantity: z.number().min(1).default(1),
}),
execute: async function (input) {
await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify(input),
})
return \`Added \${input.quantity}x \${input.productId} to cart.\`
},
}),
//
search_knowledge_base: tool({
description: 'Search the internal knowledge base and return relevant articles.',
inputSchema: z.object({
query: z.string(),
limit: z.number().max(10).default(3),
}),
execute: async function (input) {
const res = await fetch(
\`/api/kb?q=\${encodeURIComponent(input.query)}&limit=\${input.limit}\`
)
const articles = await res.json()
return JSON.stringify(articles)
},
}),
},
// 可选:页面过滤器
pageFilter: {
// 只在订单管理页面显示
include: ['/admin/orders', '/admin/orders/*'],
// 排除特定页面
exclude: ['/admin/orders/archived']
}
})`}
language="javascript"
/>
</section>
<section>
<Heading id="best-practices" className="text-2xl font-bold mb-4">
{isZh ? '最佳实践' : 'Best Practices'}
<Heading id="override-remove" className="text-2xl font-bold mb-4">
{isZh ? '覆盖与移除内置工具' : 'Override & Remove Built-in Tools'}
</Heading>
<div className="space-y-4">
<div className="p-4 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg">
<h3 className="text-lg font-semibold text-yellow-900 dark:text-yellow-300 mb-2">
{isZh ? '⚡ 性能优化' : '⚡ Performance Optimization'}
</h3>
<ul className="text-gray-600 dark:text-gray-300 space-y-1 text-sm">
<li>
{isZh
? '• 使用 pageFilter 减少不必要的工具加载'
: '• Use pageFilter to reduce unnecessary tool loading'}
</li>
<li>
{isZh
? '• 在 execute 函数中实现适当的缓存机制'
: '• Implement appropriate caching in execute functions'}
</li>
<li>
{isZh
? '• 避免在工具中执行耗时的同步操作'
: '• Avoid long-running sync operations in tools'}
</li>
</ul>
</div>
</div>
<p className="text-gray-600 dark:text-gray-300 mb-4">
{isZh
? '使用相同的名称可以覆盖内置工具的行为,设置为 null 则完全移除该工具。'
: 'Use the same name to override a built-in tool, or set it to null to remove it entirely.'}
</p>
<CodeEditor
code={`const pageAgent = new PageAgent({
customTools: {
scroll: null, // remove scroll tool
execute_javascript: null, // remove script execution
},
})`}
language="javascript"
/>
</section>
</div>
</div>
@@ -10,11 +10,11 @@ const BASELINE = new Set([
'gemini-3-flash',
'deepseek-3.2',
'qwen3.5-plus',
'qwen3.5-flash',
])
// Models grouped by brand, newest first
const MODEL_GROUPS: Record<string, string[]> = {
OpenAI: ['gpt-5.2', 'gpt-5.1', 'gpt-5', 'gpt-5-mini', 'gpt-4.1', 'gpt-4.1-mini'],
Qwen: [
'qwen3.5-plus',
'qwen3.5-flash',
@@ -23,6 +23,7 @@ const MODEL_GROUPS: Record<string, string[]> = {
'qwen-3-plus',
'qwen3:14b (ollama)',
],
OpenAI: ['gpt-5.2', 'gpt-5.1', 'gpt-5', 'gpt-5-mini', 'gpt-4.1', 'gpt-4.1-mini'],
DeepSeek: ['deepseek-3.2'],
Google: ['gemini-3-pro', 'gemini-3-flash', 'gemini-2.5'],
Anthropic: [
@@ -247,16 +248,34 @@ LLM_MODEL_NAME="qwen3:14b"`}
{isZh
? '确保上下文长度大于输入 token 数,否则 Ollama 会静默截断 prompt。普通页面约需 15k token,随步骤增加。默认 4k 上下文长度无法正常工作'
: 'Ensure context length exceeds input tokens, or Ollama will silently truncate prompts. ~15k tokens for a typical page, increases with steps. Default 4k context length will NOT work'}
<ul className="text-sm text-gray-700 dark:text-gray-300 space-y-2 list-disc pl-5">
<li>
<code className="text-xs bg-gray-200 dark:bg-gray-700 px-1 rounded">
$env:OLLAMA_CONTEXT_LENGTH=64000; ollama serve
</code>
</li>
</ul>
</li>
</ul>
</div>
<div className="mt-4">
<h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-3">
{isZh ? '建议启动参数' : 'Recommended Startup'}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
{isZh
? '启动 Ollama 时建议配置以下环境变量:扩大上下文窗口、允许跨域访问、监听所有网络接口。'
: 'Start Ollama with these environment variables: larger context window, allow cross-origin access, and listen on all interfaces.'}
</p>
<div className="space-y-2">
<p className="text-xs font-medium text-gray-500 dark:text-gray-400">macOS / Linux</p>
<CodeEditor
code={`OLLAMA_CONTEXT_LENGTH=64000 OLLAMA_HOST=0.0.0.0:11434 OLLAMA_ORIGINS="*" ollama serve`}
/>
<p className="text-xs font-medium text-gray-500 dark:text-gray-400 pt-2">
Windows (PowerShell)
</p>
<CodeEditor
code={`$env:OLLAMA_CONTEXT_LENGTH=64000; $env:OLLAMA_HOST="0.0.0.0:11434"; $env:OLLAMA_ORIGINS="*"; ollama serve`}
/>
</div>
</div>
</section>
</div>
)
@@ -105,27 +105,6 @@ const pageAgentTool = {
</p>
</div>
</div>
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4 mb-6">
<h3 className="text-lg font-semibold text-yellow-900 dark:text-yellow-100 mb-2">
{isZh ? '⚠️ 注意事项' : '⚠️ Notes'}
</h3>
<ul className="text-yellow-800 dark:text-yellow-200 space-y-1 text-sm">
<li>
{' '}
{isZh
? '确保目标网站允许自动化操作'
: 'Ensure target website allows automated operations'}
</li>
<li> {isZh ? '实现适当的频率限制' : 'Implement appropriate rate limiting'}</li>
<li>
{' '}
{isZh
? '敏感操作建议要求人工确认'
: 'Recommend human confirmation for sensitive operations'}
</li>
</ul>
</div>
</div>
)
}
+14 -14
View File
@@ -6,15 +6,15 @@ import CustomUIDocs from './advanced/custom-ui/page'
import PageAgentCoreDocs from './advanced/page-agent-core/page'
// Advanced
import PageAgentDocs from './advanced/page-agent/page'
import PageControllerDocs from './advanced/page-controller/page'
import SecurityPermissions from './advanced/security-permissions/page'
// Features
import ChromeExtension from './features/chrome-extension/page'
import Instructions from './features/custom-instructions/page'
import CustomTools from './features/custom-tools/page'
import DataMasking from './features/data-masking/page'
import Models from './features/models/page'
import BestPractices from './integration/best-practices/page'
import SecurityPermissions from './integration/security-permissions/page'
import ThirdPartyAgent from './integration/third-party-agent/page'
import ThirdPartyAgent from './features/third-party-agent/page'
import Limitations from './introduction/limitations/page'
// Introduction
import Overview from './introduction/overview/page'
@@ -80,6 +80,11 @@ export default function DocsRouter() {
<ChromeExtension />
</DocsPage>
</Route>
<Route path="/features/third-party-agent">
<DocsPage>
<ThirdPartyAgent />
</DocsPage>
</Route>
{/* Advanced */}
<Route path="/advanced/page-agent">
@@ -92,26 +97,21 @@ export default function DocsRouter() {
<PageAgentCoreDocs />
</DocsPage>
</Route>
<Route path="/advanced/page-controller">
<DocsPage>
<PageControllerDocs />
</DocsPage>
</Route>
<Route path="/advanced/custom-ui">
<DocsPage>
<CustomUIDocs />
</DocsPage>
</Route>
<Route path="/integration/security-permissions">
<Route path="/advanced/security-permissions">
<DocsPage>
<SecurityPermissions />
</DocsPage>
</Route>
<Route path="/integration/best-practices">
<DocsPage>
<BestPractices />
</DocsPage>
</Route>
<Route path="/integration/third-party-agent">
<DocsPage>
<ThirdPartyAgent />
</DocsPage>
</Route>
{/* Default redirect or 404 */}
<Route path="/docs">
@@ -1,20 +0,0 @@
import BetaNotice from '@/components/BetaNotice'
import CodeEditor from '@/components/CodeEditor'
import { useLanguage } from '@/i18n/context'
export default function BestPractices() {
const { isZh } = useLanguage()
return (
<div>
<h1 className="text-4xl font-bold mb-6">{isZh ? '最佳实践' : 'Best Practices'}</h1>
<BetaNotice />
<p className="text-xl text-gray-600 dark:text-gray-300 mb-6 leading-relaxed">
{isZh
? '使用 page-agent 的最佳实践和常见问题解决方案。'
: 'Best practices and common solutions for using page-agent.'}
</p>
<CodeEditor code={`// TODO`} />
</div>
)
}
@@ -83,7 +83,7 @@ export default function LimitationsPage() {
</div>
{/* Interaction Limitations */}
<Heading id="interaction-capabilities" className="text-2xl font-bold mb-3">
<Heading id="interaction-capabilities" className="text-2xl font-bold mb-3 mt-6">
{isZh ? '交互能力' : 'Interaction Capabilities'}
</Heading>
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6 mb-6">
@@ -128,33 +128,25 @@ export default function LimitationsPage() {
</div>
{/* Understanding Limitations */}
<Heading id="understanding" className="text-2xl font-bold mb-3">
<Heading id="understanding" className="text-2xl font-bold mb-3 mt-6">
{isZh ? '理解能力' : 'Understanding'}
</Heading>
<div className="bg-amber-50 dark:bg-amber-900/20 border-l-4 border-amber-400 p-4 mb-6">
<p className="text-amber-800 dark:text-amber-200 mb-2 font-medium">
{isZh
? 'Page Agent 不使用多模态模型,不截图,没有视觉能力。仅通过 DOM 结构理解页面。'
: 'Page Agent does not use multimodal models, does not take screenshots, and has no visual capability. It reads pages through DOM structure only.'}
</p>
<p className="text-amber-700 dark:text-amber-300 text-sm">
{isZh
? '图片、Canvas、WebGL、SVG 等视觉内容无法被识别。页面的语义化程度和可访问性直接影响 AI 的理解准确性。'
: 'Images, Canvas, WebGL, SVG and other visual content cannot be recognized. Page semantic quality and accessibility directly affect AI accuracy.'}
</p>
</div>
{/* Website Quality */}
<Heading id="page-quality-matters" className="text-2xl font-bold mb-3">
{isZh ? '网页质量影响' : 'Page Quality Matters'}
</Heading>
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6">
<p className="text-gray-600 dark:text-gray-300 text-sm">
{isZh
? '反常识的交互逻辑、纯视觉的操作提示、快速出现消失的元素等都会降低自动化成功率。语义化的 HTML 和良好的可访问性会显著提升效果。'
: 'Counter-intuitive interactions, visual-only cues, and rapidly appearing/disappearing elements reduce automation success. Semantic HTML and good accessibility significantly improve results.'}
</p>
</div>
<p className="mb-2 font-medium">
{isZh
? 'Page Agent 不使用多模态模型,不截图,没有视觉能力。仅通过 DOM 结构理解页面。'
: 'Page Agent does not use multimodal models, does not take screenshots, and has no visual capability. It reads pages through DOM structure only.'}
</p>
<p className="mb-2 font-medium">
{isZh
? '图片、Canvas、WebGL、SVG 等视觉内容无法被识别。页面的语义化程度和可访问性直接影响 AI 的理解准确性。'
: 'Images, Canvas, WebGL, SVG and other visual content cannot be recognized. Page semantic quality and accessibility directly affect AI accuracy.'}
</p>
<p className="mb-2 font-medium">
{isZh
? '反常识的交互逻辑、纯视觉的操作提示、快速出现消失的元素等都会降低自动化成功率。语义化的 HTML 和良好的可访问性会显著提升效果。'
: 'Counter-intuitive interactions, visual-only cues, and rapidly appearing/disappearing elements reduce automation success. Semantic HTML and good accessibility significantly improve results.'}
</p>
</div>
</div>
)
@@ -62,7 +62,7 @@ export default function FeaturesSection() {
color="#ef4444aa"
strokeWidth={1.5}
// multiline={false}
isView
// isView
>
{cmd}
</Highlighter>
@@ -1,5 +1,5 @@
/* eslint-disable react-dom/no-dangerously-set-innerhtml */
import { PageAgent } from 'page-agent'
import type { PageAgent as PageAgentType } from 'page-agent'
import { useEffect, useState } from 'react'
import { Link, useSearchParams } from 'wouter'
@@ -16,6 +16,8 @@ import {
} from '../../constants'
import { useLanguage } from '../../i18n/context'
let pageAgentModule: Promise<typeof import('page-agent')> | null = null
function getInjection(useCN?: boolean) {
const cdn = useCN ? CDN_DEMO_CN_URL : CDN_DEMO_URL
@@ -55,13 +57,20 @@ export default function HeroSection() {
const [activeTab, setActiveTab] = useState<'try' | 'other'>(isOther ? 'other' : 'try')
const [cdnSource, setCdnSource] = useState<'international' | 'china'>('international')
const handleExecute = async () => {
if (!task.trim()) return
const [ready, setReady] = useState(false)
useEffect(() => {
pageAgentModule ??= import('page-agent')
pageAgentModule.then(() => setReady(true))
}, [])
const handleExecute = async () => {
if (!task.trim() || !ready || !pageAgentModule) return
const { PageAgent } = await pageAgentModule
const win = window as any
if (!win.pageAgent || win.pageAgent.disposed) {
win.pageAgent = new PageAgent({
win.pageAgent = new (PageAgent as typeof PageAgentType)({
interactiveBlacklist: [document.getElementById('root')!],
language: language,
@@ -208,10 +217,21 @@ export default function HeroSection() {
/>
<button
onClick={handleExecute}
disabled={!ready}
className="absolute right-2 top-2 px-5 py-1.5 bg-linear-to-r from-blue-600 to-purple-600 text-white font-medium rounded-md hover:shadow-md transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none text-sm"
data-page-agent-not-interactive
>
{isZh ? '执行' : 'Run'}
{ready ? (
isZh ? (
'执行'
) : (
'Run'
)
) : (
<span className="animate-pulse">
{isZh ? '准备中...' : 'Preparing...'}
</span>
)}
</button>
</div>
<p className="text-xs text-gray-500 dark:text-gray-400 text-left">
@@ -8,7 +8,10 @@ export default function ScenariosSection() {
const { isZh } = useLanguage()
return (
<section className="px-6 py-16" aria-labelledby="scenarios-heading">
<section
className="px-6 py-16 bg-linear-to-b from-blue-100 to-purple-100 dark:from-blue-950/40 dark:to-gray-800"
aria-labelledby="scenarios-heading"
>
<div className="max-w-6xl mx-auto">
<BlurFade inView>
<div className="text-center mb-12">
+27
View File
@@ -0,0 +1,27 @@
import { Suspense, lazy } from 'react'
import HeroSection from './HeroSection'
const FeaturesSection = lazy(() => import('./FeaturesSection'))
const ScenariosSection = lazy(() => import('./ScenariosSection'))
const OneMoreThingSection = lazy(() => import('./OneMoreThingSection'))
export default function HomePage() {
return (
<>
<HeroSection />
<Suspense
fallback={
<div className="flex items-center justify-center gap-3 py-20 text-gray-400">
<div className="w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
Loading...
</div>
}
>
<FeaturesSection />
<ScenariosSection />
<OneMoreThingSection />
</Suspense>
</>
)
}
+22 -4
View File
@@ -1,10 +1,12 @@
import { Suspense, useLayoutEffect } from 'react'
import { Suspense, lazy, useEffect, useLayoutEffect } from 'react'
import { Route, Switch, useLocation } from 'wouter'
import Footer from './components/Footer'
import Header from './components/Header'
import HomePage from './pages/Home'
import DocsPages from './pages/docs/index'
import HomePage from './pages/home'
const docsImport = () => import('./pages/docs')
const DocsPages = lazy(docsImport)
function ScrollToTop() {
const [pathname] = useLocation()
@@ -15,6 +17,13 @@ function ScrollToTop() {
}
export default function Router() {
useEffect(() => {
const schedule = globalThis.requestIdleCallback ?? ((cb: () => void) => setTimeout(cb, 1))
const cancel = globalThis.cancelIdleCallback ?? clearTimeout
const id = schedule(() => docsImport())
return () => cancel(id)
}, [])
return (
<div className="flex min-h-screen flex-col">
<Header />
@@ -32,7 +41,16 @@ export default function Router() {
<Route path="/docs" nest>
<div className="flex-1 bg-white dark:bg-gray-900">
<DocsPages />
<Suspense
fallback={
<div className="flex items-center justify-center gap-3 py-20 text-gray-400">
<div className="w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
Loading...
</div>
}
>
<DocsPages />
</Suspense>
</div>
</Route>
+8 -3
View File
@@ -27,12 +27,12 @@ const SPA_ROUTES = [
'docs/features/custom-instructions',
'docs/features/models',
'docs/features/chrome-extension',
'docs/features/third-party-agent',
'docs/advanced/page-agent',
'docs/advanced/page-agent-core',
'docs/advanced/page-controller',
'docs/advanced/custom-ui',
'docs/integration/security-permissions',
'docs/integration/best-practices',
'docs/integration/third-party-agent',
'docs/advanced/security-permissions',
]
function spaRoutes() {
@@ -64,6 +64,11 @@ export default defineConfig(({ mode }) => ({
if (message.code === 'EVAL') return
handler(message)
},
output: {
manualChunks: {
vendor: ['react', 'react-dom', 'wouter'],
},
},
},
},
resolve: {