pi-lean-dimension 0.2.3 → 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.
Files changed (176) hide show
  1. package/README.md +1 -1
  2. package/node_modules/pi-lean-portal/AGENTS.md +15 -23
  3. package/node_modules/pi-lean-portal/README.md +31 -1
  4. package/node_modules/pi-lean-portal/__tests__/browser-toggle-profile.test.ts +12 -1
  5. package/node_modules/pi-lean-portal/__tests__/browser-toggle.test.ts +263 -1102
  6. package/node_modules/pi-lean-portal/__tests__/fetch-backend.test.ts +6 -32
  7. package/node_modules/pi-lean-portal/__tests__/helpers/mock-python-bridge.py +1 -1
  8. package/node_modules/pi-lean-portal/__tests__/helpers/probe-user-backend.ts +1 -2
  9. package/node_modules/pi-lean-portal/__tests__/helpers/setup-temp-dir.ts +26 -0
  10. package/node_modules/pi-lean-portal/__tests__/plugin-config-browser.test.ts +1 -1
  11. package/node_modules/pi-lean-portal/__tests__/plugin-contract.test.ts +1 -1
  12. package/node_modules/pi-lean-portal/__tests__/plugin-loading.test.ts +1 -1
  13. package/node_modules/pi-lean-portal/__tests__/probe-user-backend.test.ts +1 -1
  14. package/node_modules/pi-lean-portal/__tests__/python-adapter.test.ts +65 -69
  15. package/node_modules/pi-lean-portal/__tests__/router-dispatch.test.ts +1 -14
  16. package/node_modules/pi-lean-portal/__tests__/run-contributed-suites.test.ts +31 -16
  17. package/node_modules/pi-lean-portal/__tests__/snapshot-cache.test.ts +1 -1
  18. package/node_modules/pi-lean-portal/__tests__/web-guides.test.ts +146 -19
  19. package/node_modules/pi-lean-portal/backends/chromium-py/__pycache__/bridge.cpython-313.pyc +0 -0
  20. package/node_modules/pi-lean-portal/backends/firefox-py/__pycache__/bridge.cpython-313.pyc +0 -0
  21. package/node_modules/pi-lean-portal/backends/playwright-base/playwright-plugin.ts +1 -1
  22. package/node_modules/pi-lean-portal/backends/python-adapter.ts +3 -3
  23. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__init__.py +2 -4
  24. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/__init__.cpython-312.pyc +0 -0
  25. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/__init__.cpython-313.pyc +0 -0
  26. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/bridge.cpython-312.pyc +0 -0
  27. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-312.pyc +0 -0
  28. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-313.pyc +0 -0
  29. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/playwright_base.py +572 -81
  30. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/conftest.cpython-313-pytest-9.1.1.pyc +0 -0
  31. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_accessibility.cpython-313-pytest-9.1.1.pyc +0 -0
  32. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_bot_detection.cpython-313-pytest-9.1.1.pyc +0 -0
  33. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_browser_data.cpython-313-pytest-9.1.1.pyc +0 -0
  34. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_playwright_base_quirks.cpython-313-pytest-9.1.1.pyc +0 -0
  35. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_py_bridges.cpython-313-pytest-9.1.1.pyc +0 -0
  36. package/node_modules/pi-lean-portal/backends/python-base/tests/test_playwright_base_quirks.py +13 -43
  37. package/node_modules/pi-lean-portal/browser-profile.ts +2 -3
  38. package/node_modules/pi-lean-portal/browser-toggle.ts +168 -380
  39. package/node_modules/pi-lean-portal/contributed/CHOOSING.md +2 -2
  40. package/node_modules/pi-lean-portal/contributed/README.md +65 -6
  41. package/node_modules/pi-lean-portal/contributed/camoufox-py/__pycache__/bridge.cpython-312.pyc +0 -0
  42. package/node_modules/pi-lean-portal/contributed/camoufox-py/bridge.py +108 -14
  43. package/node_modules/pi-lean-portal/contributed/camoufox-py/pin.json +4 -0
  44. package/node_modules/pi-lean-portal/contributed/invisible-py/bridge.py +1 -1
  45. package/node_modules/pi-lean-portal/core/fetch-backend.ts +10 -30
  46. package/node_modules/pi-lean-portal/core/guides.ts +14 -18
  47. package/node_modules/pi-lean-portal/core/router.ts +2 -20
  48. package/node_modules/pi-lean-portal/core/shared/bot-detection.ts +19 -30
  49. package/node_modules/pi-lean-portal/core/shared/browser-data.ts +4 -4
  50. package/node_modules/pi-lean-portal/core/shared/nav-settle.ts +2 -2
  51. package/node_modules/pi-lean-portal/core/shared/paths.ts +16 -2
  52. package/node_modules/pi-lean-portal/core/shared/settings-reader.ts +2 -7
  53. package/node_modules/pi-lean-portal/core/shared/snapshot-cache.ts +1 -1
  54. package/node_modules/pi-lean-portal/index.ts +8 -1
  55. package/node_modules/pi-lean-portal/package.json +2 -1
  56. package/node_modules/pi-lean-portal/tools/web-learn.ts +1 -4
  57. package/node_modules/pi-lean-search/__tests__/web-search.test.ts +87 -0
  58. package/node_modules/pi-lean-search/index.ts +134 -64
  59. package/node_modules/pi-lean-search/package.json +2 -1
  60. package/node_modules/playwright/README.md +3 -3
  61. package/node_modules/playwright/lib/agents/copilot-setup-steps.yml +2 -2
  62. package/node_modules/playwright/lib/cli/reportActions.js +2 -2
  63. package/node_modules/playwright/lib/common/index.js +77 -52
  64. package/node_modules/playwright/lib/common/index.js.txt +10 -11
  65. package/node_modules/playwright/lib/errorContext.js +2 -3
  66. package/node_modules/playwright/lib/index.js +30 -8
  67. package/node_modules/playwright/lib/matchers/expect.js +4811 -4379
  68. package/node_modules/playwright/lib/matchers/expect.js.LICENSE +87 -140
  69. package/node_modules/playwright/lib/matchers/expect.js.txt +30 -35
  70. package/node_modules/playwright/lib/program.js +19 -0
  71. package/node_modules/playwright/lib/runner/index.js +309 -153
  72. package/node_modules/playwright/lib/runner/index.js.txt +13 -12
  73. package/node_modules/playwright/lib/transform/babelBundle.js +12899 -11654
  74. package/node_modules/playwright/lib/transform/babelBundle.js.LICENSE +379 -354
  75. package/node_modules/playwright/lib/transform/babelBundle.js.txt +39 -39
  76. package/node_modules/playwright/lib/transform/esmLoader.js +3129 -1116
  77. package/node_modules/playwright/lib/transform/esmLoader.js.LICENSE +81 -61
  78. package/node_modules/playwright/lib/transform/esmLoader.js.txt +21 -18
  79. package/node_modules/playwright/lib/util.js +2 -29
  80. package/node_modules/playwright/lib/worker/workerProcessEntry.js +14 -32
  81. package/node_modules/playwright/lib/worker/workerProcessEntry.js.txt +7 -8
  82. package/node_modules/playwright/package.json +3 -6
  83. package/node_modules/playwright/types/test.d.ts +391 -4
  84. package/node_modules/playwright/types/testReporter.d.ts +79 -4
  85. package/node_modules/playwright-core/browsers.json +12 -18
  86. package/node_modules/playwright-core/lib/bootstrap.js +1 -1
  87. package/node_modules/playwright-core/lib/coreBundle.js +11344 -9900
  88. package/node_modules/playwright-core/lib/server/electron/loader.js +5 -2
  89. package/node_modules/playwright-core/lib/serverRegistry.js +5 -1
  90. package/node_modules/playwright-core/lib/tools/cli-client/help.json +21 -6
  91. package/node_modules/playwright-core/lib/tools/cli-client/program.js +1 -1
  92. package/node_modules/playwright-core/lib/tools/cli-client/session.js +4 -0
  93. package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/SKILL.md +21 -5
  94. package/node_modules/playwright-core/lib/tools/{cli-client/skill/references/spec-driven-testing.md → skills/playwright-cli/references/test-generation.md} +135 -7
  95. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/SKILL.md +143 -0
  96. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
  97. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/migration.md +85 -0
  98. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/react.md +67 -0
  99. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/vue.md +75 -0
  100. package/node_modules/playwright-core/lib/tools/utils/extension.js +31 -8
  101. package/node_modules/playwright-core/lib/utilsBundle.js +37698 -33670
  102. package/node_modules/playwright-core/lib/utilsBundle.js.LICENSE +391 -268
  103. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-CyWAfh-p.js +11 -0
  104. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-DhC616m4.css +1 -0
  105. package/node_modules/playwright-core/lib/vite/dashboard/index.html +2 -2
  106. package/node_modules/playwright-core/lib/vite/htmlReport/report.css +2 -1
  107. package/node_modules/playwright-core/lib/vite/htmlReport/report.js +15 -55
  108. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
  109. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-CwYYHbcZ.js +32 -0
  110. package/node_modules/playwright-core/lib/vite/recorder/assets/index-DYjdXIbE.js +129 -0
  111. package/node_modules/playwright-core/lib/vite/recorder/assets/index-l_lX622x.css +1 -0
  112. package/node_modules/playwright-core/lib/vite/recorder/index.html +2 -2
  113. package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-By56iMx7.js +32 -0
  114. package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-B34OrIms.js +181 -0
  115. package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
  116. package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-COQkjINf.js +7 -0
  117. package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
  118. package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.BLFoOugd.css +1 -0
  119. package/node_modules/playwright-core/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
  120. package/node_modules/playwright-core/lib/vite/traceViewer/index.Dl36UVQT.js +1 -0
  121. package/node_modules/playwright-core/lib/vite/traceViewer/index.html +5 -5
  122. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
  123. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.html +2 -2
  124. package/node_modules/playwright-core/lib/vite/traceViewer/sw.bundle.js +3 -4
  125. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.C7UW1sC9.css +1 -0
  126. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.D962mr9b.js +5 -0
  127. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +5 -5
  128. package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
  129. package/node_modules/playwright-core/lib/webp_codec.LICENSE +173 -0
  130. package/node_modules/playwright-core/lib/webp_codec.wasm +0 -0
  131. package/node_modules/playwright-core/lib/xdg-open +338 -137
  132. package/node_modules/playwright-core/package.json +2 -2
  133. package/node_modules/playwright-core/types/protocol.d.ts +249 -36
  134. package/node_modules/playwright-core/types/structs.d.ts +3 -1
  135. package/node_modules/playwright-core/types/types.d.ts +2009 -266
  136. package/package.json +3 -3
  137. package/node_modules/pi-lean-portal/__tests__/url-safety.test.ts +0 -188
  138. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/bridge.cpython-313.pyc +0 -0
  139. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/patch_playwright.cpython-313.pyc +0 -0
  140. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/bridge.py +0 -525
  141. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_accessibility.cpython-313.pyc +0 -0
  142. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_bot_detection.cpython-313.pyc +0 -0
  143. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_browser_data.cpython-313.pyc +0 -0
  144. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_chromium_py_bridge.cpython-313-pytest-9.1.1.pyc +0 -0
  145. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_firefox_py_bridge.cpython-313-pytest-9.1.1.pyc +0 -0
  146. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_playwright_base_quirks.cpython-313.pyc +0 -0
  147. package/node_modules/pi-lean-portal/core/shared/url-safety.ts +0 -164
  148. package/node_modules/playwright-core/lib/server/deviceDescriptorsSource.json +0 -2739
  149. package/node_modules/playwright-core/lib/tools/cli-client/skill/references/test-generation.md +0 -134
  150. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-BY2S1tHT.css +0 -1
  151. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-C_5TMfeg.js +0 -52
  152. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-DYBRYzYX.css +0 -1
  153. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-DeBYQozu.js +0 -32
  154. package/node_modules/playwright-core/lib/vite/recorder/assets/index-4ZiSSCmn.css +0 -1
  155. package/node_modules/playwright-core/lib/vite/recorder/assets/index-Bq-mQf8S.js +0 -193
  156. package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-LEHpjmcn.js +0 -32
  157. package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-BNmKHKpQ.js +0 -264
  158. package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-BYQrIQwR.js +0 -1
  159. package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-CsJ4vdCR.js +0 -9
  160. package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.DYBRYzYX.css +0 -1
  161. package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.CjdS-WJx.css +0 -1
  162. package/node_modules/playwright-core/lib/vite/traceViewer/index.CzXZzn5A.css +0 -1
  163. package/node_modules/playwright-core/lib/vite/traceViewer/index.DMMX1gXU.js +0 -2
  164. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.v8KI4P3m.js +0 -2
  165. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.BZQ54Kgt.css +0 -1
  166. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.Ut8wwJNp.js +0 -6
  167. package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.DYP7pi_n.css +0 -32
  168. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/element-attributes.md +0 -0
  169. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/playwright-tests.md +0 -0
  170. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/request-mocking.md +0 -0
  171. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/running-code.md +0 -0
  172. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/session-management.md +0 -0
  173. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/storage-state.md +0 -0
  174. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/tracing.md +0 -0
  175. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/video-recording.md +0 -0
  176. /package/node_modules/playwright-core/lib/tools/{trace → skills/playwright-trace}/SKILL.md +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # pi-lean-dimension
