pi-lean-portal 0.3.3 → 0.5.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.
Files changed (39) hide show
  1. package/README.md +159 -519
  2. package/backends/playwright-base/playwright-plugin.ts +40 -107
  3. package/backends/python-adapter.ts +40 -66
  4. package/backends/python-base/pi_browser_bridge/accessibility.py +30 -15
  5. package/backends/python-base/pi_browser_bridge/browser_data.py +34 -42
  6. package/backends/python-base/pi_browser_bridge/playwright_base.py +25 -30
  7. package/browser-cookies.ts +5 -0
  8. package/browser-profile.ts +7 -13
  9. package/browser-toggle.ts +29 -163
  10. package/core/fetch-backend.ts +20 -69
  11. package/core/guides.ts +134 -23
  12. package/core/plugin-api.ts +14 -26
  13. package/core/plugin-config.ts +38 -12
  14. package/core/plugin-registry.ts +7 -2
  15. package/core/router.ts +132 -154
  16. package/core/shared/accessibility-tree.ts +7 -2
  17. package/core/shared/bot-detection.ts +7 -7
  18. package/core/shared/browser-events.ts +10 -14
  19. package/core/shared/dom-extractor.ts +4 -18
  20. package/core/shared/nav-settle.ts +6 -6
  21. package/core/shared/session-manager.ts +8 -34
  22. package/core/shared/settings-reader.ts +5 -3
  23. package/core/shared/snapshot-cache.ts +27 -70
  24. package/core/shared/storage-state.ts +13 -12
  25. package/core/shared/temp-files.ts +73 -0
  26. package/index.ts +29 -48
  27. package/package.json +4 -6
  28. package/tools/browser-back.ts +8 -3
  29. package/tools/browser-console.ts +3 -8
  30. package/tools/browser-inspect.ts +15 -14
  31. package/tools/browser-navigate.ts +41 -24
  32. package/tools/browser-press.ts +7 -2
  33. package/tools/browser-scroll.ts +8 -3
  34. package/tools/browser-snapshot.ts +10 -8
  35. package/tools/index.ts +4 -1
  36. package/tools/utils.ts +17 -3
  37. package/tools/web-fetch.ts +12 -8
  38. package/core/shared/ship-manifest.ts +0 -151
  39. package/ship-manifest.test.ts +0 -17
package/README.md CHANGED
@@ -1,9 +1,7 @@
1
1
  # pi-lean-portal User Guide
2
2
 
3
- > **pi-lean-portal** gives the Pi coding agent interactive web browsing —
4
- > Playwright Chromium/Firefox, accessibility-tree snapshots with `@e` element
5
- > refs, persistent profiles, cookies, and navigation guides that resurface by
6
- > domain. A `/web` toggle removes the tools from the agent's context when
3
+ > **pi-lean-portal** gives the Pi coding agent interactive web browsing via Playwright Chromium/Firefox, accessibility-tree snapshots with `@e` element
4
+ > refs, persistent profiles, cookies, and domain-aware navigation guides. A `/web` toggle removes the tools from the agent's context when
7
5
  > switched off, so web browsing doesn't consume tokens on sessions that aren't
8
6
  > doing web work. If a site blocks the shipped browsers, drop in your own
