@53able/pi-agent-browser 0.1.1 → 0.3.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 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
@@ -0,0 +1,196 @@
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
+ `agent_browser_open`は、各セッションが`headed: true`で起動されたかどうかも記録します。`agent_browser_handoff`が、headedで開かれていないセッションに対して呼ばれた場合、人間への警告に加えて、可視ウィンドウを開き直すための`agent_browser_open`の引数(セッション名、`headed: true`、`restore: true`、最後に開いていたURL)をそのまま提示します — どのコマンドを打ち直せばよいか推測する必要はありません。ログインや2FA、CAPTCHA、本人確認に差し掛かる可能性があるタスクでは、最初から`headed: true`で開くことを推奨します。一部の確認チャレンジは使い切り・期限付きのため、途中で開き直すとログインからやり直しになることがあります。
171
+
172
+ ## Troubleshooting
173
+
174
+ ```bash
175
+ agent-browser doctor
176
+ ```
177
+
178
+ または Pi に `agent_browser_doctor` を実行させてください。
179
+
180
+ よくある修正:
181
+
182
+ ```bash
183
+ npm install -g agent-browser
184
+ agent-browser install
185
+ ```
186
+
187
+ ## Project history
188
+
189
+ package 名の判断、npm Trusted Publishing、semantic-release workflow については、言語別の project history を参照してください。
190
+
191
+ - [English](docs/project-history.en.md)
192
+ - [日本語](docs/project-history.ja-JP.md)
193
+
194
+ ## License
195
+
196
+ 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 をブラウザ経由で読み、outputs/browser/example.md に保存して
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,17 @@ 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
+
170
+ `agent_browser_open` also remembers whether each named session was launched with `headed: true`. If `agent_browser_handoff` is called for a session that was not opened headed, it automatically warns the human and includes the exact `agent_browser_open` arguments (session name, `headed: true`, `restore: true`, and the last known URL) needed to reopen a visible window — no more guessing which command to rerun. Prefer `headed: true` up front for any task that might hit a login, 2FA, CAPTCHA, or identity-verification step, since some challenges are single-use and reopening mid-flow may require restarting the login.
171
+
157
172
  ## Troubleshooting
158
173
 
159
174
  Run:
@@ -171,6 +186,22 @@ npm install -g agent-browser
171
186
  agent-browser install