2
2
 
3
- > Web browsing and search tools for [Pi](https://github.com/earendil-works/pi-coding-agent), the AI coding agent.
3
+ > Web browsing and search tools for [Pi](https://github.com/earendil-works/pi), the AI coding agent.
4
4
 
5
5
  A monorepo housing three Pi extension packages that give your AI agent the
6
6
  ability to browse the web interactively, fetch static pages as Markdown, and
@@ -39,16 +39,11 @@ Capabilities (`PluginCapabilities`) advertise quirks. The router checks them at
39
39
  ## Browser launch hook
40
40
 
41
41
  The portal's own backends launch their browser directly and drive
42
- their own page — there is **no external-attach path**. The
43
- `getAttachEndpoint()` interface method, the `AttachEndpoint` union,
44
- `core/shared/cdp-endpoint.ts`, the `launchServer()` / `connect()`
45
- hop, and the `_cdpEndpoint` / `_wsEndpoint` / `_browserServer` /
46
- `_reconnectBrowser()` scaffolding were all removed (see
42
+ their own page — there is **no external-attach path** and no
43
+ post-launch hook for third-party subclasses (see
47
44
  [`docs/decisions/miniwob-and-host-setup.md`](../../docs/decisions/miniwob-and-host-setup.md)).
48
- There is no post-launch hook for third-party subclasses and no
49
- `connectOverCDP?` interface hook — a host-owns-browser ("Mode B")
50
- path was considered and dropped as YAGNI; either will be re-added
51
- alongside a real consumer that needs it.
45
+ A host-owns-browser path ("Mode B") was considered and dropped as
46
+ YAGNI; either will be re-added alongside a real consumer that needs it.
52
47
 
53
48
  ## Stealth backends (user-managed)
54
49
 
@@ -140,22 +135,20 @@ as class attributes on your subclass:
140
135
  |------|---------|-----------------|
141
136
  | `_fingerprint_managed_context` | `False` | `create_browser_context()` skips hardcoded `viewport`/`user_agent`; lets the fingerprint package set them. |
142
137
  | `_eval_prefix` | `""` | Prepended to every `page.evaluate` expression in `do_evaluate` (e.g. Camoufox's `"mw:"` routes writes to the main world). |
143
- | `_scroll_via_wheel` | `False` | `do_scroll` uses `page.mouse.wheel` instead of `page.evaluate("window.scrollBy")` (avoids eval-write under isolated-world stealth). |
138
+ | `_scroll_via_wheel` | `False` | `do_scroll` uses `page.mouse.wheel` instead of `page.evaluate("window.scrollBy")`. Legacy Camoufox (``135.0.1-beta.24``) needed this; current binary (``152.0.4-beta.28``) is the reverse (wheel no-ops, eval scrollBy works), so Camoufox leaves the default `False`. |
144
139
  | `_skip_default_viewport` | `False` | Skips Playwright's `Browser.setDefaultViewport` CDP call (Camoufox binary rejects its `isMobile` prop). |
145
140
  | `_skip_networkidle` | `False` | Nav-settle uses `load` instead of `networkidle` (patched binaries don't fire `networkidle` reliably). |
146
- | `_wrap_mw_eval_in_eval` | `False` | `do_evaluate` rewrites the expression as `eval(<JSON-string of expression>)` before prepending `_eval_prefix`, so multi-statement scripts survive Camoufox's `let _s = (${script})` main-world wrapper (see prose below). |
141
+ | `_wrap_mw_eval_in_eval` | `False` | `do_evaluate` rewrites the expression as `eval(<JSON-string of expression>)` before prepending `_eval_prefix`, so multi-statement scripts survive Camoufox's `let _s = (${script})` main-world wrapper. |
142
+ | `_settle_budget_ms` | `400` | Poll budget (ms) for `_wait_for_navigation_settle` when no `framenavigated` event fires. Stealth backends with high Juggler event latency (e.g. Camoufox) override to 2000. |
143
+ | `_url_stability_settle` | `False` | When True, the no-nav poll confirms URL is stable at a new value for 150 ms before exiting, rather than a fixed budget. |
147
144
  | `_csp_safe_readonly_via_init_script` | `False` | `do_evaluate(read_only=True)` (the EXTRACTOR_SCRIPT) reads its JSON result from a `<meta id="__pi-extract">` tag that `create_browser_context` registers as a `context.add_init_script` (isolated world, CSP-free) at `DOMContentLoaded`, instead of `page.evaluate`. For patched-Firefox stealth binaries that route `page.evaluate` through `eval()` in the page's main world (CSP-subject) — Camoufox is NOT affected (its binary keeps Juggler's CSP-free isolated-world). The adapter plumbs the script via the `browser.init` config key `readOnlyExtractorScript`. Stale across SPA route changes (no new load) — fine for navigate→inspect. |
148
145
 
149
146
  All flags default off → `chromium-py` / `firefox-py` behavior is
150
- bit-identical to a pre-stealth install. A dropped v2 `_context_factory`
151
- flag (dispatching to a `_camoufox_new_context` helper) was removed
152
- when `camoufox.NewContext` turned out to be broken on the current
153
- binary (`Protocol error (Browser.setDefaultViewport)` from the same
154
- `isMobile` rejection `_skip_default_viewport` handles). Camoufox
155
- injects the fingerprint at **browser launch** via `camoufox.NewBrowser`,
147
+ bit-identical to a pre-stealth install. Camoufox injects the
148
+ fingerprint at **browser launch** via `camoufox.NewBrowser`,
156
149
  so standard `browser.new_context()` with
157
- `_fingerprint_managed_context = True` is correct — do not re-attempt
158
- `NewContext`.
150
+ `_fingerprint_managed_context = True` is correct — do not
151
+ re-attempt `NewContext`.
159
152
 
160
153
  ### Camoufox: the shipped example template
161
154
 
@@ -204,14 +197,13 @@ All tool calls dispatch through the router. Key responsibilities:
204
197
 
205
198
  ## Known Constraints & Debt
206
199
 
207
- - **Console capture in Python backends** — Both `chromium-py` and `firefox-py` inherit console capture (500-entry ring buffer) and dialog auto-dismissal from `PlaywrightBridge._setup_page_session()` in `python-base`. The base `BrowserBridge` (an `abc.ABC`) does not define `_setup_page_session`; future Python plugins that subclass `BrowserBridge` directly must supply their own session setup (subclasses of `PlaywrightBridge` inherit it).
200
+ - **Console capture in Python backends** — Both `chromium-py` and `firefox-py` inherit console capture (500-entry ring buffer) and dialog auto-dismissal from `PlaywrightBridge._setup_page_session()` in `python-base`. All Python browser backends must subclass `PlaywrightBridge` to get session setup and JSON-RPC dispatch.
208
201
  - **AbortSignal not supported on Python bridge** — the router passes `signal` through unconditionally (no capability check). The Python adapter accepts and silently ignores the signal. `supportsAbortSignal` is advertised but unenforced.
209
202
  - **Sessions are per taskId** — mapped to `browser-NNN` keys via `_sessionKeys`/`_sessionCounter` in `core/shared/task-id.ts`. Created on first navigate, cleaned up on `session_shutdown`.
210
- - **Python shared-context machinery removed (B1)** — the `browser.newPage`/`browser.closePage` RPC routes, `_profile_contexts` ref-counting, and `ensure_profile_session`/`remove_profile_session` methods were removed from both the base `BrowserBridge` and `ChromiumPyBridge`. Named profiles now use disk persistence (load-on-navigate via `storageState`) matching the TS Chromium plugin. Both backends use `ensure_session(task_id, config)` for all sessions.
203
+ - **Python shared-context machinery removed** — Named profiles now use disk persistence (load-on-navigate via `storageState`) matching the TS Chromium plugin. Both backends use `ensure_session(task_id, config)` for all sessions.
211
204
  - **Python bridge reuses BrowserContexts across navigations** — `ensure_session()` returns the existing session on re-navigate (unlike the TS Chromium plugin which creates a fresh context per navigate). This means in-process cookies survive re-navigation without explicit save, but also means `storageState` from the router is ignored on re-navigate (the context already exists). The Python adapter's `_persistState()` saves current cookies to disk before the navigate RPC for cross-process persistence.
212
205
  - **`_persistState()` helper in both backends** — both `PlaywrightPluginBase._persistState` (direct `context.storageState()` call) and `PythonPluginAdapter._persistState` (JSON-RPC `browser.getStorageState` retrieval) delegate to the shared `persistSessionState()` helper in `core/shared/storage-state.ts`, which owns the `session?.persistState` gate, the `saveStorageState()` call, and the warn-and-swallow error path. Called both from `cleanup()` and — on re-navigate — from `getOrCreateContext()` (Chromium) or `navigate()` (Python) before the old context is closed/reused.
213
206
  - **Role-based locators only**: never XPath/CSS — always `getByRole()` via `buildLocator()` with positional `.nth()` for duplicates. The `INTERACTIVE_ROLES` set defines which roles get @e refs.
214
- - **All URLs go through `url-safety.ts`** — blocks localhost, private IPs (10.x, 172.16-31.x, 192.168.x, 169.254.169.254), dangerous schemes (file:, ftp:, data:, javascript:, vbscript:), and heuristically detects secrets in URLs.
215
207
  - **Screenshot**: JPEG 80% quality, viewport constrained to 1280px wide, returns data URI.
216
208
  - **Accessibility tree parser is single-pass, no-cap**: both TypeScript (`core/shared/accessibility-tree.ts`) and Python (`backends/python-base/pi_browser_bridge/accessibility.py`) use an identical single-pass algorithm — every interactive element gets an @e ref, no dialog prioritization, no element cap. Full ARIA trees beyond truncation are cached to disk via `snapshot-cache.ts`.
217
209
  - **Bot detection has three tiers**: checked against page title (challenge phrases), body text (challenge phrases + CDN patterns), and raw HTML (CAPTCHA widget embed codes). Both the TypeScript (`core/shared/bot-detection.ts`) and Python (`python-base/pi_browser_bridge/bot_detection.py`) backends share the same HTML-level signal set.
@@ -220,7 +212,7 @@ All tool calls dispatch through the router. Key responsibilities:
220
212
  - **`browser-inspect`** (`core/shared/dom-extractor.ts`): runs inline JS via `page.evaluate()`. Requires `getElementCache()` on the plugin. Text output truncated at ~2500 chars by default; pass `maxChars=0` for full. Keyword filtering via `query` parameter (case-insensitive substring on text, href, src).
221
213
  - **`parentRef` on `AriaCachedNode`**: enables `subtree=...` queries in `browser-inspect`. Set by depth-based parent stack in `parseSnapshot()`'s single pass. Dialogs become parent of interior elements.
222
214
  - **`dialogDetected` is resolved from element cache**: computed from the parsed `ElementCache` via `Array.some()` matching `role="dialog"` or `role="alertdialog"`. Not affected by snapshot truncation (unlike the old string-scan approach).
223
- - **Guide staleness**: no builtin site guides shipped — entirely user-authored via `~/.pi/agent/pi-lean-portal/web-guides/*.md`. Guides carry `updated` date and `currentDate` timestamp in output.
215
+ - **Guide staleness**: no builtin site guides shipped — entirely user-authored via `~/.pi/agent/pi-lean-portal/web-guides/*.md`. User `.md` files override builtins by name collision (the file name minus `.md` becomes the guide key; a user `bot-detection.md` shadows the builtin `bot-detection` pattern). Guides carry `updated` date and `currentDate` timestamp in output.
224
216
  - **Learn mode toggle**: `/web learn` enables `web-learn` tool; `/web on` removes it. Agent never calls `web-learn` unprompted. Default is off on fresh sessions.
225
217
  - **Navigation settle** (`core/shared/nav-settle.ts`): after click or press, detects page navigation via a `framenavigated` listener and waits for `load + networkidle` (capped, errors swallowed) before reading URL/title/snapshot. Replaces the old fixed `waitForTimeout(300)` pattern that caused URL/DOM mismatches. Framework-agnostic via a lightweight `NavigationSettlePage` interface for testability.
226
218
  - **Stealth backends are user-managed, not shipped** — they live under `~/.pi/agent/pi-lean-portal/user-backends/`, are never in the npm tarball, and the extension never auto-downloads them. The user-side install burden is real: a per-engine venv, a ~100 MB patched-binary fetch, and an explicit `settings.json` entry with an **absolute** `pythonPath`. See `contributed/README.md` for the install flow.
@@ -61,7 +61,7 @@ The browser tools are **enabled by default**. You can:
61
61
 
62
62
  ## Extending it
63
63
 
64
- Beyond the toggle, two surfaces are user-extensible rather than hardcoded:
64
+ Beyond the toggle, three surfaces are extensible or shared rather than hardcoded:
65
65
 
66
66
  - **Navigation guides** — `web-learn` saves site-specific playbooks that
67
67
  auto-match by domain and resurface in later sessions. See
@@ -71,6 +71,14 @@ Beyond the toggle, two surfaces are user-extensible rather than hardcoded:
71
71
  and drive a patched engine like [Camoufox](https://github.com/daijro/camoufox)
72
72
  yourself. The full flow lives in [Backend Architecture](#backend-architecture)
73
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.
74
82
 
75
83
  ---
76
84
 
@@ -265,6 +273,28 @@ HTML to Markdown. It does **not** create a browser session.
265
273
  | `pagination` | On-demand | Next buttons, infinite scroll, pages |
266
274
  | `search` | On-demand | Search boxes, comboboxes, result lists |
267
275
 
276
+ ### Overriding Built-in Guides
277
+
278
+ A same-named `.md` file in `~/.pi/agent/pi-lean-portal/web-guides/` (e.g.
279
+ `bot-detection.md`) **shadows the builtin entirely** — the whole guide is
280
+ replaced, not field-merged. To keep a pattern guide firing, include
281
+ `trigger.signal: botDetected` (or `dialogDetected`) in the frontmatter;
282
+ omitting it disables the trigger.
283
+
284
+ ```yaml
285
+ ---
286
+ category: pattern
287
+ trigger.signal: botDetected
288
+ icon: 🤖
289
+ shortName: my bot guide
290
+ ---
291
+ Custom guidance text for bot challenges.
292
+ ```
293
+
294
+ Site guides and pattern guides live in **disjoint namespaces** — a site guide
295
+ for `www.botdetection.com` does not collide with the `bot-detection` pattern
296
+ guide; both fire when applicable.
297
+
268
298
  ### Viewing Guides
269
299
 
270
300
  ```text
@@ -10,7 +10,8 @@
10
10
 
11
11
  import { describe, it, expect, beforeEach, vi, type Mock } from "vitest";
12
12
  import { existsSync, readdirSync, statSync } from "node:fs";
13
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
+ import { EventEmitter } from "node:events";
14
+ import type { ExtensionAPI, EventBus } from "@earendil-works/pi-coding-agent";
14
15
 
15
16
  // Mock node:fs to control the filesystem for profile tests
16
17
  vi.mock("node:fs", () => ({
@@ -30,6 +31,7 @@ import { listProfiles, formatProfileList } from "../browser-profile.js";
30
31
  // ─── Helpers ─────────────────────────────────────────────────────
31
32
 
32
33
  function mockPi(tools?: string[]): ExtensionAPI {
34
+ const emitter = new EventEmitter();
33
35
  const allTools = (tools ?? []).map((name) => ({ name }));
34
36
  // Return a full active set by default
35
37
  const active = tools ?? [];
@@ -40,6 +42,15 @@ function mockPi(tools?: string[]): ExtensionAPI {
40
42
  appendEntry: vi.fn(),
41
43
  registerCommand: vi.fn(),
42
44
  on: vi.fn(),
45
+ get events() {
46
+ return {
47
+ emit: (channel: string, data: unknown) => emitter.emit(channel, data),
48
+ on: (channel: string, handler: (data: unknown) => void) => {
49
+ emitter.on(channel, handler);
50
+ return () => emitter.off(channel, handler);
51
+ },
52
+ } as EventBus;
53
+ },
43
54
  } as unknown as ExtensionAPI;
44
55
  }
45
56