@53able/pi-agent-browser 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.ja-JP.md +194 -0
- package/README.md +30 -1
- package/docs/i18n.md +59 -0
- package/docs/project-history.en.md +154 -0
- package/docs/project-history.ja-JP.md +154 -0
- package/docs/project-history.md +6 -0
- package/index.ts +210 -3
- package/package.json +3 -1
- package/skills/agent-browser/SKILL.md +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Moved the Japanese README to `README.ja-JP.md` and added top-level language switchers.
|
|
8
|
+
- Added a documentation localization policy based on Trendshift repository examples.
|
|
9
|
+
|
|
3
10
|
## 0.1.1
|
|
4
11
|
|
|
5
12
|
### Changed
|
package/README.ja-JP.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# pi-agent-browser
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 日本語
|
|
4
|
+
|
|
5
|
+
`pi-agent-browser` は、Pi から実ブラウザを操作するための typed tools を追加する拡張です。内部では [`agent-browser`](https://github.com/vercel-labs/agent-browser) を使います。
|
|
6
|
+
|
|
7
|
+
Pi が Web ページを開き、rendered context を抽出し、ログイン状態を保ち、スクリーンショットを証跡として残す必要があるときに使います。raw shell command を毎回組み立てる代わりに、Pi の tool として呼び出せます。
|
|
8
|
+
|
|
9
|
+
## できること
|
|
10
|
+
|
|
11
|
+
- **Rendered page の context extraction** — 公開ページやログイン済み browser tab から text を読む
|
|
12
|
+
- **安定した操作対象** — accessibility snapshot の `@ref` を使って click / fill する
|
|
13
|
+
- **証跡保存** — screenshot や抽出 text を artifact path に保存する
|
|
14
|
+
- **Session persistence** — 名前付き `agent-browser` session と restore でログイン状態を再利用する
|
|
15
|
+
- **安全な browsing control** — `allowedDomains`、`maxOutput`、state management で操作範囲を絞る
|
|
16
|
+
- **Typed Pi tools** — 1つの自由形式 command string ではなく、操作ごとの schema を使う
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
- package support 付きの Pi / Feynman runtime
|
|
21
|
+
- [`agent-browser`](https://github.com/vercel-labs/agent-browser) が `PATH` 上にあること
|
|
22
|
+
- `agent-browser install` で Chrome for Testing が入っていること
|
|
23
|
+
|
|
24
|
+
確認:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
agent-browser doctor
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`agent-browser` が `PATH` 外にある場合:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
export AGENT_BROWSER_BIN=/absolute/path/to/agent-browser
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
npm から install:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pi install npm:@53able/pi-agent-browser
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
GitHub から install:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pi install https://github.com/53able/pi-agent-browser
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
local checkout から install:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
git clone https://github.com/53able/pi-agent-browser.git
|
|
54
|
+
pi install ./pi-agent-browser
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Registered tools
|
|
58
|
+
|
|
59
|
+
- `agent_browser_open` — URL を開く、または `about:blank` を起動する
|
|
60
|
+
- `agent_browser_read` — URL または active rendered tab から text / JSON を抽出する
|
|
61
|
+
- `agent_browser_snapshot` — 操作用の accessibility tree refs を取得する
|
|
62
|
+
- `agent_browser_click` — `@ref` または CSS selector で click する
|
|
63
|
+
- `agent_browser_fill` — `@ref` または CSS selector で input を埋める
|
|
64
|
+
- `agent_browser_screenshot` — screenshot を disk に保存する
|
|
65
|
+
- `agent_browser_eval` — active tab で JavaScript を評価する
|
|
66
|
+
- `agent_browser_state` — saved state を save / load / list / show / rename / clear / clean する
|
|
67
|
+
- `agent_browser_close` — browser session を閉じる
|
|
68
|
+
- `agent_browser_handoff` — 2FA・CAPTCHA・本人確認のために一時停止し、人間に操作を戻す
|
|
69
|
+
- `agent_browser_commit` — 決済・削除・投稿・送信など、やり直しのきかない操作の確認ゲート
|
|
70
|
+
- `agent_browser_doctor` — `agent-browser doctor` を実行する
|
|
71
|
+
|
|
72
|
+
## よく使う流れ
|
|
73
|
+
|
|
74
|
+
### ページを読んで抽出結果を保存する
|
|
75
|
+
|
|
76
|
+
Pi にはそのまま依頼できます。
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
https://example.com をブラウザ経由で読み、outputs/browser/example.md に保存して
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`agent_browser_read` の引数で表すと次の形です。
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"url": "https://example.com",
|
|
87
|
+
"outputPath": "outputs/browser/example.md",
|
|
88
|
+
"allowedDomains": ["example.com"]
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### rendered page を操作する
|
|
93
|
+
|
|
94
|
+
1. ページを開きます。
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"url": "https://example.com",
|
|
99
|
+
"session": "research-example",
|
|
100
|
+
"allowedDomains": ["example.com"]
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
2. `agent_browser_snapshot` で新しい `@ref` を取得します。
|
|
105
|
+
3. `agent_browser_click` または `agent_browser_fill` にその ref を渡します。
|
|
106
|
+
4. text が必要なら `agent_browser_read`、見た目の証跡が必要なら `agent_browser_screenshot` を使います。
|
|
107
|
+
5. 終わったら `agent_browser_close` で session を閉じます。
|
|
108
|
+
|
|
109
|
+
### ログイン状態を再利用する
|
|
110
|
+
|
|
111
|
+
安定した session name と `restore: true` を使います。
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"url": "https://example.com",
|
|
116
|
+
"session": "work-example",
|
|
117
|
+
"restore": true,
|
|
118
|
+
"headed": true
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
一度ログインしたあと、同じ `session` と `restore` で開くと saved browser state を再利用できます。
|
|
123
|
+
|
|
124
|
+
### saved state を確認・削除する
|
|
125
|
+
|
|
126
|
+
一覧:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"operation": "list"
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
特定 session の state を削除:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"operation": "clear",
|
|
139
|
+
"sessionName": "work-example"
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
すべて削除する場合だけ、明示的に `all` を使います。
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"operation": "clear",
|
|
148
|
+
"all": true
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Safety guidance
|
|
153
|
+
|
|
154
|
+
- browsing scope が分かっている場合は `allowedDomains` を渡してください。
|
|
155
|
+
- report、audit、後日の verification に使う抽出結果は `outputPath` に保存してください。
|
|
156
|
+
- navigation、form submit、modal dismissal、failed click のあとには fresh snapshot を取り直してください。
|
|
157
|
+
- text evidence には `agent_browser_read`、visual evidence には `agent_browser_screenshot` を優先してください。
|
|
158
|
+
- `agent_browser_eval` は、`read` や `snapshot` では必要な state が取れない場合にだけ使ってください。
|
|
159
|
+
- saved browser state を広く消す前に、対象を確認してください。
|
|
160
|
+
|
|
161
|
+
## 人間への handoff(引き渡し)
|
|
162
|
+
|
|
163
|
+
すべての操作を無人で任せるべきではありません。適切なタイミングで人間に操作を戻すためのツールが2つあります。
|
|
164
|
+
|
|
165
|
+
- **安全な境界線** — `agent_browser_handoff` は、AI自身では対応できない・すべきでない操作(2FA/OTPの入力、CAPTCHA、本人確認)の直前でエージェントを一時停止させます。スクリーンショットで状況を記録したあと、実際のUIダイアログでブロックし、人間が「対応完了」または「中止」を選ぶまで待ちます。`agent_browser_read` / `agent_browser_snapshot` も簡易なキーワード検知を行い、こうしたチェックポイントらしき内容を出力に警告として付加します(この警告自体は停止ではなく、`agent_browser_handoff` を呼ぶきっかけとして扱ってください)。
|
|
166
|
+
- **Commit前の停止** — `agent_browser_commit` は、決済・削除・投稿・送信といった「やり直しのきかない操作」専用の確認ゲートです。証跡を保存し、クリックの前に必ず人間の明示的な確認を求め、承認されない限りクリックは実行されません。対話的なUIが利用できない場合は、クリックせずにそのまま拒否します。こうした操作の最終確認クリックには `agent_browser_click` ではなく必ずこちらを使ってください。
|
|
167
|
+
|
|
168
|
+
どちらのツールも対話的なセッション(TUI、またはUI対応のRPC)を必要とします。無人・headless実行では、黙って進めるのではなく拒否します。
|
|
169
|
+
|
|
170
|
+
## Troubleshooting
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
agent-browser doctor
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
または Pi に `agent_browser_doctor` を実行させてください。
|
|
177
|
+
|
|
178
|
+
よくある修正:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
npm install -g agent-browser
|
|
182
|
+
agent-browser install
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Project history
|
|
186
|
+
|
|
187
|
+
package 名の判断、npm Trusted Publishing、semantic-release workflow については、言語別の project history を参照してください。
|
|
188
|
+
|
|
189
|
+
- [English](docs/project-history.en.md)
|
|
190
|
+
- [日本語](docs/project-history.ja-JP.md)
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
MIT
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# pi-agent-browser
|
|
2
2
|
|
|
3
|
+
English | [日本語](README.ja-JP.md)
|
|
4
|
+
|
|
3
5
|
Typed browser automation tools for Pi, powered by [`agent-browser`](https://github.com/vercel-labs/agent-browser).
|
|
4
6
|
|
|
5
7
|
Use this package when Pi needs to operate real web pages, extract rendered context, keep login sessions, or capture visual evidence without falling back to ad-hoc shell commands.
|
|
@@ -63,6 +65,8 @@ pi install ./pi-agent-browser
|
|
|
63
65
|
- `agent_browser_eval` — evaluate JavaScript in the active tab
|
|
64
66
|
- `agent_browser_state` — save, load, list, show, rename, clear, or clean saved state
|
|
65
67
|
- `agent_browser_close` — close a browser session
|
|
68
|
+
- `agent_browser_handoff` — pause and hand control to a human for 2FA, CAPTCHA, or identity verification
|
|
69
|
+
- `agent_browser_commit` — confirmation gate for irreversible actions (payment, delete, publish, send)
|
|
66
70
|
- `agent_browser_doctor` — run `agent-browser doctor`
|
|
67
71
|
|
|
68
72
|
## Common workflows
|
|
@@ -72,7 +76,7 @@ pi install ./pi-agent-browser
|
|
|
72
76
|
Ask Pi:
|
|
73
77
|
|
|
74
78
|
```text
|
|
75
|
-
https://example.com
|
|
79
|
+
Read https://example.com through the browser and save the extracted text to outputs/browser/example.md.
|
|
76
80
|
```
|
|
77
81
|
|
|
78
82
|
Equivalent `agent_browser_read` arguments:
|
|
@@ -154,6 +158,15 @@ Clear all saved states only when intentionally resetting everything:
|
|
|
154
158
|
- Use `agent_browser_eval` only when `read` or `snapshot` cannot expose the required state.
|
|
155
159
|
- Review saved browser state before clearing broadly.
|
|
156
160
|
|
|
161
|
+
## Human handoff
|
|
162
|
+
|
|
163
|
+
Not every step should run unattended. Two tools hand control back to a human at the right moments:
|
|
164
|
+
|
|
165
|
+
- **Safe boundaries** — `agent_browser_handoff` pauses the agent for steps it cannot or should not perform itself: 2FA/OTP entry, CAPTCHA, identity verification. It captures a screenshot for context, then blocks on a real confirmation dialog until a human resumes or aborts. `agent_browser_read` and `agent_browser_snapshot` also run a lightweight keyword scan and flag likely checkpoints in their output as a nudge to call this tool.
|
|
166
|
+
- **Stop before commit** — `agent_browser_commit` is a dedicated gate for irreversible actions: payment, deletion, publishing, sending. It captures evidence, requires explicit human confirmation before clicking, and refuses outright (without clicking) if no interactive UI is available to ask. Use it instead of `agent_browser_click` for the final confirming step of any such action.
|
|
167
|
+
|
|
168
|
+
Both tools require an interactive session (TUI or RPC with UI support); in headless/unattended runs they refuse rather than proceed silently.
|
|
169
|
+
|
|
157
170
|
## Troubleshooting
|
|
158
171
|
|
|
159
172
|
Run:
|
|
@@ -171,6 +184,22 @@ npm install -g agent-browser
|
|
|
171
184
|
agent-browser install
|
|
172
185
|
```
|
|
173
186
|
|
|
187
|
+
## Project history
|
|
188
|
+
|
|
189
|
+
If you maintain or contribute to this package, read the project history in [English](docs/project-history.en.md) or [Japanese](docs/project-history.ja-JP.md) for the package-name decision, npm Trusted Publishing setup, and semantic-release workflow.
|
|
190
|
+
|
|
191
|
+
## Release automation
|
|
192
|
+
|
|
193
|
+
Releases are automated with semantic-release. Maintainers merge Conventional Commits into `main`; GitHub Actions runs `npx semantic-release@25`, determines the next version, publishes to npm through Trusted Publishing, creates the Git tag, and writes the GitHub Release.
|
|
194
|
+
|
|
195
|
+
Commit message examples:
|
|
196
|
+
|
|
197
|
+
- `fix: handle empty snapshot output` → patch release
|
|
198
|
+
- `feat: add browser state export tool` → minor release
|
|
199
|
+
- `feat!: rename tool arguments` or `BREAKING CHANGE:` → major release
|
|
200
|
+
|
|
201
|
+
The npm Trusted Publisher should continue to point at `.github/workflows/publish.yml`. No long-lived `NPM_TOKEN` is required.
|
|
202
|
+
|
|
174
203
|
## License
|
|
175
204
|
|
|
176
205
|
MIT
|
package/docs/i18n.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Documentation localization policy
|
|
2
|
+
|
|
3
|
+
This project keeps English as the primary README language and provides Japanese as a first translated README.
|
|
4
|
+
|
|
5
|
+
The current structure follows patterns observed in trending GitHub repositories via Trendshift:
|
|
6
|
+
|
|
7
|
+
- `alibaba/open-code-review` places language links at the top of `README.md` and keeps translated files such as `README.ja-JP.md`, `README.zh-CN.md`, and localized `CONTRIBUTING.*.md` files.
|
|
8
|
+
- `shiyu-coder/Kronos` places a compact language switcher directly under the project title and links to multiple translated README variants.
|
|
9
|
+
- `mattpocock/skills`, `earendil-works/pi`, `likec4/likec4`, and `block/buzz` keep the main README focused on the strongest user path; this is useful even when a project later adds translations.
|
|
10
|
+
|
|
11
|
+
## File naming
|
|
12
|
+
|
|
13
|
+
Use locale-suffixed Markdown files at the repository root for user-facing README translations:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
README.md # English, default for GitHub and npm
|
|
17
|
+
README.ja-JP.md # Japanese
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Use locale-suffixed files under `docs/` for maintainer documentation:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
docs/project-history.en.md
|
|
24
|
+
docs/project-history.ja-JP.md
|
|
25
|
+
docs/project-history.md # language index
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## README language switcher
|
|
29
|
+
|
|
30
|
+
The top of each README should include a short language switcher:
|
|
31
|
+
|
|
32
|
+
```md
|
|
33
|
+
English | [日本語](README.ja-JP.md)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Japanese README:
|
|
37
|
+
|
|
38
|
+
```md
|
|
39
|
+
[English](README.md) | 日本語
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Keep this near the title so readers can switch before scanning installation instructions.
|
|
43
|
+
|
|
44
|
+
## Translation scope
|
|
45
|
+
|
|
46
|
+
Translate user-facing material first:
|
|
47
|
+
|
|
48
|
+
- what the package does
|
|
49
|
+
- installation
|
|
50
|
+
- registered tools
|
|
51
|
+
- common workflows
|
|
52
|
+
- safety guidance
|
|
53
|
+
- troubleshooting
|
|
54
|
+
|
|
55
|
+
Maintainer-only material can live in `docs/` and should be translated when it affects contribution or release work.
|
|
56
|
+
|
|
57
|
+
## Maintenance rule
|
|
58
|
+
|
|
59
|
+
When changing user-facing README content, update both `README.md` and `README.ja-JP.md` in the same pull request. If a section exists in only one language, make that explicit rather than leaving readers to guess whether the other file is stale.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Why this package exists
|
|
2
|
+
|
|
3
|
+
This note explains the decisions behind `@53able/pi-agent-browser`: why it was created, why it uses a scoped npm name, and how releases are automated.
|
|
4
|
+
|
|
5
|
+
It is mainly for maintainers and contributors. If you only want to install and use the package, start with the [README](../README.md).
|
|
6
|
+
|
|
7
|
+
## The problem we wanted to solve
|
|
8
|
+
|
|
9
|
+
Pi can already run shell commands, so it can call [`agent-browser`](https://github.com/vercel-labs/agent-browser) directly. That works for quick experiments, but it has a weak interface for repeatable agent work:
|
|
10
|
+
|
|
11
|
+
- every browser action is a shell string
|
|
12
|
+
- outputs are not consistently shaped
|
|
13
|
+
- saved screenshots and extracted text are easy to lose
|
|
14
|
+
- login/session state needs manual handling
|
|
15
|
+
- safety options such as `allowedDomains` are easy to forget
|
|
16
|
+
|
|
17
|
+
The extension began as a local wrapper around `agent-browser` to solve those issues. The goal was not to replace `agent-browser`; it was to make its browser capabilities feel native inside Pi.
|
|
18
|
+
|
|
19
|
+
## What was extracted
|
|
20
|
+
|
|
21
|
+
The local extension exposed browser actions as typed Pi tools:
|
|
22
|
+
|
|
23
|
+
- `agent_browser_open`
|
|
24
|
+
- `agent_browser_read`
|
|
25
|
+
- `agent_browser_snapshot`
|
|
26
|
+
- `agent_browser_click`
|
|
27
|
+
- `agent_browser_fill`
|
|
28
|
+
- `agent_browser_screenshot`
|
|
29
|
+
- `agent_browser_eval`
|
|
30
|
+
- `agent_browser_state`
|
|
31
|
+
- `agent_browser_close`
|
|
32
|
+
- `agent_browser_doctor`
|
|
33
|
+
|
|
34
|
+
This made each action explicit. Pi can see the expected arguments, return artifact paths, preserve session names, and guide the model toward safer browsing patterns.
|
|
35
|
+
|
|
36
|
+
Once the local version worked, it was moved into a standalone repository so other Pi users could install it.
|
|
37
|
+
|
|
38
|
+
Repository:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
https://github.com/53able/pi-agent-browser
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Why the package is scoped
|
|
45
|
+
|
|
46
|
+
The unscoped npm name `pi-agent-browser` was already taken:
|
|
47
|
+
|
|
48
|
+
- npm: https://www.npmjs.com/package/pi-agent-browser
|
|
49
|
+
- repository metadata: `github.com/coctostan/pi-agent-browser`
|
|
50
|
+
|
|
51
|
+
That package has a similar goal, but a different interface. It exposes one generic `browser` tool that accepts an `agent-browser` command string.
|
|
52
|
+
|
|
53
|
+
This package uses multiple typed tools instead. The tradeoff is intentional:
|
|
54
|
+
|
|
55
|
+
| Package | Interface | Best fit |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `pi-agent-browser` | One free-form `browser` command string | Flexible CLI-style control |
|
|
58
|
+
| `@53able/pi-agent-browser` | Separate typed `agent_browser_*` tools | Structured Pi workflows, saved artifacts, session/state handling |
|
|
59
|
+
|
|
60
|
+
To avoid confusion and make ownership clear, this package is published as:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
@53able/pi-agent-browser
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## npm and Trusted Publishing setup
|
|
67
|
+
|
|
68
|
+
The first npm release created the package:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
@53able/pi-agent-browser@0.1.0
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
After the package existed on npm, Trusted Publisher was configured for GitHub Actions:
|
|
75
|
+
|
|
76
|
+
- npm package: `@53able/pi-agent-browser`
|
|
77
|
+
- GitHub owner: `53able`
|
|
78
|
+
- GitHub repository: `pi-agent-browser`
|
|
79
|
+
- Workflow filename: `publish.yml`
|
|
80
|
+
- Allowed action: `npm publish`
|
|
81
|
+
|
|
82
|
+
This lets GitHub Actions publish through npm OIDC. The repository does not need a long-lived `NPM_TOKEN` secret for publishing.
|
|
83
|
+
|
|
84
|
+
## README cleanup
|
|
85
|
+
|
|
86
|
+
The first npm README mixed user-facing instructions with maintainer details such as publishing setup. That made the package page less useful for people deciding whether to install it.
|
|
87
|
+
|
|
88
|
+
The README was revised to lead with user value:
|
|
89
|
+
|
|
90
|
+
- rendered-page context extraction
|
|
91
|
+
- stable `@ref` handles for interaction
|
|
92
|
+
- screenshot and artifact capture
|
|
93
|
+
- persistent browser sessions
|
|
94
|
+
- safer browsing controls
|
|
95
|
+
- typed Pi tools instead of raw command strings
|
|
96
|
+
|
|
97
|
+
That user-focused README was released as `0.1.1`.
|
|
98
|
+
|
|
99
|
+
## Current release workflow
|
|
100
|
+
|
|
101
|
+
Releases now run through [`semantic-release`](https://github.com/semantic-release/semantic-release).
|
|
102
|
+
|
|
103
|
+
The current flow is:
|
|
104
|
+
|
|
105
|
+
1. A maintainer merges commits into `main`.
|
|
106
|
+
2. GitHub Actions runs `.github/workflows/publish.yml`.
|
|
107
|
+
3. The workflow runs `npx semantic-release@25`.
|
|
108
|
+
4. semantic-release reads commits since the latest release tag.
|
|
109
|
+
5. If a release is needed, semantic-release chooses the next version, publishes to npm, creates the Git tag, and creates a GitHub Release.
|
|
110
|
+
6. npm publish uses Trusted Publishing / OIDC.
|
|
111
|
+
|
|
112
|
+
The workflow deliberately avoids `actions/setup-node`'s `registry-url` option. That option can create an `.npmrc` that interferes with semantic-release's npm authentication flow.
|
|
113
|
+
|
|
114
|
+
## Commit messages drive releases
|
|
115
|
+
|
|
116
|
+
Use Conventional Commits on `main`.
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
fix: handle empty snapshot output
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Creates a patch release.
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
feat: add browser state export tool
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Creates a minor release.
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
feat!: rename browser state arguments
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Creates a major release.
|
|
135
|
+
|
|
136
|
+
A commit such as `docs: ...`, `ci: ...`, or `chore: ...` can still run the workflow, but it should not publish a new npm version.
|
|
137
|
+
|
|
138
|
+
## Release workflow check
|
|
139
|
+
|
|
140
|
+
After the semantic-release migration, the workflow ran successfully. semantic-release reported that npm OIDC authentication worked and that no release was needed for the `ci:` commit:
|
|
141
|
+
|
|
142
|
+
```text
|
|
143
|
+
OIDC token exchange with the npm registry succeeded
|
|
144
|
+
Found git tag v0.1.1 associated with version 0.1.1
|
|
145
|
+
There are no relevant changes, so no new version is released.
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
That means the authentication and release plumbing are in place. The next `fix:`, `feat:`, or breaking-change commit on `main` should produce the corresponding npm release.
|
|
149
|
+
|
|
150
|
+
## User install command
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pi install npm:@53able/pi-agent-browser
|
|
154
|
+
```
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# このパッケージを作った理由
|
|
2
|
+
|
|
3
|
+
この文書は、`@53able/pi-agent-browser` を作った理由、scoped package 名を選んだ理由、リリース自動化の流れを記録したものです。
|
|
4
|
+
|
|
5
|
+
主な読者はメンテナとコントリビュータです。インストールして使いたいだけの場合は、まず [README](../README.md) を読んでください。
|
|
6
|
+
|
|
7
|
+
## 解決したかった問題
|
|
8
|
+
|
|
9
|
+
Pi は shell command を実行できるため、[`agent-browser`](https://github.com/vercel-labs/agent-browser) を直接呼び出せます。短い検証ならそれで十分です。ただし、繰り返し使うエージェントの作業としては、次の弱さがありました。
|
|
10
|
+
|
|
11
|
+
- ブラウザ操作がすべて文字列コマンドになる
|
|
12
|
+
- 出力の形がそろわない
|
|
13
|
+
- 保存したスクリーンショットや抽出テキストを見失いやすい
|
|
14
|
+
- ログイン状態や session state を手で扱う必要がある
|
|
15
|
+
- `allowedDomains` などの安全設定を忘れやすい
|
|
16
|
+
|
|
17
|
+
この拡張は、それらを解消するためのローカル wrapper として始まりました。`agent-browser` を置き換えることではなく、Pi の中で `agent-browser` の機能を自然に使えるようにすることが目的です。
|
|
18
|
+
|
|
19
|
+
## 切り出したもの
|
|
20
|
+
|
|
21
|
+
ローカル拡張では、ブラウザ操作を次の typed Pi tools として公開しました。
|
|
22
|
+
|
|
23
|
+
- `agent_browser_open`
|
|
24
|
+
- `agent_browser_read`
|
|
25
|
+
- `agent_browser_snapshot`
|
|
26
|
+
- `agent_browser_click`
|
|
27
|
+
- `agent_browser_fill`
|
|
28
|
+
- `agent_browser_screenshot`
|
|
29
|
+
- `agent_browser_eval`
|
|
30
|
+
- `agent_browser_state`
|
|
31
|
+
- `agent_browser_close`
|
|
32
|
+
- `agent_browser_doctor`
|
|
33
|
+
|
|
34
|
+
各操作を分けることで、Pi は引数の形を把握できます。artifact path を返したり、session 名を保ったり、安全なブラウザ操作へ誘導したりしやすくなります。
|
|
35
|
+
|
|
36
|
+
ローカル版が動いたあと、他の Pi ユーザーもインストールできるように standalone repository へ移しました。
|
|
37
|
+
|
|
38
|
+
Repository:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
https://github.com/53able/pi-agent-browser
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## scoped package にした理由
|
|
45
|
+
|
|
46
|
+
unscoped な npm package 名 `pi-agent-browser` は、すでに使われていました。
|
|
47
|
+
|
|
48
|
+
- npm: https://www.npmjs.com/package/pi-agent-browser
|
|
49
|
+
- repository metadata: `github.com/coctostan/pi-agent-browser`
|
|
50
|
+
|
|
51
|
+
既存パッケージも `agent-browser` を Pi から使うという意味では近い目的を持っています。ただし interface が違います。既存パッケージは、`agent-browser` のコマンド文字列を受け取る単一の `browser` tool を公開しています。
|
|
52
|
+
|
|
53
|
+
このパッケージは、複数の typed tools を公開します。違いは次の通りです。
|
|
54
|
+
|
|
55
|
+
| Package | Interface | 向いている用途 |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| `pi-agent-browser` | 1つの自由形式 `browser` command string | CLI に近い柔軟な操作 |
|
|
58
|
+
| `@53able/pi-agent-browser` | 複数の typed `agent_browser_*` tools | 構造化された Pi workflow、artifact 保存、session/state 管理 |
|
|
59
|
+
|
|
60
|
+
名前の衝突を避け、所有者を明確にするため、このパッケージは次の名前で公開しています。
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
@53able/pi-agent-browser
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## npm と Trusted Publishing
|
|
67
|
+
|
|
68
|
+
最初の npm release で package を作成しました。
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
@53able/pi-agent-browser@0.1.0
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
package が npm 上に存在したあと、GitHub Actions 用の Trusted Publisher を設定しました。
|
|
75
|
+
|
|
76
|
+
- npm package: `@53able/pi-agent-browser`
|
|
77
|
+
- GitHub owner: `53able`
|
|
78
|
+
- GitHub repository: `pi-agent-browser`
|
|
79
|
+
- Workflow filename: `publish.yml`
|
|
80
|
+
- Allowed action: `npm publish`
|
|
81
|
+
|
|
82
|
+
これにより、GitHub Actions は npm OIDC 経由で publish できます。publish 用の長期 `NPM_TOKEN` secret は不要です。
|
|
83
|
+
|
|
84
|
+
## README の整理
|
|
85
|
+
|
|
86
|
+
最初の npm README には、利用者向けの説明と、メンテナ向けの publish 設定が混ざっていました。パッケージページを見た人にとって、インストールする価値が分かりにくい状態でした。
|
|
87
|
+
|
|
88
|
+
README は、利用者のメリットが先に伝わるように整理しました。
|
|
89
|
+
|
|
90
|
+
- rendered page からの context extraction
|
|
91
|
+
- `@ref` による安定した操作対象
|
|
92
|
+
- screenshot と artifact の保存
|
|
93
|
+
- browser session の再利用
|
|
94
|
+
- browsing scope を絞る安全設定
|
|
95
|
+
- raw command string ではなく typed Pi tools として使えること
|
|
96
|
+
|
|
97
|
+
この利用者向け README は `0.1.1` として release しました。
|
|
98
|
+
|
|
99
|
+
## 現在のリリース workflow
|
|
100
|
+
|
|
101
|
+
現在の release は [`semantic-release`](https://github.com/semantic-release/semantic-release) で行います。
|
|
102
|
+
|
|
103
|
+
流れは次の通りです。
|
|
104
|
+
|
|
105
|
+
1. メンテナが `main` に commit を merge する。
|
|
106
|
+
2. GitHub Actions が `.github/workflows/publish.yml` を実行する。
|
|
107
|
+
3. workflow が `npx semantic-release@25` を実行する。
|
|
108
|
+
4. semantic-release が最新 release tag 以降の commit を読む。
|
|
109
|
+
5. release が必要なら、次の version を決め、npm publish、Git tag 作成、GitHub Release 作成を行う。
|
|
110
|
+
6. npm publish は Trusted Publishing / OIDC を使う。
|
|
111
|
+
|
|
112
|
+
workflow では、`actions/setup-node` の `registry-url` option をあえて使っていません。この option は `.npmrc` を生成し、semantic-release の npm authentication flow とぶつかることがあるためです。
|
|
113
|
+
|
|
114
|
+
## Commit message が release を決める
|
|
115
|
+
|
|
116
|
+
`main` に入れる commit は Conventional Commits に従います。
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
fix: handle empty snapshot output
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
patch release になります。
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
feat: add browser state export tool
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
minor release になります。
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
feat!: rename browser state arguments
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
major release になります。
|
|
135
|
+
|
|
136
|
+
`docs: ...`、`ci: ...`、`chore: ...` のような commit でも workflow は動きます。ただし通常は npm の新しい version は publish されません。
|
|
137
|
+
|
|
138
|
+
## release workflow の確認
|
|
139
|
+
|
|
140
|
+
semantic-release へ移行したあと、workflow は成功しました。semantic-release は、npm OIDC authentication が成功し、`ci:` commit では release 不要だと報告しました。
|
|
141
|
+
|
|
142
|
+
```text
|
|
143
|
+
OIDC token exchange with the npm registry succeeded
|
|
144
|
+
Found git tag v0.1.1 associated with version 0.1.1
|
|
145
|
+
There are no relevant changes, so no new version is released.
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
これで認証と release の配線は確認できています。次に `fix:`、`feat:`、breaking change の commit が `main` に入ると、対応する npm release が作られる想定です。
|
|
149
|
+
|
|
150
|
+
## インストール方法
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pi install npm:@53able/pi-agent-browser
|
|
154
|
+
```
|
package/index.ts
CHANGED
|
@@ -9,6 +9,32 @@ const DEFAULT_TIMEOUT_MS = 120_000;
|
|
|
9
9
|
const DEFAULT_MAX_PREVIEW_CHARS = 12_000;
|
|
10
10
|
const DEFAULT_ARTIFACT_DIR = "outputs/browser";
|
|
11
11
|
|
|
12
|
+
// Heuristics for content that signals a human-only checkpoint (2FA, CAPTCHA, identity
|
|
13
|
+
// verification). These are advisory only — read/snapshot stay non-destructive, so we
|
|
14
|
+
// annotate the result instead of blocking. Actual pausing happens in agent_browser_handoff.
|
|
15
|
+
const SENSITIVE_CONTENT_PATTERNS: { label: string; pattern: RegExp }[] = [
|
|
16
|
+
{
|
|
17
|
+
label: "2FA / one-time code",
|
|
18
|
+
pattern: /\b(two[-\s]?factor|2fa|one[-\s]?time (?:code|password)|otp|verification code)\b|認証コード|二段階認証|二要素認証|ワンタイムパスワード/i,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
label: "CAPTCHA",
|
|
22
|
+
pattern: /\b(captcha|recaptcha|hcaptcha|prove you.?re (?:not a robot|human)|are you a robot)\b|画像認証|パズル認証/i,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
label: "identity verification",
|
|
26
|
+
pattern: /\bverify your identity\b|\bidentity verification\b|\bconfirm it.?s you\b|本人確認|身分証明/i,
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
function scanForSensitiveContent(text: string): string[] {
|
|
31
|
+
const hits: string[] = [];
|
|
32
|
+
for (const { label, pattern } of SENSITIVE_CONTENT_PATTERNS) {
|
|
33
|
+
if (pattern.test(text)) hits.push(label);
|
|
34
|
+
}
|
|
35
|
+
return hits;
|
|
36
|
+
}
|
|
37
|
+
|
|
12
38
|
type JsonObject = Record<string, unknown>;
|
|
13
39
|
|
|
14
40
|
type RunResult = {
|
|
@@ -25,6 +51,7 @@ type RunResult = {
|
|
|
25
51
|
type ToolDetails = RunResult & {
|
|
26
52
|
artifactPaths?: string[];
|
|
27
53
|
parsedJson?: unknown;
|
|
54
|
+
securityFlags?: string[];
|
|
28
55
|
};
|
|
29
56
|
|
|
30
57
|
function optionalString(description: string) {
|
|
@@ -119,6 +146,25 @@ const StateSchema = Type.Object(commonSchema({
|
|
|
119
146
|
json: optionalBoolean("Forward --json for machine-readable state output."),
|
|
120
147
|
}));
|
|
121
148
|
|
|
149
|
+
const HandoffSchema = Type.Object(commonSchema({
|
|
150
|
+
category: Type.Union(
|
|
151
|
+
[Type.Literal("2fa"), Type.Literal("captcha"), Type.Literal("identity_verification"), Type.Literal("other")],
|
|
152
|
+
{ description: "Why AI control must pause: 2fa, captcha, identity_verification, or other." },
|
|
153
|
+
),
|
|
154
|
+
reason: Type.String({ description: "Concise description of what the human must do (e.g. 'Enter the SMS code sent to the user\\'s phone')." }),
|
|
155
|
+
screenshot: optionalBoolean("Capture a screenshot for the human before waiting. Defaults to true."),
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
const CommitSchema = Type.Object(commonSchema({
|
|
159
|
+
selector: Type.String({ description: "Element ref from snapshot, such as @e2, or a CSS selector, for the irreversible action (e.g. final pay/post/delete button)." }),
|
|
160
|
+
summary: Type.String({ description: "Plain-language description of what this click will do, e.g. '¥12,000 の決済を確定する' or 'この投稿を完全に削除する'." }),
|
|
161
|
+
riskCategory: Type.Union(
|
|
162
|
+
[Type.Literal("payment"), Type.Literal("delete"), Type.Literal("post_publish"), Type.Literal("send"), Type.Literal("other")],
|
|
163
|
+
{ description: "Category of irreversible action: payment, delete, post_publish, send, or other." },
|
|
164
|
+
),
|
|
165
|
+
screenshot: optionalBoolean("Capture a screenshot as evidence before asking for confirmation. Defaults to true."),
|
|
166
|
+
}));
|
|
167
|
+
|
|
122
168
|
const DoctorSchema = Type.Object({
|
|
123
169
|
fix: optionalBoolean("Run agent-browser doctor --fix."),
|
|
124
170
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Tool execution timeout in milliseconds." })),
|
|
@@ -244,10 +290,11 @@ function preview(text: string, maxChars = DEFAULT_MAX_PREVIEW_CHARS): { text: st
|
|
|
244
290
|
return { text: text.slice(0, maxChars) + `\n\n[truncated: ${text.length - maxChars} chars omitted]`, truncated: true };
|
|
245
291
|
}
|
|
246
292
|
|
|
247
|
-
function buildTextResult(result: RunResult, artifactPaths: string[] = [], parsedJson?: unknown): { content: { type: "text"; text: string }[]; details: ToolDetails } {
|
|
293
|
+
function buildTextResult(result: RunResult, artifactPaths: string[] = [], parsedJson?: unknown, notes: string[] = []): { content: { type: "text"; text: string }[]; details: ToolDetails } {
|
|
248
294
|
const stdoutPreview = preview(result.stdout.trim());
|
|
249
295
|
const stderrPreview = preview(result.stderr.trim(), 4000);
|
|
250
296
|
const lines = [
|
|
297
|
+
...notes,
|
|
251
298
|
result.ok ? "agent-browser command succeeded." : "agent-browser command failed.",
|
|
252
299
|
`Command: ${result.command} ${result.args.map((a) => JSON.stringify(a)).join(" ")}`,
|
|
253
300
|
`Exit: ${result.exitCode ?? "null"}${result.signal ? ` signal=${result.signal}` : ""}`,
|
|
@@ -272,6 +319,15 @@ function buildTextResult(result: RunResult, artifactPaths: string[] = [], parsed
|
|
|
272
319
|
};
|
|
273
320
|
}
|
|
274
321
|
|
|
322
|
+
function sensitiveContentNotes(hits: string[]): string[] {
|
|
323
|
+
if (hits.length === 0) return [];
|
|
324
|
+
return [
|
|
325
|
+
`⚠ Possible human-only checkpoint detected: ${hits.join(", ")}.`,
|
|
326
|
+
"Do not attempt to click/fill through this yourself — call agent_browser_handoff first so a human can complete it.",
|
|
327
|
+
"",
|
|
328
|
+
];
|
|
329
|
+
}
|
|
330
|
+
|
|
275
331
|
function parseJsonIfRequested(result: RunResult, requested: boolean): unknown | undefined {
|
|
276
332
|
if (!requested || !result.stdout.trim()) return undefined;
|
|
277
333
|
try {
|
|
@@ -341,7 +397,9 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
341
397
|
if (typeof params.outputPath === "string" && params.outputPath.trim()) {
|
|
342
398
|
artifacts.push(await saveArtifact(resolveOutputPath(ctx, params.outputPath, "read.txt"), result.stdout));
|
|
343
399
|
}
|
|
344
|
-
|
|
400
|
+
const hits = scanForSensitiveContent(result.stdout);
|
|
401
|
+
const { content, details } = buildTextResult(result, artifacts, parseJsonIfRequested(result, params.json === true), sensitiveContentNotes(hits));
|
|
402
|
+
return { content, details: { ...details, securityFlags: hits } };
|
|
345
403
|
},
|
|
346
404
|
});
|
|
347
405
|
|
|
@@ -363,7 +421,9 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
363
421
|
if (typeof params.outputPath === "string" && params.outputPath.trim()) {
|
|
364
422
|
artifacts.push(await saveArtifact(resolveOutputPath(ctx, params.outputPath, "snapshot.txt"), result.stdout));
|
|
365
423
|
}
|
|
366
|
-
|
|
424
|
+
const hits = scanForSensitiveContent(result.stdout);
|
|
425
|
+
const { content, details } = buildTextResult(result, artifacts, undefined, sensitiveContentNotes(hits));
|
|
426
|
+
return { content, details: { ...details, securityFlags: hits } };
|
|
367
427
|
},
|
|
368
428
|
});
|
|
369
429
|
|
|
@@ -506,6 +566,153 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
|
|
|
506
566
|
},
|
|
507
567
|
});
|
|
508
568
|
|
|
569
|
+
pi.registerTool({
|
|
570
|
+
name: "agent_browser_handoff",
|
|
571
|
+
label: "Agent Browser Handoff",
|
|
572
|
+
description: "Pause AI control and hand the browser to a human for steps AI cannot or must not perform: 2FA codes, CAPTCHA, identity verification. Blocks until the human confirms completion or aborts.",
|
|
573
|
+
promptSnippet: "Call agent_browser_handoff and stop acting on the page whenever a 2FA/CAPTCHA/identity-verification checkpoint blocks progress.",
|
|
574
|
+
promptGuidelines: [
|
|
575
|
+
"Never attempt to solve CAPTCHAs, guess 2FA/OTP codes, or fabricate identity-verification data yourself.",
|
|
576
|
+
"Call this tool as soon as such a checkpoint appears; do not keep clicking or filling around it.",
|
|
577
|
+
"If the tool reports no interactive UI is available, stop the task and tell the operator to rerun it interactively.",
|
|
578
|
+
],
|
|
579
|
+
parameters: HandoffSchema,
|
|
580
|
+
async execute(_id, rawParams, signal, onUpdate, ctx) {
|
|
581
|
+
const params = rawParams as JsonObject;
|
|
582
|
+
const category = String(params.category);
|
|
583
|
+
const reason = String(params.reason);
|
|
584
|
+
const artifacts: string[] = [];
|
|
585
|
+
|
|
586
|
+
if (params.screenshot !== false) {
|
|
587
|
+
const path = resolveOutputPath(ctx, undefined, "handoff.png");
|
|
588
|
+
await mkdir(resolve(path, ".."), { recursive: true });
|
|
589
|
+
const shotArgs: string[] = [];
|
|
590
|
+
pushCommonArgs(shotArgs, params);
|
|
591
|
+
shotArgs.push("screenshot", path);
|
|
592
|
+
await runAgentBrowser(shotArgs, { signal, timeoutMs: params.timeoutMs as number | undefined, cwd: ctx.cwd, onUpdate });
|
|
593
|
+
if (existsSync(path)) artifacts.push(path);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (!ctx.hasUI) {
|
|
597
|
+
return {
|
|
598
|
+
content: [{
|
|
599
|
+
type: "text",
|
|
600
|
+
text: [
|
|
601
|
+
`Human handoff required (${category}) but no interactive UI is available in this run mode.`,
|
|
602
|
+
`Reason: ${reason}`,
|
|
603
|
+
artifacts.length ? `Evidence: ${artifacts.join(", ")}` : undefined,
|
|
604
|
+
"Stop this task now and ask the operator to rerun it interactively so the handoff can be completed.",
|
|
605
|
+
].filter(Boolean).join("\n"),
|
|
606
|
+
}],
|
|
607
|
+
details: { ok: false, command: "handoff", args: [category], stdout: "", stderr: "no interactive UI", exitCode: null, signal: null, durationMs: 0, artifactPaths: artifacts },
|
|
608
|
+
terminate: true,
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
ctx.ui.notify(`Human handoff needed (${category}): ${reason}`, "warning");
|
|
613
|
+
ctx.ui.setStatus("agent-browser-handoff", `⏸ Waiting for human: ${reason}`);
|
|
614
|
+
let choice: string | undefined;
|
|
615
|
+
try {
|
|
616
|
+
choice = await ctx.ui.select(
|
|
617
|
+
`Human handoff required (${category}): ${reason}`,
|
|
618
|
+
["Resume - I completed this manually", "Abort this task"],
|
|
619
|
+
);
|
|
620
|
+
} finally {
|
|
621
|
+
ctx.ui.setStatus("agent-browser-handoff", undefined);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const resumed = choice === "Resume - I completed this manually";
|
|
625
|
+
return {
|
|
626
|
+
content: [{
|
|
627
|
+
type: "text",
|
|
628
|
+
text: [
|
|
629
|
+
resumed
|
|
630
|
+
? "Human confirmed the handoff step is complete. Take a fresh agent_browser_snapshot before continuing."
|
|
631
|
+
: "Human aborted the task during handoff. Stop and report back; do not retry automatically.",
|
|
632
|
+
artifacts.length ? `Evidence captured: ${artifacts.join(", ")}` : undefined,
|
|
633
|
+
].filter(Boolean).join("\n"),
|
|
634
|
+
}],
|
|
635
|
+
details: { ok: resumed, command: "handoff", args: [category], stdout: choice ?? "", stderr: "", exitCode: resumed ? 0 : 1, signal: null, durationMs: 0, artifactPaths: artifacts },
|
|
636
|
+
terminate: true,
|
|
637
|
+
};
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
pi.registerTool({
|
|
642
|
+
name: "agent_browser_commit",
|
|
643
|
+
label: "Agent Browser Commit",
|
|
644
|
+
description: "Gate for irreversible actions (payment, delete, publish/post, send). Captures evidence, requires explicit human confirmation, then performs the click. Never bypassed for unattended runs.",
|
|
645
|
+
promptSnippet: "Use agent_browser_commit instead of agent_browser_click for the final step of any irreversible action (pay, delete, publish, send).",
|
|
646
|
+
promptGuidelines: [
|
|
647
|
+
"Route the final confirming click of a payment, deletion, publish/post, or send action through this tool, never agent_browser_click.",
|
|
648
|
+
"Write summary in plain language describing exactly what will happen, including amounts or targets when known.",
|
|
649
|
+
"If this tool reports the human declined or no UI was available, stop and report back — do not retry the click through agent_browser_click.",
|
|
650
|
+
],
|
|
651
|
+
parameters: CommitSchema,
|
|
652
|
+
async execute(_id, rawParams, signal, onUpdate, ctx) {
|
|
653
|
+
const params = rawParams as JsonObject;
|
|
654
|
+
const selector = String(params.selector);
|
|
655
|
+
const summary = String(params.summary);
|
|
656
|
+
const riskCategory = String(params.riskCategory);
|
|
657
|
+
const artifacts: string[] = [];
|
|
658
|
+
|
|
659
|
+
if (params.screenshot !== false) {
|
|
660
|
+
const path = resolveOutputPath(ctx, undefined, "commit-evidence.png");
|
|
661
|
+
await mkdir(resolve(path, ".."), { recursive: true });
|
|
662
|
+
const shotArgs: string[] = [];
|
|
663
|
+
pushCommonArgs(shotArgs, params);
|
|
664
|
+
shotArgs.push("screenshot", path);
|
|
665
|
+
await runAgentBrowser(shotArgs, { signal, timeoutMs: params.timeoutMs as number | undefined, cwd: ctx.cwd, onUpdate });
|
|
666
|
+
if (existsSync(path)) artifacts.push(path);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (!ctx.hasUI) {
|
|
670
|
+
return {
|
|
671
|
+
content: [{
|
|
672
|
+
type: "text",
|
|
673
|
+
text: [
|
|
674
|
+
`Irreversible action blocked (${riskCategory}): no interactive UI available to confirm with a human.`,
|
|
675
|
+
`Action: ${summary}`,
|
|
676
|
+
artifacts.length ? `Evidence: ${artifacts.join(", ")}` : undefined,
|
|
677
|
+
"The click was NOT performed. Stop this task and ask the operator to rerun it interactively.",
|
|
678
|
+
].filter(Boolean).join("\n"),
|
|
679
|
+
}],
|
|
680
|
+
details: { ok: false, command: "commit", args: [riskCategory, selector], stdout: "", stderr: "no interactive UI", exitCode: null, signal: null, durationMs: 0, artifactPaths: artifacts },
|
|
681
|
+
terminate: true,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
ctx.ui.notify(`Confirmation needed for irreversible action (${riskCategory}): ${summary}`, "warning");
|
|
686
|
+
const approved = await ctx.ui.confirm(
|
|
687
|
+
`Confirm irreversible action (${riskCategory})`,
|
|
688
|
+
`${summary}\n\nTarget: ${selector}\nThis cannot be undone. Proceed?`,
|
|
689
|
+
);
|
|
690
|
+
|
|
691
|
+
if (!approved) {
|
|
692
|
+
return {
|
|
693
|
+
content: [{
|
|
694
|
+
type: "text",
|
|
695
|
+
text: [
|
|
696
|
+
"Human declined the irreversible action. The click was NOT performed.",
|
|
697
|
+
`Action: ${summary}`,
|
|
698
|
+
artifacts.length ? `Evidence captured before decline: ${artifacts.join(", ")}` : undefined,
|
|
699
|
+
"Stop and report back; do not retry this action automatically.",
|
|
700
|
+
].filter(Boolean).join("\n"),
|
|
701
|
+
}],
|
|
702
|
+
details: { ok: false, command: "commit", args: [riskCategory, selector], stdout: "declined", stderr: "", exitCode: 1, signal: null, durationMs: 0, artifactPaths: artifacts },
|
|
703
|
+
terminate: true,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const args: string[] = [];
|
|
708
|
+
pushCommonArgs(args, params);
|
|
709
|
+
args.push("click", selector);
|
|
710
|
+
pushExtraArgs(args, params);
|
|
711
|
+
const result = await runAgentBrowser(args, { signal, timeoutMs: params.timeoutMs as number | undefined, cwd: ctx.cwd, onUpdate });
|
|
712
|
+
return buildTextResult(result, artifacts);
|
|
713
|
+
},
|
|
714
|
+
});
|
|
715
|
+
|
|
509
716
|
pi.registerTool({
|
|
510
717
|
name: "agent_browser_doctor",
|
|
511
718
|
label: "Agent Browser Doctor",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@53able/pi-agent-browser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Typed Pi tools for browser automation, rendered-page context extraction, screenshots, and saved session state via agent-browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
"files": [
|
|
25
25
|
"index.ts",
|
|
26
26
|
"skills",
|
|
27
|
+
"docs",
|
|
27
28
|
"README.md",
|
|
29
|
+
"README.ja-JP.md",
|
|
28
30
|
"LICENSE",
|
|
29
31
|
"CHANGELOG.md",
|
|
30
32
|
"package.json"
|
|
@@ -27,6 +27,15 @@ Use the `agent_browser_*` tools instead of raw `bash` commands when browser stat
|
|
|
27
27
|
- To keep login state automatically, open with a stable `session` and `restore: true`.
|
|
28
28
|
- Before clearing state, list saved states with `agent_browser_state` unless the user explicitly asks for broad cleanup.
|
|
29
29
|
|
|
30
|
+
## Human handoff and commit gates
|
|
31
|
+
|
|
32
|
+
Two tools exist so the agent stops and hands control back to a human instead of guessing through security or irreversible steps:
|
|
33
|
+
|
|
34
|
+
- `agent_browser_handoff` — call this the moment 2FA/OTP entry, a CAPTCHA, or identity verification blocks progress. It captures a screenshot, then blocks (via a real UI dialog) until a human resumes or aborts. Never try to solve CAPTCHAs, guess codes, or fabricate identity data yourself.
|
|
35
|
+
- `agent_browser_commit` — use this instead of `agent_browser_click` for the final confirming click of any irreversible action: payment, deletion, publish/post, send. It captures evidence, requires explicit human confirmation, and only clicks if approved. If no interactive UI is available, it refuses and does not click.
|
|
36
|
+
|
|
37
|
+
`agent_browser_read` and `agent_browser_snapshot` also run a lightweight heuristic scan for 2FA/CAPTCHA/identity-verification keywords and prepend a warning to their output when detected — treat that warning as a signal to call `agent_browser_handoff` next, not as a blocker by itself.
|
|
38
|
+
|
|
30
39
|
## Common examples
|
|
31
40
|
|
|
32
41
|
Read a public page:
|