172
187
  ```
173
188
 
189
+ ## Project history
190
+
191
+ 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.
192
+
193
+ ## Release automation
194
+
195
+ 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.
196
+
197
+ Commit message examples:
198
+
199
+ - `fix: handle empty snapshot output` → patch release
200
+ - `feat: add browser state export tool` → minor release
201
+ - `feat!: rename tool arguments` or `BREAKING CHANGE:` → major release
202
+
203
+ The npm Trusted Publisher should continue to point at `.github/workflows/publish.yml`. No long-lived `NPM_TOKEN` is required.
204
+
174
205
  ## License
175
206
 
176
207
  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
+ ```
@@ -0,0 +1,6 @@
1
+ # Project history
2
+
3
+ Choose a language:
4
+
5
+ - [English](project-history.en.md)
6
+ - [日本語](project-history.ja-JP.md)
package/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { ExtensionAPI, AgentToolUpdateCallback, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { Type } from "typebox";
3
- import { access, mkdir, writeFile } from "node:fs/promises";
3
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
4
4
  import { constants, existsSync } from "node:fs";
5
5
  import { join, resolve, basename } from "node:path";
6
6
  import { spawn } from "node:child_process";
@@ -9,6 +9,77 @@ 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
+
38
+ // Tracks whether each agent-browser session was last opened headed, so
39
+ // agent_browser_handoff / agent_browser_commit can tell a human definitively
40
+ // whether a visible browser window exists for them to act in, instead of guessing.
41
+ const SESSION_STATE_DIR = ".pi-agent-browser";
42
+ const SESSION_STATE_FILE = "sessions.json";
43
+ const DEFAULT_SESSION_KEY = "__default__";
44
+
45
+ type SessionState = { headed: boolean; lastUrl?: string; updatedAt: string };
46
+
47
+ function sessionKey(params: JsonObject): string {
48
+ return typeof params.session === "string" && params.session.trim() ? params.session.trim() : DEFAULT_SESSION_KEY;
49
+ }
50
+
51
+ async function readSessionStates(cwd: string): Promise<Record<string, SessionState>> {
52
+ try {
53
+ const raw = await readFile(join(cwd, SESSION_STATE_DIR, SESSION_STATE_FILE), "utf8");
54
+ return JSON.parse(raw) as Record<string, SessionState>;
55
+ } catch {
56
+ return {};
57
+ }
58
+ }
59
+
60
+ async function writeSessionState(cwd: string, session: string, patch: Partial<Omit<SessionState, "updatedAt">>): Promise<void> {
61
+ const dir = join(cwd, SESSION_STATE_DIR);
62
+ await mkdir(dir, { recursive: true });
63
+ const states = await readSessionStates(cwd);
64
+ const previous = states[session];
65
+ states[session] = {
66
+ headed: patch.headed ?? previous?.headed ?? false,
67
+ lastUrl: patch.lastUrl ?? previous?.lastUrl,
68
+ updatedAt: new Date().toISOString(),
69
+ };
70
+ await writeFile(join(dir, SESSION_STATE_FILE), JSON.stringify(states, null, 2), "utf8");
71
+ }
72
+
73
+ function reopenHeadedGuidance(session: string, lastUrl: string | undefined): string[] {
74
+ const sessionArg = session === DEFAULT_SESSION_KEY ? {} : { session };
75
+ const reopenArgs = JSON.stringify({ ...sessionArg, headed: true, restore: true, ...(lastUrl ? { url: lastUrl } : {}) });
76
+ return [
77
+ `⚠ このセッション(${session === DEFAULT_SESSION_KEY ? "既定セッション" : session})はheadless(ウィンドウ非表示)で開かれています。`,
78
+ `人間が直接操作できるようにするには、agent_browser_open を次の引数で呼び直してください: ${reopenArgs}`,
79
+ "同じセッション名なのでcookie等は復元されますが、2FA/CAPTCHAなど使い切り・期限付きのチャレンジは、再度最初からやり直しになる場合があります。",
80
+ ];
81
+ }
82
+
12
83
  type JsonObject = Record<string, unknown>;
13
84
 
14
85
  type RunResult = {
@@ -25,6 +96,7 @@ type RunResult = {
25
96
  type ToolDetails = RunResult & {
26
97
  artifactPaths?: string[];
27
98
  parsedJson?: unknown;
99
+ securityFlags?: string[];
28
100
  };
29
101
 
30
102
  function optionalString(description: string) {
@@ -119,6 +191,25 @@ const StateSchema = Type.Object(commonSchema({
119
191
  json: optionalBoolean("Forward --json for machine-readable state output."),
120
192
  }));
121
193
 
194
+ const HandoffSchema = Type.Object(commonSchema({
195
+ category: Type.Union(
196
+ [Type.Literal("2fa"), Type.Literal("captcha"), Type.Literal("identity_verification"), Type.Literal("other")],
197
+ { description: "Why AI control must pause: 2fa, captcha, identity_verification, or other." },
198
+ ),
199
+ reason: Type.String({ description: "Concise description of what the human must do (e.g. 'Enter the SMS code sent to the user\\'s phone')." }),
200
+ screenshot: optionalBoolean("Capture a screenshot for the human before waiting. Defaults to true."),
201
+ }));
202
+
203
+ const CommitSchema = Type.Object(commonSchema({
204
+ 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)." }),
205
+ summary: Type.String({ description: "Plain-language description of what this click will do, e.g. '¥12,000 の決済を確定する' or 'この投稿を完全に削除する'." }),
206
+ riskCategory: Type.Union(
207
+ [Type.Literal("payment"), Type.Literal("delete"), Type.Literal("post_publish"), Type.Literal("send"), Type.Literal("other")],
208
+ { description: "Category of irreversible action: payment, delete, post_publish, send, or other." },
209
+ ),
210
+ screenshot: optionalBoolean("Capture a screenshot as evidence before asking for confirmation. Defaults to true."),
211
+ }));
212
+
122
213
  const DoctorSchema = Type.Object({
123
214
  fix: optionalBoolean("Run agent-browser doctor --fix."),
124
215
  timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Tool execution timeout in milliseconds." })),
@@ -244,10 +335,11 @@ function preview(text: string, maxChars = DEFAULT_MAX_PREVIEW_CHARS): { text: st
244
335
  return { text: text.slice(0, maxChars) + `\n\n[truncated: ${text.length - maxChars} chars omitted]`, truncated: true };
245
336
  }
246
337
 
247
- function buildTextResult(result: RunResult, artifactPaths: string[] = [], parsedJson?: unknown): { content: { type: "text"; text: string }[]; details: ToolDetails } {
338
+ function buildTextResult(result: RunResult, artifactPaths: string[] = [], parsedJson?: unknown, notes: string[] = []): { content: { type: "text"; text: string }[]; details: ToolDetails } {
248
339
  const stdoutPreview = preview(result.stdout.trim());
249
340
  const stderrPreview = preview(result.stderr.trim(), 4000);
250
341
  const lines = [
342
+ ...notes,
251
343
  result.ok ? "agent-browser command succeeded." : "agent-browser command failed.",
252
344
  `Command: ${result.command} ${result.args.map((a) => JSON.stringify(a)).join(" ")}`,
253
345
  `Exit: ${result.exitCode ?? "null"}${result.signal ? ` signal=${result.signal}` : ""}`,
@@ -272,6 +364,15 @@ function buildTextResult(result: RunResult, artifactPaths: string[] = [], parsed
272
364
  };
273
365
  }
274
366
 
367
+ function sensitiveContentNotes(hits: string[]): string[] {
368
+ if (hits.length === 0) return [];
369
+ return [
370
+ `⚠ Possible human-only checkpoint detected: ${hits.join(", ")}.`,
371
+ "Do not attempt to click/fill through this yourself — call agent_browser_handoff first so a human can complete it.",
372
+ "",
373
+ ];
374
+ }
375
+
275
376
  function parseJsonIfRequested(result: RunResult, requested: boolean): unknown | undefined {
276
377
  if (!requested || !result.stdout.trim()) return undefined;
277
378
  try {
@@ -290,6 +391,7 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
290
391
  promptGuidelines: [
291
392
  "Use agent_browser_snapshot after opening or navigating before clicking by @ref.",
292
393
  "Pass allowedDomains for untrusted or task-scoped browsing when possible.",
394
+ "Prefer headed: true whenever the task might hit a login, 2FA, CAPTCHA, or identity-verification step — a human can only intervene through agent_browser_handoff if a visible window already exists.",
293
395
  ],
294
396
  parameters: OpenSchema,
295
397
  async execute(_id, rawParams, signal, onUpdate, ctx) {
@@ -309,6 +411,12 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
309
411
  if (typeof params.url === "string" && params.url.trim()) args.push(params.url.trim());
310
412
  pushExtraArgs(args, params);
311
413
  const result = await runAgentBrowser(args, { signal, timeoutMs: params.timeoutMs as number | undefined, cwd: ctx.cwd, onUpdate });
414
+ if (result.ok) {
415
+ await writeSessionState(ctx.cwd, sessionKey(params), {
416
+ headed: params.headed === true,
417
+ lastUrl: typeof params.url === "string" && params.url.trim() ? params.url.trim() : undefined,
418
+ });
419
+ }
312
420
  return buildTextResult(result);
313
421
  },
314
422
  });
@@ -341,7 +449,9 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
341
449
  if (typeof params.outputPath === "string" && params.outputPath.trim()) {
342
450
  artifacts.push(await saveArtifact(resolveOutputPath(ctx, params.outputPath, "read.txt"), result.stdout));
343
451
  }
344
- return buildTextResult(result, artifacts, parseJsonIfRequested(result, params.json === true));
452
+ const hits = scanForSensitiveContent(result.stdout);
453
+ const { content, details } = buildTextResult(result, artifacts, parseJsonIfRequested(result, params.json === true), sensitiveContentNotes(hits));
454
+ return { content, details: { ...details, securityFlags: hits } };
345
455
  },
346
456
  });
347
457
 
@@ -363,7 +473,9 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
363
473
  if (typeof params.outputPath === "string" && params.outputPath.trim()) {
364
474
  artifacts.push(await saveArtifact(resolveOutputPath(ctx, params.outputPath, "snapshot.txt"), result.stdout));
365
475
  }
366
- return buildTextResult(result, artifacts);
476
+ const hits = scanForSensitiveContent(result.stdout);
477
+ const { content, details } = buildTextResult(result, artifacts, undefined, sensitiveContentNotes(hits));
478
+ return { content, details: { ...details, securityFlags: hits } };
367
479
  },
368
480
  });
369
481
 
@@ -506,6 +618,159 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
506
618
  },