9
7
  > backend (e.g. [Camoufox](https://github.com/daijro/camoufox)).
@@ -14,23 +12,6 @@
14
12
 
15
13
  ---
16
14
 
17
- ## Table of Contents
18
-
19
- 1. [Quick Start](#quick-start)
20
- 2. [Extending it](#extending-it)
21
- 3. [`/web` Command — Browser Toggle & Profiles](#web-command--browser-toggle--profiles)
22
- 4. [All 12 Tools](#all-12-tools)
23
- 5. [Stateless Fetching (web-fetch)](#stateless-fetching-web-fetch)
24
- 6. [Navigation Guides (web-guide & web-learn)](#navigation-guides-web-guide--web-learn)
25
- 7. [`/web status` — Detailed Runtime Status](#web-status--detailed-runtime-status)
26
- 8. [Profiles — Persistent Sessions](#profiles--persistent-sessions)
27
- 9. [Cookie Management](#cookie-management)
28
- 10. [Backend Architecture](#backend-architecture)
29
- 11. [Configuration (settings.json)](#configuration-settingsjson)
30
- 12. [Tips & Best Practices](#tips--best-practices)
31
-
32
- ---
33
-
34
15
  ## Quick Start
35
16
 
36
17
  ```bash
@@ -42,46 +23,16 @@ Once loaded, you'll see a notification like:
42
23
 
43
24
  > 🌐 Browser extension loaded (plugins: chromium, firefox). Try: web-fetch for static pages or browser-navigate for interactive browsing.
44
25
 
45
- The browser tools are **enabled by default**. You can:
26
+ The browser tools are **enabled by default**: `web-fetch` for static pages,
27
+ `browser-navigate` (plus click/type/scroll/screenshots via `@e` refs) for
28
+ interactive browsing.
46
29
 
47
- - **Fetch a static page**: The agent uses `web-fetch` for quick, stateless
48
- content retrieval (no JavaScript, no session).
49
- - **Browse interactively**: The agent uses `browser-navigate` to visit a page,
50
- then clicks, types, scrolls, and screenshots using `@e1`/`@e2` element
51
- references from the accessibility tree.
52
-
53
- > **Tip:** If you only need Markdown content from a static page, `web-fetch`
54
- > is faster and lighter than launching a full browser session.
55
- >
56
30
  > **Playwright browser binaries are not downloaded during `npm install`.**
57
31
  > Run `npx playwright install chromium firefox` separately. On first
58
32
  > `browser-navigate` without them, you'll be prompted with the exact command.
59
33
 
60
34
  ---
61
35
 
62
- ## Extending it
63
-
64
- Beyond the toggle, three surfaces are extensible or shared rather than hardcoded:
65
-
66
- - **Navigation guides** — `web-learn` saves site-specific playbooks that
67
- auto-match by domain and resurface in later sessions. See
68
- [Navigation Guides](#navigation-guides-web-guide--web-learn).
69
- - **Custom browser backends** — if a site blocks the shipped Chromium/Firefox,
70
- drop a `bridge.py` subclass into `~/.pi/agent/pi-lean-portal/user-backends/`
71
- and drive a patched engine like [Camoufox](https://github.com/daijro/camoufox)
72
- yourself. The full flow lives in [Backend Architecture](#backend-architecture)
73
- and [`contributed/README.md`](./contributed/README.md).
74
- - **Toolset toggles** — the `/web` toggle is powered by the shared
75
- [`pi-tool-masking`](https://github.com/coreyryanhanson/pi-tool-masking/) peer dep,
76
- which handles active-set masking, sibling-tool union math, and the
77
- `TOOLSET_EVENTS` protocol that keeps the `browser`/`search` status-bar
78
- glyphs in sync across extensions. It's published as a general toggle
79
- mechanism — any extension that hides/shows its own tools can build on the
80
- same scheme so masking and glyph events stay consistent across the whole
81
- agent, rather than each toggle reinventing its own.
82
-
83
- ---
84
-
85
36
  ## `/web` Command — Browser Toggle & Profiles
86
37
 
87
38
  The `/web` command controls whether web tools are visible to the AI agent,
@@ -90,202 +41,132 @@ toggles guide-saving mode, and manages browser profiles.
90
41
  ### Three-State Toggle
91
42
 
92
43
  | Command | Effect |
93
- |---------|--------|
94
- | `/web on` | **Browsing only** — all interactive browser tools + `web-fetch` are available. `web-learn` is hidden. If `pi-lean-search` is also installed, `web-search` is enabled too. |
95
- | `/web learn` | **Browsing + guide-saving** — same as `on`, plus the `web-learn` tool is available so the AI can save/update navigation guides on request. |
96
- | `/web off` | **All web tools hidden** — saves ~1500–2000 tokens per turn by removing tool schemas from the prompt. `web-fetch` and `web-search` are also hidden. |
44
+ | ------- | ------ |
45
+ | `/web on` | **Browsing only**: all interactive browser tools + `web-fetch` are available. `web-learn` is hidden. If `pi-lean-search` is also installed, `web-search` is enabled too. |
46
+ | `/web learn` | **Browsing + guide-saving**: same as `on`, plus the `web-learn` tool is available so the AI can save/update navigation guides on request. |
47
+ | `/web off` | **All web tools hidden**: saves ~1500–2000 tokens per turn by removing tool schemas from the prompt. `web-fetch` and `web-search` are also hidden. |
97
48
 
98
49
  ### Check Current State
99
50
 
100
51
  | Command | Effect |
101
- |---------|--------|
52
+ | ------- | ------ |
102
53
  | `/web` | Show current toggle status and available sub-commands. |
103
- | `/web status` | **Detailed runtime status** — toggle state, plugin health, active sessions, and profiles on disk. |
104
-
105
- ### Why Toggle?
106
-
107
- - **`/web off`**: When you're done browsing, turning tools off reduces context
108
- usage and keeps the agent focused on coding.
109
- - **`/web learn`**: Only enable this when you want the agent to save navigation
110
- guidance for a site. The agent never calls `web-learn` unprompted — it must
111
- be in learn mode.
112
- - **State persists** across `/reload`, `/resume`, `/fork`, and `/tree`
113
- navigation — the toggle remembers what you chose.
114
-
115
- ### Persistence
116
-
117
- The toggle state is stored in the conversation's branch history. A fresh
118
- conversation starts with the default from `settings.json` (`browserToggle.defaultEnabled`,
119
- which defaults to `true`).
120
-
121
- > ⚠️ `browserToggle.defaultEnabled` is still read today but is scheduled to be
122
- > replaced by the `pi-tool-masking` library's `toolsetDefaults` block in an
123
- > upcoming release — **that block is not read yet**, so keep the legacy key
124
- > until the replacement ships. See
125
- > [Configuration (`settings.json`)](#configuration-settingsjson) for the
126
- > future shape and how to pre-add it harmlessly. A migration warning fires on
127
- > session start once the legacy key is present and the web default hasn't yet
128
- > been mirrored into `toolsetDefaults`.
129
-
130
- ---
131
-
132
- ## All 12 Tools
133
-
134
- `pi-lean-portal` registers 12 tools. The first 9 require a **browser session**
135
- (created by `browser-navigate`). `web-fetch`, `web-guide`, and `web-learn` are stateless.
136
-
137
- **Auto-captured screenshots:** `browser-navigate` and `browser-snapshot` automatically
138
- capture a screenshot to a temp file (`/tmp/pi-lean-portal/screenshot-<taskId>.jpg`).
139
- Use the `read` tool to visually inspect the page when the accessibility tree isn't enough.
140
- Screenshots capture the 1280×720 viewport (not full-page) — the same viewport the
141
- browser uses for navigation.
142
-
143
- ### 1. `browser-navigate` — Visit a Page
144
-
145
- Navigates to a URL using a browser plugin (default: Chromium). Returns an
146
- accessibility tree annotated with **@e1, @e2, …** element references.
147
-
148
- ```text
149
- Title: Example Domain
150
- URL: https://example.com
151
- Backend: chromium
152
- Interactive elements: 3
153
-
154
- link "More information..." [@e1]
155
- → https://www.iana.org/domains/example
156
- …
157
- ```
158
-
159
- **Parameters:**
160
-
161
- - `url` — the target URL
162
- - `strategy` (optional) — `"auto"` (default) or a plugin name like `"chromium"` or `"firefox"`
163
- - `timeout` (optional) — seconds, default 30, max 120
164
- - `profile` (optional) — `"none"`, `"session"`, or a named profile (see
165
- [Profiles](#profiles--persistent-sessions))
166
-
167
- **Output includes:**
168
-
169
- - Page title, URL, backend name, element count
170
- - Profile info when using profiles
171
- - Bot detection warning when applicable
172
- - Screenshot file path for visual inspection
173
- - Guide footer appended when relevant guides apply
174
-
175
- ### 2. `browser-snapshot` — Refresh the Accessibility Tree
176
-
177
- Returns the current page's accessibility tree with up-to-date `@e` refs. Also
178
- captures a screenshot to a temp file (path in output). Use after clicking, typing,
179
- or scrolling to get fresh element references.
180
-
181
- - `full=true` — returns the complete tree (by default it's compacted to
182
- ~2500 characters for LLM efficiency)
183
-
184
- ### 3. `browser-click` — Click an Element
54
+ | `/web status` | **Detailed runtime status** including toggle state, plugin health, active sessions, and profiles on disk. |
185
55
 
186
56
  ```text
187
- browser-click ref="@e5"
57
+ 🌐 Browser tools: ✅ on | 📖 Learn mode: ❌ off
58
+ ────────────────────────────────────────
59
+ Status: idle
60
+ Plugins: chromium, firefox, chromium-py (disabled), firefox-py (disabled)
61
+ Use web-fetch for stateless HTTP fetches.
62
+ Active sessions: 1
63
+ PW [chromium] https://example.com — Example Domain [profile: session]
64
+ Profiles: 1 on disk (named)
65
+ shopping (0.3 KB) ← active
66
+ Session profiles: 1 (manage with /web profile)
188
67
  ```
189
68
 
190
- Clicks the element identified by its `@e` reference. Returns a fresh snapshot
191
- of the page after the click.
192
-
193
- ### 4. `browser-type` — Type Into a Field
194
-
195
- ```text
196
- browser-type ref="@e3" text="hello world"
197
- ```
69
+ ### Persistence
198
70
 
199
- Clears the input, then types the given text. Works on textboxes, searchboxes,
200
- and comboboxes.
71
+ Toggle state persists across `/reload`, `/resume`, `/fork`, and `/tree` — it's
72
+ stored in the conversation's branch history. Fresh conversations start from
73
+ the `toolsetDefaults` block in `settings.json`
74
+ ([Configuration](#configuration-settingsjson)).
201
75
 
202
- ### 5. `browser-scroll` — Scroll the Page
76
+ ---
203
77
 
204
- ```text
205
- browser-scroll direction="down"
206
- ```
78
+ ## Profiles — Persistent Sessions
207
79
 
208
- Scrolls approximately one viewport height. Returns a fresh snapshot.
80
+ Profiles let the AI agent maintain persistent browser state (cookies,
81
+ localStorage) across calls, conversations, and even across different subagents.
209
82
 
210
- ### 6. `browser-back` — Go Back
83
+ ### Profile Modes
211
84
 
212
- Navigates back in browser history. Returns the previous page's snapshot.
85
+ | Mode | `browser-navigate profile=` | Behavior |
86
+ | ---- | --------------------------- | -------- |
87
+ | None | `"none"` | Clean slate every time (no cookies or state) |
88
+ | Session | `"session"` (default) | Persists state for the current conversation; survives `/reload` and `/resume` |
89
+ | Named | `"shopping"`, `"work"`, etc. | Shared across conversations and subagents, similar to browser tabs sharing a profile |
213
90
 
214
- ### 7. `browser-press` — Press a Keyboard Key
91
+ ### Managing Profiles with `/web profile`
215
92
 
216
- ```text
217
- browser-press key="Enter"
218
- ```
93
+ | Sub-command | Effect |
94
+ | ----------- | ------ |
95
+ | `/web profile list` | List all profiles on disk with their state size |
96
+ | `/web profile create shopping` | Create a new named profile |
97
+ | `/web profile session` | Set conversation-scoped default to session mode |
98
+ | `/web profile none` | Reset default to ephemeral (no persistence) |
99
+ | `/web profile shopping` | Switch default profile to an existing named profile |
100
+ | `/web profile clear shopping` | Delete the saved state for a profile (keeps the directory) |
101
+ | `/web profile clear-all --confirm` | Clear ALL profile states |
102
+ | `/web profile prune --confirm` | Remove stale session profiles for ended conversations |
219
103
 
220
- Useful keys: `Enter`, `Tab`, `Escape`, `ArrowDown`, `ArrowUp`, `Backspace`.
104
+ > **How it works:** Profile state is stored at
105
+ > `~/.pi/agent/pi-lean-portal/browser-state/<profile-name>/storage-state.json`.
106
+ > Session-scoped profiles are auto-cleaned when the pi conversation ends.
221
107
 
222
- ### 8. `browser-console` — Read Console / Run JS
108
+ ---
223
109
 
224
- Three modes:
110
+ ## Cookie Management
225
111
 
226
- | Usage | Effect |
227
- |-------|--------|
228
- | `browser-console expression="document.title"` | Evaluates JS and returns the result |
229
- | `browser-console` (no params) | Returns captured console messages (log, warn, error, info) |
230
- | `browser-console clear=true` | Clears the captured console log |
112
+ The `/web cookies` command lets you inspect and clear session cookies:
231
113
 
232
- ### 9. `browser-inspect` — Targeted Element Discovery
114
+ | Sub-command | Effect |
115
+ | ----------- | ------ |
116
+ | `/web cookies list` | List all cookies in the current session (name, value, domain, expiry, flags) |
117
+ | `/web cookies clear --confirm` | Clear ALL cookies for the current session |
233
118
 
234
- A lighter alternative to loading a full snapshot. Queries the page for specific
235
- elements or extracts text content.
119
+ > Cookies are saved as part of profile state. When you switch profiles,
120
+ > the cookies from the old profile are preserved and the new profile's
121
+ > cookies are loaded.
236
122
 
237
- | Parameter | Example | Effect |
238
- |-----------|---------|--------|
239
- | `role` | `"link,button"` | Filter by ARIA role(s) |
240
- | `name` | `"Submit"` | Filter by accessible name (case-insensitive) |
241
- | `ref` | `"e5"` | Look up a specific `@e` ref |
242
- | `subtree` | `"dialog"` | Scope to elements inside a container |
243
- | `text` | `true` | Run DOM text extractor with `@e` annotations |
244
- | `maxChars` | `500` | Limit output length; `0` for full content |
245
- | `query` | `"pricing"` | Keyword filter (only active with `text=true`) |
123
+ ---
246
124
 
247
- > **Tip:** Use `browser-inspect role="dialog"` to quickly check for consent
248
- > dialogs without loading the full tree.
125
+ ## All 12 Tools
249
126
 
250
- ---
127
+ `pi-lean-portal` registers 12 tools. Three are **stateless**; the rest require
128
+ a **browser session** (created by `browser-navigate`).
251
129
 
252
- ## Stateless Fetching (`web-fetch`)
130
+ | Tool | What it does | State |
131
+ | ---- | ------------ | ----- |
132
+ | `web-fetch` | Fetch a URL → Markdown, no browser session | stateless |
133
+ | `web-guide` / `web-learn` | Read / save navigation guides (learn mode via `/web learn`) | stateless |
134
+ | `browser-navigate` | Open a page → accessibility tree with `@e` element refs | session |
135
+ | `browser-snapshot` | Refresh the tree (`full=true` returns the uncompacted tree) | session |
136
+ | `browser-click` / `browser-type` / `browser-scroll` / `browser-back` / `browser-press` | Interact via `@e` refs (click, type, scroll, history, keyboard) | session |
137
+ | `browser-console` | Read captured console messages; also evaluates JS in the page | session |
138
+ | `browser-inspect` | Targeted element/text queries without loading a full snapshot | session |
253
139
 
254
- `web-fetch` is a lightweight, stateless tool that fetches a URL and converts
255
- HTML to Markdown. It does **not** create a browser session.
140
+ ### Automatic Artifacts
256
141
 
257
- **When to use:**
142
+ Session tools save their full output to disk so the agent never needs a second call:
258
143
 
259
- - Static pages, API docs, READMEs, articles
260
- - Quick lookups where JavaScript isn't needed
261
- - When the interactive browser is blocked by bot detection — `web-fetch`
262
- sometimes succeeds where the browser triggers a challenge
144
+ - **Screenshots**: `browser-navigate` and `browser-snapshot` auto-capture a viewport-sized (1280×720) JPEG to `/tmp/pi-lean-portal/screenshot-<taskId>.jpg`[^1].
145
+ - **Full Snapshots**: Any snapshot compacted for size is cached in full at `/tmp/pi-lean-portal/snapshot-*.txt` (last 2 per task).
263
146
 
264
- **Output handling:**
147
+ Both file paths are surfaced as hints in the tool output, which the agent can follow up with the `read` tool.
265
148
 
266
- - Content is truncated to ~4000 characters inline
267
- - Larger content is spilled to a temp file in `/tmp/pi-lean-portal/` — the agent can `read` the file with offset/limit for specific sections. This cache ensures that the agent can access the full page content even when it exceeds the LLM's immediate context window.
268
- - Bot detection and JS-only shells are detected heuristically
149
+ [^1]: Not full-page.
269
150
 
270
151
  ---
271
152
 
272
- ## Navigation Guides (`web-guide` & `web-learn`)
153
+ ## Navigation Guides
273
154
 
274
155
  ### Built-in Pattern Guides
275
156
 
276
157
  `pi-lean-portal` ships with four built-in pattern guides that appear in the guide footer when relevant:
277
158
 
278
159
  | Guide | Trigger | What It Covers |
279
- |-------|---------|----------------|
280
- | `bot-detection` | When bot blocking is detected | Cloudflare, challenge pages, what NOT to do |
281
- | `cookie-consent` | When a dialog is detected in the snapshot | Accept/Reject buttons, Escape key, verification |
160
+ | ----- | ------- | -------------- |
161
+ | `bot-detection` | Bot blocking detected (`botDetected`) | Cloudflare, challenge pages, what NOT to do |
162
+ | `cookie-consent` | Dialog detected (`dialogDetected`) | Accept/Reject buttons, Escape key, verification |
282
163
  | `pagination` | On-demand | Next buttons, infinite scroll, pages |
283
164
  | `search` | On-demand | Search boxes, comboboxes, result lists |
284
165
 
285
166
  ### Overriding Built-in Guides
286
167
 
287
168
  A same-named `.md` file in `~/.pi/agent/pi-lean-portal/web-guides/` (e.g.
288
- `bot-detection.md`) **shadows the builtin entirely** — the whole guide is
169
+ `bot-detection.md`) **shadows the builtin entirely**; the whole guide is
289
170
  replaced, not field-merged. To keep a pattern guide firing, include
290
171
  `trigger.signal: botDetected` (or `dialogDetected`) in the frontmatter;
291
172
  omitting it disables the trigger.
@@ -304,133 +185,41 @@ Site guides and pattern guides live in **disjoint namespaces** — a site guide
304
185
  for `www.botdetection.com` does not collide with the `bot-detection` pattern
305
186
  guide; both fire when applicable.
306
187
 
307
- ### Viewing Guides
308
-
309
- ```text
310
- web-guide → lists all available guides
311
- web-guide guide="cookie-consent" → shows guidance text
312
- ```
313
-
314
- The output includes the guide's last updated date and source (`builtin` or
315
- `user`).
316
-
317
- ### Creating Your Own Site Guides (`web-learn`)
318
-
319
- When the agent is in **learn mode** (`/web learn`), it can save or update
320
- navigation guidance for specific sites using `web-learn`.
321
-
322
- ```text
323
- web-learn domain="reddit.com" content="…guidance text…"
324
- ```
325
-
326
- This creates a `.md` file with YAML frontmatter in `~/.pi/agent/pi-lean-portal/web-guides/`.
327
- The guide becomes available immediately via `web-guide` and appears in the guide footer on
328
- future navigations to that domain.
329
-
330
- **Learn mode is off by default** — the agent never saves guides unprompted.
331
- You must explicitly enable it with `/web learn`.
332
-
333
- ---
334
-
335
- ## `/web status` — Detailed Runtime Status
336
-
337
- ```text
338
- /web status
339
- ```
340
-
341
- Shows everything about the browser runtime in one notification:
342
-
343
- ```text
344
- 🌐 Browser tools: ✅ on | 📖 Learn mode: ❌ off
345
- ────────────────────────────────────────
346
- Status: idle
347
- Plugins: chromium, firefox, chromium-py (disabled), firefox-py (disabled)
348
- Use web-fetch for stateless HTTP fetches.
349
- Active sessions: 1
350
- PW [chromium] https://example.com — Example Domain [profile: session]
351
- Profiles: 1 on disk (named)
352
- shopping (0.3 KB) ← active
353
- Session profiles: 1 (manage with /web profile)
354
- ```
355
-
356
- Covers:
357
-
358
- - **Toggle state** — whether browser/learn tools are active in the agent's context
359
- - **Backend health** — idle, busy, or error state
360
- - **All registered plugins** — enabled/disabled status
361
- - **Active sessions** — current URL, title, profile name per session
362
- - **Profiles on disk** — named profiles with state size and which is
363
- currently active; session profiles are collapsed into a single count line
364
- (inspect individually with `/web profile list`)
365
-
366
- When `pi-lean-search` is also installed, the status bar shows two independent
367
- glyphs: `● idle` (browser state) and `● searxng` (search health/state).
368
-
369
- ---
370
-
371
- ## Profiles — Persistent Sessions
372
-
373
- Profiles let the AI agent maintain persistent browser state (cookies,
374
- localStorage) across calls, conversations, and even across different subagents.
375
-
376
- ### Profile Modes
377
-
378
- | Mode | `browser-navigate profile=` | Behavior |
379
- |------|-----------------------------|----------|
380
- | None | `"none"` | Clean slate every time — no cookies, no state |
381
- | Session | `"session"` (default) | Persists state for the current conversation; survives `/reload` and `/resume` |
382
- | Named | `"shopping"`, `"work"`, etc. | Shared across conversations and subagents — like browser tabs sharing a profile |
383
-
384
- ### Managing Profiles with `/web profile`
385
-
386
- All profile management happens through the `/web profile` command:
387
-
388
- | Sub-command | Effect |
389
- |-------------|--------|
390
- | `/web profile list` | List all profiles on disk with their state size |
391
- | `/web profile create shopping` | Create a new named profile |
392
- | `/web profile session` | Set conversation-scoped default to session mode |
393
- | `/web profile none` | Reset default to ephemeral (no persistence) |
394
- | `/web profile shopping` | Switch default profile to an existing named profile |
395
- | `/web profile clear shopping` | Delete the saved state for a profile (keeps the directory) |
396
- | `/web profile clear-all --confirm` | Clear ALL profile states |
397
- | `/web profile prune --confirm` | Remove stale session profiles for ended conversations |
398
-
399
- > **How it works:** Profile state is stored at
400
- > `~/.pi/agent/pi-lean-portal/browser-state/<profile-name>/storage-state.json`.
401
- > Session-scoped profiles use names like `_session-<piSessionId>` and are
402
- > auto-cleaned when the pi conversation ends.
188
+ ### Domain Matching
403
189
 
404
- ---
190
+ A guide's declared domain matches the **exact hostname or any subdomain of
191
+ it**. Declaring `reddit.com` covers `www.reddit.com`, `old.reddit.com`, and
192
+ any other subdomain, so a guide saved once resurfaces across a site's URL
193
+ variations. This is what makes guides cheap to author: you rarely need more
194
+ than the apex domain. A domain may match multiple guides (a web guide plus
195
+ one or more API guides) — all matching guides surface together, sorted
196
+ host-first (API guides before web guides).
405
197
 
406
- ## Cookie Management
198
+ ### API guides from `pi-lean-host` (co-install)
407
199
 
408
- The `/web cookies` command lets you inspect and clear session cookies:
409
-
410
- | Sub-command | Effect |
411
- |-------------|--------|
412
- | `/web cookies list` | List all cookies in the current session (name, value, domain, expiry, flags) |
413
- | `/web cookies clear --confirm` | Clear ALL cookies for the current session |
414
-
415
- > Cookies are saved as part of profile state. When you switch profiles,
416
- > the cookies from the old profile are preserved and the new profile's
417
- > cookies are loaded.
200
+ When `pi-lean-host` is installed alongside portal and `/api` is on, its
201
+ user-authored API guides also **surface in the navigate footer** alongside
202
+ your web guides using the same reactive mechanism, with no extra setup. API guides sort
203
+ first (API access is cheaper than browsing) but both always appear, so a
204
+ partial-coverage API keeps its web guide for the gaps the API doesn't cover.
205
+ The footer routes API guides to `api-guide({domain, guide})` rather than
206
+ `web-guide`. See the
207
+ [host README](https://github.com/coreyryanhanson/pi-lean-dimension/blob/main/packages/pi-lean-host/README.md#co-installing-with-pi-lean-portal)
208
+ for the host side of the contract.
418
209
 
419
210
  ---
420
211
 
421
212
  ## Backend Architecture
422
213
 
423
214
  `pi-lean-portal` uses a **plugin-based architecture**. The core framework is
424
- backend-agnostic — plugins implement a standard `BrowserPlugin` interface,
215
+ backend-agnostic; plugins implement a standard `BrowserPlugin` interface,
425
216
  and the router dispatches tool calls to the right plugin based on a
426
217
  `strategy` parameter.
427
218
 
428
219
  ### Four Shipped Backends
429
220
 
430
- The extension ships with **four browser backends** out of the box:
431
-
432
221
  | Backend | Engine | Type | Default |
433
- |---------|--------|------|---------|
222
+ | ------- | ------ | ---- | ------- |
434
223
  | `chromium` | Chromium | Node/Playwright | **Enabled** (auto strategy) |
435
224
  | `firefox` | Firefox | Node/Playwright | **Enabled** |
436
225
  | `chromium-py` | Chromium | Python/Playwright | Disabled |
@@ -440,134 +229,43 @@ The extension ships with **four browser backends** out of the box:
440
229
  > `firefox` backend. For the Python parity backends, install Playwright
441
230
  > inside `backends/python-base/.venv`.
442
231
 
443
- ### Plugin Capabilities
444
-
445
- All four shipped backends share the same capability set (they all use
446
- Playwright under the hood). The router adapts based on each plugin's
447
- capability advertisement:
448
-
449
- | Capability | Chromium / Firefox (Node) | `-py` backends (Python) |
450
- |------------|--------------------------|------------------------|
451
- | Full-page screenshots | ✅ | ✅ |
452
- | Console message capture | ✅ | ✅ |
453
- | JavaScript evaluation | ✅ | ✅ |
454
- | Bot detection | ✅ | ✅ |
455
- | Dialog auto-dismissal | ✅ | ✅ |
456
- | AbortSignal support | ✅ | Advertised, silently ignored |
232
+ All four shipped backends support screenshots (viewport-sized, not
233
+ full-page), console capture, JS evaluation, bot detection, and dialog
234
+ auto-dismissal. The one divergence:
235
+ `AbortSignal` is advertised but silently ignored on the Python `-py` backends.
457
236
 
458
- > The `-py` Python backends are disabled by default. They ship as parity
459
- > references for the Python bridge contract and as templates for users
460
- > authoring their own Python-based backends (see the custom backends
461
- > section below). Keep `chromium-py` as a reference when reading the
462
- > bridge code.
237
+ > The `-py` backends are disabled by default, as they ship as parity references
238
+ > for the Python bridge contract and as templates for authoring your own
239
+ > Python-based backends.
463
240
 
464
241
  ### How Plugin Selection Works
465
242
 
466
- - The **order** of plugins in the config array determines priority — the
243
+ - The **order** of plugins in the config array determines priority; the
467
244
  first enabled plugin is the "auto" default (typically Chromium).
468
245
  - The **AI agent explicitly selects** which backend to use via the
469
246
  `strategy` parameter in `browser-navigate`:
470
247
  - `strategy="auto"` → uses the first enabled plugin (typically Chromium)
471
248
  - `strategy="firefox"` → uses the Firefox Node backend
472
249
  - `strategy="chromium-py"` → uses the Python Chromium backend
473
- - **No automatic fallbacks** and **no mid-session transitions** — if a
250
+ - **No automatic fallbacks** and **no mid-session transitions**. If a
474
251
  plugin fails, the agent decides what to do next.
475
- - The extension auto-detects whether a plugin is Node-based (`index.ts`)
476
- or Python-based (`bridge.py`) by inspecting the directory.
477
252
 
478
253
  ### Stealth & Custom Browser Backends
479
254
 
480
- This package ships four backends — `chromium`, `firefox`, `chromium-py`,
481
- and `firefox-py` — all built on Playwright. Additional browser support
482
- (including stealth engines like **Camoufox**) is intentionally **left
483
- to users** to author and drop in, rather than being bundled with the
484
- package. The infrastructure for this is now in place: a **quirks system**
485
- lets a backend declare how it diverges from the base Playwright behavior,
486
- and a **config channel** (`browser.init` RPC) forwards launch options
487
- from `settings.json` to the Python bridge subprocess.
488
-
489
- Most users will never need a stealth backend (see
490
- [`contributed/CHOOSING.md`](./contributed/CHOOSING.md) for when to reach
491
- for one at all). When you do, the flow is:
492
-
493
- 1. **Drop a `bridge.py` into the user-backends tree.** The convention is
494
- `~/.pi/agent/pi-lean-portal/user-backends/<name>-py/bridge.py` (the
495
- `-py` suffix mirrors the shipped `chromium-py` / `firefox-py`). This is
496
- a separate tree from the package's own `backends/` directory, which is
497
- not edited after install — exactly so custom backends survive updates.
498
- 2. **Create a venv and fetch the engine binary** (e.g.
499
- `python -m camoufox fetch`). The shared `pi_browser_bridge` library is
500
- injected onto `PYTHONPATH` automatically at spawn time, so you do not
501
- need to `pip install` it.
502
- 3. **Register it in `browser.plugins`** with an **absolute** `pythonPath`
503
- and a `launch` object whose keys are forwarded to the bridge.
504
- 4. **Verify with `/web status`** and a `browser-navigate`.
505
-
506
- The shipped **Camoufox template** at
507
- [`contributed/camoufox-py/bridge.py`](./contributed/camoufox-py/bridge.py)
508
- is a worked example — copy it as a starting point. The full install flow,
509
- the quirks schema reference, and the security model (user-backends are
510
- **trusted user code** — never auto-downloaded, no plugin marketplace) live
511
- in [`contributed/README.md`](./contributed/README.md). The decision doc at
512
- [`contributed/CHOOSING.md`](./contributed/CHOOSING.md) covers when to use
513
- a stealth backend at all and the two lifecycle patterns for implementing
514
- your own.
515
-
516
- #### Writing your own backend (high level)
517
-
518
- A custom Python backend is a subclass of `PlaywrightBridge`
519
- (`backends/python-base/pi_browser_bridge/playwright_base.py`) that sets
520
- the **quirks flags** its engine needs as class attributes and overrides
521
- the launch hook matching how the engine owns Playwright. The flags
522
- (`_fingerprint_managed_context`, `_eval_prefix`, `_scroll_via_wheel`,
523
- `_skip_default_viewport`, `_skip_networkidle`, `_wrap_mw_eval_in_eval`)
524
- all default off, so a subclass that sets none of them is bit-identical to
525
- the shipped `chromium-py` / `firefox-py`. The full table with effects is
526
- in [`contributed/README.md`](./contributed/README.md#quirks-schema-reference).
527
-
528
- Node-based custom backends follow the same shape via the
529
- `PlaywrightPluginBase` class — the auto-detection in `plugin-loading`
530
- picks up `index.ts` (Node) or `bridge.py` (Python) entry points from the
531
- user-backends directory.
532
-
533
- #### Tests are auto-discovered
534
-
535
- You usually do **not** need to write your own tests. The contributed
536
- runner at `__tests__/run-contributed-suites.test.ts` discovers every
537
- backend under `user-backends/*-py/`, loads config from the test-local
538
- `settings.json`, and runs the shared contract + persistence + parity +
539
- quirks-introspection suites against it — forwarding your configured
540
- `launch` options. Opt in with `CONTRIB_RUN=1`:
541
-
542
- ```bash
543
- npm run setup:miniwob # one-time: clone MiniWoB++ content
544
- CONTRIB_RUN=1 npx vitest run packages/pi-lean-portal/__tests__/run-contributed-suites.test.ts
545
- ```
546
-
547
- A custom backend's config entry looks like:
548
-
549
- ```jsonc
550
- {
551
- "browser": {
552
- "plugins": [
553
- { "name": "chromium", "dir": "chromium", "enabled": true, "config": {} },
554
- { "name": "firefox", "dir": "firefox", "enabled": true, "config": {} },
555
- { "name": "camoufox-py", "dir": "camoufox-py", "enabled": true, "config": {
556
- "pythonPath": "/home/me/.pi/agent/pi-lean-portal/user-backends/camoufox-py/.venv/bin/python",
557
- "launch": { "headless": true, "os": "windows", "humanize": true }
558
- }
559
- }
560
- ]
561
- }
562
- }
563
- ```
564
-
565
- `pythonPath` must be **absolute**; `dir` resolves against the user-backends
566
- root (multi-root discovery: package `backends/` → `USER_BACKENDS_DIR` →
567
- absolute). `launch` keys are forwarded to the bridge as
568
- `plugin_config.launch` via the `browser.init` RPC. Stealth backends are
569
- never in the default fallback list — a fresh install with no
570
- `browser.plugins` loads only the four shipped backends.
255
+ Stealth engines like **Camoufox** are intentionally **left to users** to
256
+ author: drop a `bridge.py` into
257
+ `~/.pi/agent/pi-lean-portal/user-backends/<name>-py/` — a separate tree
258
+ from the package's `backends/` directory (never edited after install,
259
+ so custom backends survive updates) and never in the default fallback list.
260
+ Register one in `browser.plugins` with an **absolute** `pythonPath` (see
261
+ [Configuration](#configuration-settingsjson)).
262
+
263
+ The full install flow, the quirks schema, and the worked Camoufox template
264
+ live in
265
+ [`contributed/README.md`](https://github.com/coreyryanhanson/pi-lean-dimension/blob/main/packages/pi-lean-portal/contributed/README.md);
266
+ [`contributed/CHOOSING.md`](https://github.com/coreyryanhanson/pi-lean-dimension/blob/main/packages/pi-lean-portal/contributed/CHOOSING.md)
267
+ covers when to reach for one at all (most users never need one).
268
+ Node-based custom backends follow the same shape via `PlaywrightPluginBase`.
571
269
 
572
270
  ---
573
271
 
@@ -587,7 +285,7 @@ Controls which browser backends are loaded. Entries are processed in order
587
285
  "plugins": [
588
286
  {
589
287
  "name": "chromium", // Required: unique plugin identifier
590
- "dir": "chromium", // Required: directory under backends/
288
+ "dir": "chromium", // Required: backend directory
591
289
  "enabled": true, // Optional, defaults to true
592
290
  "config": {} // Optional, passed to the plugin's init()
593
291
  }
@@ -596,11 +294,9 @@ Controls which browser backends are loaded. Entries are processed in order
596
294
  }
597
295
  ```
598
296
 
599
- Each entry requires only a unique name, a backend directory path, and an
600
- optional `config` object passed to the plugin's `init()`. For the Python
601
- backends, `config` carries `pythonPath` and a `launch` object (the shape
602
- shown for `camoufox-py` in the [Stealth & Custom Browser Backends](#stealth--custom-browser-backends)
603
- section above is the reference for a user-authored Python backend).
297
+ A user-installed stealth backend additionally puts an **absolute** `pythonPath`
298
+ and a `launch` object into `config` — see
299
+ [`contributed/README.md`](https://github.com/coreyryanhanson/pi-lean-dimension/blob/main/packages/pi-lean-portal/contributed/README.md#6-register-in-settingsjson).
604
300
 
605
301
  ### `browser.defaultProfile`
606
302
 
@@ -615,48 +311,28 @@ a `profile` parameter:
615
311
  }
616
312
  ```
617
313
 
618
- ### `browserToggle.defaultEnabled` *(deprecated — not yet replaced)*
314
+ ### `toolsetDefaults`
619
315
 
620
- Whether browser tools are enabled on fresh conversations. **This key is still
621
- read today and works as shown.** It is *scheduled* to be replaced by the
622
- `pi-tool-masking` library's `toolsetDefaults` block in an upcoming release —
623
- **that block is not read yet**, so keep this key until the replacement ships.
316
+ Whether browser tools are enabled on fresh conversations. Read by the
317
+ `pi-tool-masking` library at restore time, between the chat-branch tier and
318
+ the toolset's packaged default:
624
319
 
625
320
  ```jsonc
626
321
  {
627
- "browserToggle": {
628
- "defaultEnabled": true
322
+ "toolsetDefaults": {
323
+ "toolset-state:pi-lean-dimension.web": { "enabled": true },
324
+ "toolset-state:pi-lean-dimension.web-learn": { "enabled": false },
325
+ "toolset-state:pi-lean-dimension.search": { "enabled": true }
629
326
  }
630
327
  }
631
328
  ```
632
329
 
633
- > ⚠️ **Preparing for the offload.** Settings-based toolset defaults are being
634
- > offloaded to the `pi-tool-masking` library, which will read a new
635
- > `toolsetDefaults` block keyed by persist key. **The new block is not read
636
- > by the current release** — it's harmless to add now and will take effect
637
- > automatically once the offload ships, at which point the legacy key stops
638
- > being read. To pre-stage the migration, set both:
639
- >
640
- > ```jsonc
641
- > {
642
- > "browserToggle": { "defaultEnabled": true },
643
- > "toolsetDefaults": {
644
- > "toolset-state:pi-lean-dimension.web": { "enabled": true },
645
- > "toolset-state:pi-lean-dimension.web-learn": { "enabled": false },
646
- > "toolset-state:pi-lean-dimension.search": { "enabled": true }
647
- > }
648
- > }
649
- > ```
650
- >
651
- > - Keys are the toolsets' `persistKey` values (`toolset-state:<id>`).
652
- > - Omit a `toolsetDefaults` key to use the toolset's packaged default (`web`
653
- > and `search` default `true`; `web-learn` defaults `false`).
654
- > - The `search` key only applies when `pi-lean-search` is installed.
655
- >
656
- > Once the offload ships and the legacy read is removed, delete the
657
- > `browserToggle` block. Until then, a migration warning fires on session
658
- > start when the legacy key is present and the web default hasn't yet been
659
- > mirrored into `toolsetDefaults`; mirroring it suppresses the warning.
330
+ - Keys are the toolsets' `persistKey` values (`toolset-state:<id>`).
331
+ - Omit a `toolsetDefaults` key to use the toolset's packaged default (`web`
332
+ and `search` default `true`; `web-learn` defaults `false`).
333
+ - The `search` key only applies when `pi-lean-search` is installed.
334
+ - Pins do not apply in spawned subagent children (see pi-tool-masking
335
+ 1.3.0's `piToolMasking.childPolicy` for the opt-out).
660
336
 
661
337
  ### `browser.maxStorageStateSize`
662
338
 
@@ -677,61 +353,30 @@ Size threshold for profile state warnings (default: 10 MB):
677
353
  ### When to use `web-fetch` vs `browser-navigate`
678
354
 
679
355
  | Use `web-fetch` | Use `browser-navigate` |
680
- |-----------------|----------------------|
356
+ | --------------- | ---------------------- |
681
357
  | Static content, docs, READMEs | Interactive pages, JS-heavy SPAs |
682
358
  | Quick lookups, no session needed | Form filling, clicking, authentication |
683
- | Bot-detected pages (fallback) | Visual inspection (auto-captured screenshots) |
684
- | Content you want as clean Markdown | Pages where you need the accessibility tree |
685
-
686
- ### Working with `@e` Element References
687
-
688
- - `@e1`, `@e2`, etc. are assigned based on the accessibility tree order
689
- - `browser-click`/`type`/`scroll` already return a fresh snapshot and cache
690
- the full tree to disk — no separate `browser-snapshot` needed unless you
691
- want the uncompacted tree (`full=true`) or a screenshot
692
- - `browser-inspect` is cheaper than `browser-snapshot full=true` for finding
693
- specific elements
359
+ | Content you want as clean Markdown | Visual inspection (auto-captured screenshots), pages where you need the accessibility tree |
694
360
 
695
- ### Navigating Large Pages
361
+ ### Working with `@e` Element References & Large Pages
696
362
 
697
- - Snapshots are automatically compacted to ~2500 characters
698
- - Very large pages (>8000 chars) preserve the top ~2000 chars
699
- - The **full tree is cached to disk** at `/tmp/pi-lean-portal/snapshot-*.txt`
700
- when it would otherwise be truncated — use `read` on the cache file with
701
- offset/limit to retrieve the complete tree
702
- - `browser-inspect text=true query="keyword"` finds specific content without
703
- loading the full tree
363
+ The agent interacts via `@e1`, `@e2` refs from the accessibility tree —
364
+ no CSS selectors or XPath. Snapshots are auto-compacted to ~2500 chars;
365
+ full trees and screenshots spill to `/tmp/pi-lean-portal/` (paths are
366
+ surfaced in tool output).
704
367
 
705
368
  ### Bot Detection
706
369
 
707
- When a page triggers anti-automation:
708
-
709
- 1. The agent sees a warning in the navigate output
710
- 2. The **bot-detection guide** footer appears with strategies available via `web-guide`
711
- 3. If very few elements are detected (<5), the navigation is treated as
712
- a hard failure — the agent won't try to interact with a challenge page
713
- 4. Retry with a stealth backend — the `browser-navigate` `strategy`
714
- parameter lists registered backend names; a stealth backend (e.g.
715
- `strategy="camoufox"`) can pass challenges the default `chromium`/
716
- `firefox` triggers. Only names listed in the `strategy` description
717
- are valid — there is no `"stealth"` alias
718
- 5. Try `web-fetch` on the same URL — it skips JS execution, so it can
719
- retrieve raw HTML on pages that block the interactive browser via
720
- client-side fingerprinting (it won't help against server-side WAFs)
721
-
722
- ### Guide Creation Discipline
723
-
724
- When creating navigation guides with `web-learn`:
725
-
726
- - Keep guidance **concise** (≤800 chars recommended)
727
- - Focus on: page structure, consent dialogs, known quirks, and useful
728
- selector patterns
729
- - Include domains so the guide appears in the footer on future navigations
730
- - No runtime enforcement on guide length, but brevity helps the LLM
370
+ When a page triggers anti-automation, the agent sees a warning plus a
371
+ bot-detection guide footer, and challenge pages with <5 visible elements
372
+ fail hard rather than being interacted with. If a site consistently blocks
373
+ the shipped Chromium/Firefox, that's the trigger to install a stealth
374
+ backend (see [Stealth & Custom Browser Backends](#stealth--custom-browser-backends)) —
375
+ not something to configure up front.
731
376
 
732
377
  ### Security
733
378
 
734
- - URLs are parsed with `new URL()` as input validation — malformed
379
+ - URLs are parsed with `new URL()` as input validation. Malformed
735
380
  URLs are rejected, but no SSRF boundary is enforced (a coding agent
736
381
  already has filesystem and shell access, so blocking localhost or
737
382
  private IPs would be theater)
@@ -740,9 +385,4 @@ When creating navigation guides with `web-learn`:
740
385
 
741
386
  ---
742
387
 
743
- > `pi-lean-portal` is part of the
744
- > [pi-lean-dimension](https://github.com/coreyryanhanson/pi-lean-dimension)
745
- > web-tools suite. For questions, issues, or feature requests, check the
746
- > project's documentation or open an issue.
747
- >
748
388
  > License: AGPL-3.0-only