507
619
  });
508
620
 
621
+ pi.registerTool({
622
+ name: "agent_browser_handoff",
623
+ label: "Agent Browser Handoff",
624
+ 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.",
625
+ promptSnippet: "Call agent_browser_handoff and stop acting on the page whenever a 2FA/CAPTCHA/identity-verification checkpoint blocks progress.",
626
+ promptGuidelines: [
627
+ "Never attempt to solve CAPTCHAs, guess 2FA/OTP codes, or fabricate identity-verification data yourself.",
628
+ "Call this tool as soon as such a checkpoint appears; do not keep clicking or filling around it.",
629
+ "If the tool reports no interactive UI is available, stop the task and tell the operator to rerun it interactively.",
630
+ ],
631
+ parameters: HandoffSchema,
632
+ async execute(_id, rawParams, signal, onUpdate, ctx) {
633
+ const params = rawParams as JsonObject;
634
+ const category = String(params.category);
635
+ const reason = String(params.reason);
636
+ const artifacts: string[] = [];
637
+ const session = sessionKey(params);
638
+ const sessionState = (await readSessionStates(ctx.cwd))[session];
639
+ const headlessNotes = sessionState?.headed === true ? [] : reopenHeadedGuidance(session, sessionState?.lastUrl);
640
+
641
+ if (params.screenshot !== false) {
642
+ const path = resolveOutputPath(ctx, undefined, "handoff.png");
643
+ await mkdir(resolve(path, ".."), { recursive: true });
644
+ const shotArgs: string[] = [];
645
+ pushCommonArgs(shotArgs, params);
646
+ shotArgs.push("screenshot", path);
647
+ await runAgentBrowser(shotArgs, { signal, timeoutMs: params.timeoutMs as number | undefined, cwd: ctx.cwd, onUpdate });
648
+ if (existsSync(path)) artifacts.push(path);
649
+ }
650
+
651
+ if (!ctx.hasUI) {
652
+ return {
653
+ content: [{
654
+ type: "text",
655
+ text: [
656
+ `Human handoff required (${category}) but no interactive UI is available in this run mode.`,
657
+ `Reason: ${reason}`,
658
+ artifacts.length ? `Evidence: ${artifacts.join(", ")}` : undefined,
659
+ ...headlessNotes,
660
+ "Stop this task now and ask the operator to rerun it interactively so the handoff can be completed.",
661
+ ].filter(Boolean).join("\n"),
662
+ }],
663
+ details: { ok: false, command: "handoff", args: [category], stdout: "", stderr: "no interactive UI", exitCode: null, signal: null, durationMs: 0, artifactPaths: artifacts },
664
+ terminate: true,
665
+ };
666
+ }
667
+
668
+ if (headlessNotes.length) ctx.ui.notify(headlessNotes.join(" "), "warning");
669
+ ctx.ui.notify(`Human handoff needed (${category}): ${reason}`, "warning");
670
+ ctx.ui.setStatus("agent-browser-handoff", headlessNotes.length ? `⏸ ブラウザ非表示 — ${reason}` : `⏸ Waiting for human: ${reason}`);
671
+ let choice: string | undefined;
672
+ try {
673
+ choice = await ctx.ui.select(
674
+ `Human handoff required (${category}): ${reason}`,
675
+ ["Resume - I completed this manually", "Abort this task"],
676
+ );
677
+ } finally {
678
+ ctx.ui.setStatus("agent-browser-handoff", undefined);
679
+ }
680
+
681
+ const resumed = choice === "Resume - I completed this manually";
682
+ return {
683
+ content: [{
684
+ type: "text",
685
+ text: [
686
+ ...headlessNotes,
687
+ resumed
688
+ ? "Human confirmed the handoff step is complete. Take a fresh agent_browser_snapshot before continuing."
689
+ : "Human aborted the task during handoff. Stop and report back; do not retry automatically.",
690
+ artifacts.length ? `Evidence captured: ${artifacts.join(", ")}` : undefined,
691
+ ].filter(Boolean).join("\n"),
692
+ }],
693
+ details: { ok: resumed, command: "handoff", args: [category], stdout: choice ?? "", stderr: "", exitCode: resumed ? 0 : 1, signal: null, durationMs: 0, artifactPaths: artifacts },
694
+ terminate: true,
695
+ };
696
+ },
697
+ });
698
+
699
+ pi.registerTool({
700
+ name: "agent_browser_commit",
701
+ label: "Agent Browser Commit",
702
+ 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.",
703
+ promptSnippet: "Use agent_browser_commit instead of agent_browser_click for the final step of any irreversible action (pay, delete, publish, send).",
704
+ promptGuidelines: [
705
+ "Route the final confirming click of a payment, deletion, publish/post, or send action through this tool, never agent_browser_click.",
706
+ "Write summary in plain language describing exactly what will happen, including amounts or targets when known.",
707
+ "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.",
708
+ ],
709
+ parameters: CommitSchema,
710
+ async execute(_id, rawParams, signal, onUpdate, ctx) {
711
+ const params = rawParams as JsonObject;
712
+ const selector = String(params.selector);
713
+ const summary = String(params.summary);
714
+ const riskCategory = String(params.riskCategory);
715
+ const artifacts: string[] = [];
716
+
717
+ if (params.screenshot !== false) {
718
+ const path = resolveOutputPath(ctx, undefined, "commit-evidence.png");
719
+ await mkdir(resolve(path, ".."), { recursive: true });
720
+ const shotArgs: string[] = [];
721
+ pushCommonArgs(shotArgs, params);
722
+ shotArgs.push("screenshot", path);
723
+ await runAgentBrowser(shotArgs, { signal, timeoutMs: params.timeoutMs as number | undefined, cwd: ctx.cwd, onUpdate });
724
+ if (existsSync(path)) artifacts.push(path);
725
+ }
726
+
727
+ if (!ctx.hasUI) {
728
+ return {
729
+ content: [{
730
+ type: "text",
731
+ text: [
732
+ `Irreversible action blocked (${riskCategory}): no interactive UI available to confirm with a human.`,
733
+ `Action: ${summary}`,
734
+ artifacts.length ? `Evidence: ${artifacts.join(", ")}` : undefined,
735
+ "The click was NOT performed. Stop this task and ask the operator to rerun it interactively.",
736
+ ].filter(Boolean).join("\n"),
737
+ }],
738
+ details: { ok: false, command: "commit", args: [riskCategory, selector], stdout: "", stderr: "no interactive UI", exitCode: null, signal: null, durationMs: 0, artifactPaths: artifacts },
739
+ terminate: true,
740
+ };
741
+ }
742
+
743
+ ctx.ui.notify(`Confirmation needed for irreversible action (${riskCategory}): ${summary}`, "warning");
744
+ const approved = await ctx.ui.confirm(
745
+ `Confirm irreversible action (${riskCategory})`,
746
+ `${summary}\n\nTarget: ${selector}\nThis cannot be undone. Proceed?`,
747
+ );
748
+
749
+ if (!approved) {
750
+ return {
751
+ content: [{
752
+ type: "text",
753
+ text: [
754
+ "Human declined the irreversible action. The click was NOT performed.",
755
+ `Action: ${summary}`,
756
+ artifacts.length ? `Evidence captured before decline: ${artifacts.join(", ")}` : undefined,
757
+ "Stop and report back; do not retry this action automatically.",
758
+ ].filter(Boolean).join("\n"),
759
+ }],
760
+ details: { ok: false, command: "commit", args: [riskCategory, selector], stdout: "declined", stderr: "", exitCode: 1, signal: null, durationMs: 0, artifactPaths: artifacts },
761
+ terminate: true,
762
+ };
763
+ }
764
+
765
+ const args: string[] = [];
766
+ pushCommonArgs(args, params);
767
+ args.push("click", selector);
768
+ pushExtraArgs(args, params);
769
+ const result = await runAgentBrowser(args, { signal, timeoutMs: params.timeoutMs as number | undefined, cwd: ctx.cwd, onUpdate });
770
+ return buildTextResult(result, artifacts);
771
+ },
772
+ });
773
+
509
774
  pi.registerTool({
510
775
  name: "agent_browser_doctor",
511
776
  label: "Agent Browser Doctor",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@53able/pi-agent-browser",
3
- "version": "0.1.1",
3
+ "version": "0.3.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,19 @@ 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
+
39
+ ### If the human can't find the browser window
40
+
41
+ `agent_browser_open` records whether each named session was launched with `headed: true`. When `agent_browser_handoff` runs for a session that was not opened headed, it automatically prepends a warning naming the session and the exact `agent_browser_open` arguments (including the last known URL) needed to reopen it visibly, instead of leaving the human to guess. Prefer `headed: true` from the start for any task that might hit a login, 2FA, CAPTCHA, or identity-verification step — a human can only intervene through `agent_browser_handoff` if a visible window already exists. Note that some verification challenges are single-use or session-bound, so reopening may require restarting the login flow rather than resuming mid-challenge.
42
+
30
43
  ## Common examples
31
44
 
32
45
  Read a public page: