pi-lean-dimension 0.3.2 → 0.4.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 (31) hide show
  1. package/README.md +18 -3
  2. package/node_modules/pi-lean-portal/README.md +21 -6
  3. package/node_modules/pi-lean-portal/__tests__/browser-toggle.test.ts +24 -0
  4. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-312.pyc +0 -0
  5. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-313.pyc +0 -0
  6. package/node_modules/pi-lean-portal/browser-toggle.ts +23 -26
  7. package/node_modules/pi-lean-portal/package.json +2 -2
  8. package/node_modules/pi-lean-search/__tests__/web-search.test.ts +22 -3
  9. package/node_modules/pi-lean-search/index.ts +11 -1
  10. package/node_modules/pi-lean-search/package.json +2 -2
  11. package/node_modules/pi-tool-masking/LICENSE +21 -661
  12. package/node_modules/pi-tool-masking/README.md +104 -14
  13. package/node_modules/pi-tool-masking/index.ts +767 -43
  14. package/node_modules/pi-tool-masking/package.json +6 -4
  15. package/node_modules/playwright/lib/common/index.js +3 -6
  16. package/node_modules/playwright/lib/common/index.js.txt +2 -2
  17. package/node_modules/playwright/lib/transform/esmLoader.js +3 -6
  18. package/node_modules/playwright/lib/transform/esmLoader.js.txt +2 -2
  19. package/node_modules/playwright/package.json +2 -2
  20. package/node_modules/playwright-core/lib/coreBundle.js +1 -1
  21. package/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-By56iMx7.js → codeMirrorModule-rXmQmLUY.js} +1 -1
  22. package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-B-dXF5JN.js +181 -0
  23. package/node_modules/playwright-core/lib/vite/traceViewer/assets/{xtermModule-COQkjINf.js → xtermModule-BuZfJS5v.js} +1 -1
  24. package/node_modules/playwright-core/lib/vite/traceViewer/{index.Dl36UVQT.js → index.KZ4wOW1K.js} +1 -1
  25. package/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  26. package/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.D962mr9b.js → uiMode.Dzuouizj.js} +2 -2
  27. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  28. package/node_modules/playwright-core/package.json +1 -1
  29. package/node_modules/playwright-core/types/structs.d.ts +8 -3
  30. package/package.json +3 -3
  31. package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-B34OrIms.js +0 -181
package/README.md CHANGED
@@ -21,13 +21,28 @@ npx playwright install chromium firefox
21
21
  That's it — the tools are registered and start enabled by default. Control
22
22
  them with `/web on|off|learn` (`on` = browser tools, `learn` = browser tools
23
23
  plus guide-saving via `web-learn`, `off` = everything off). The state persists
24
- per session. To set a different default for **new** sessions, add this to your
25
- Pi settings (`~/.pi/agent/settings.json` or `.pi/settings.json`):
24
+ per session. To set a different default for **new** sessions, add a
25
+ `toolsetDefaults` block to your Pi settings (`~/.pi/agent/settings.json` or
26
+ `.pi/settings.json`):
26
27
 
27
28
  ```json
28
- { "browserToggle": { "defaultEnabled": false } }
29
+ {
30
+ "toolsetDefaults": {
31
+ "toolset-state:pi-lean-dimension.web": { "enabled": false },
32
+ "toolset-state:pi-lean-dimension.web-learn": { "enabled": false },
33
+ "toolset-state:pi-lean-dimension.search": { "enabled": false }
34
+ }
35
+ }
29
36
  ```
30
37
 
38
+ `toolsetDefaults` is read by the [`pi-tool-masking`](https://github.com/coreyryanhanson/pi-tool-masking/) library on restore, before
39
+ the toolset's packaged default. Omit a key to use the packaged default; the
40
+ `search` key only applies when `pi-lean-search` is installed.
41
+
42
+ > The legacy `browserToggle.defaultEnabled` key is **removed in 0.4.0** and
43
+ > no longer read. If you pinned it, move the value into the matching
44
+ > `toolsetDefaults` entry above.
45
+
31
46
  ---
32
47
 
33
48
  ## Install options
@@ -115,8 +115,10 @@ toggles guide-saving mode, and manages browser profiles.
115
115
  ### Persistence
116
116
 
117
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`).
118
+ conversation starts with the default from `settings.json` — the
119
+ `pi-tool-masking` library's `toolsetDefaults` block (see
120
+ [Configuration (`settings.json`)](#configuration-settingsjson)), falling back
121
+ to the toolset's packaged default (`true` for `web`).
120
122
 
121
123
  ---
122
124
 
@@ -606,18 +608,31 @@ a `profile` parameter:
606
608
  }
607
609
  ```
608
610
 
609
- ### `browserToggle.defaultEnabled`
611
+ ### `toolsetDefaults` *(settings-based toolset defaults)*
610
612
 
611
- Whether browser tools are enabled on fresh conversations:
613
+ Whether browser tools are enabled on fresh conversations. Read by the
614
+ `pi-tool-masking` library at restore time, between the chat-branch tier and
615
+ the toolset's packaged default:
612
616
 
613
617
  ```jsonc
614
618
  {
615
- "browserToggle": {
616
- "defaultEnabled": true
619
+ "toolsetDefaults": {
620
+ "toolset-state:pi-lean-dimension.web": { "enabled": true },
621
+ "toolset-state:pi-lean-dimension.web-learn": { "enabled": false },
622
+ "toolset-state:pi-lean-dimension.search": { "enabled": true }
617
623
  }
618
624
  }
619
625
  ```
620
626
 
627
+ - Keys are the toolsets' `persistKey` values (`toolset-state:<id>`).
628
+ - Omit a `toolsetDefaults` key to use the toolset's packaged default (`web`
629
+ and `search` default `true`; `web-learn` defaults `false`).
630
+ - The `search` key only applies when `pi-lean-search` is installed.
631
+
632
+ > The legacy `browserToggle.defaultEnabled` key is **removed in 0.4.0** — it
633
+ > is no longer read. If you pinned it, move the value into the matching
634
+ > `toolsetDefaults` entry above.
635
+
621
636
  ### `browser.maxStorageStateSize`
622
637
 
623
638
  Size threshold for profile state warnings (default: 10 MB):
@@ -371,6 +371,30 @@ describe("/web focus-mode guard", () => {
371
371
  expect(finalActive).toContain(name);
372
372
  }
373
373
  });
374
+
375
+ // Allowlist focus (an upstream pi-tool-masking consumer) holds the line
376
+ // the same way inclusion does. The published library type doesn't name
377
+ // "allowlist", so we set the shared module state directly — mirroring what
378
+ // an allowlist-capable consumer's restore writes into globalThis.
379
+ it("refuses /web on/off/learn while allowlist focus is active", async () => {
380
+ const { pi } = mockPi([]);
381
+ browserToggle(pi);
382
+ (globalThis as any)[MODULE_STATE_KEY] = {
383
+ defaultResolutionMode: "allowlist",
384
+ };
385
+
386
+ for (const sub of ["on", "off", "learn"]) {
387
+ (pi.setActiveTools as any).mockClear();
388
+ const ctx = mockCtx();
389
+ await captureWebHandler(pi)(sub, ctx);
390
+
391
+ expect(ctx.ui.notify).toHaveBeenCalledWith(
392
+ expect.stringContaining("Focus mode (allowlist) is active"),
393
+ "warning",
394
+ );
395
+ expect(pi.setActiveTools).not.toHaveBeenCalled();
396
+ }
397
+ });
374
398
  });
375
399
 
376
400
  // ==================================================================
@@ -1,6 +1,5 @@
1
1
  import type {
2
2
  ExtensionAPI,
3
- ExtensionCommandContext,
4
3
  ExtensionContext,
5
4
  ThemeColor,
6
5
  } from "@earendil-works/pi-coding-agent";
@@ -10,7 +9,14 @@ import {
10
9
  getDefaultResolutionMode,
11
10
  } from "pi-tool-masking";
12
11
  import type { ToolsetSpec } from "pi-tool-masking";
13
- import { readMergedSettings } from "./core/shared/settings-reader.js";
12
+
13
+ // Focus-mode guard: refuse actuating subcommands while the library holds the
14
+ // line — inclusion mode or allowlist focus (an upstream pi-tool-masking
15
+ // consumer).
16
+ function isFocusHolding(): boolean {
17
+ const mode = getDefaultResolutionMode();
18
+ return mode === "inclusion" || mode === "allowlist";
19
+ }
14
20
 
15
21
  // ---- Toolset specs -----------------------------------------------
16
22
 
@@ -139,21 +145,10 @@ function renderBrowserGlyph(
139
145
  // ---- Toggle initializer ------------------------------------------
140
146
 
141
147
  export default function initBrowserToggle(pi: ExtensionAPI) {
142
- const merged = readMergedSettings();
143
- const browserToggleSegment = (merged as Record<string, unknown>)[
144
- "browserToggle"
145
- ] as Record<string, unknown> | undefined;
146
- const webDefault =
147
- browserToggleSegment &&
148
- typeof browserToggleSegment["defaultEnabled"] === "boolean"
149
- ? (browserToggleSegment["defaultEnabled"] as boolean)
150
- : true;
151
-
152
- const webSpec: ToolsetSpec = {
153
- ...PORTAL_WEB_SPEC,
154
- defaultEnabled: webDefault,
155
- };
156
- const webToolset = defineToolset(pi, webSpec);
148
+ // Settings-based toolset defaults (`toolsetDefaults` tier) are read by
149
+ // pi-tool-masking itself inside defineToolset/restore — pass the packaged
150
+ // spec straight through.
151
+ const webToolset = defineToolset(pi, PORTAL_WEB_SPEC);
157
152
  const learnToolset = defineToolset(pi, PORTAL_LEARN_SPEC);
158
153
 
159
154
  // ── Keep cached state in sync with library events ─────────
@@ -180,16 +175,18 @@ export default function initBrowserToggle(pi: ExtensionAPI) {
180
175
  const cmd = args.trim().toLowerCase();
181
176
 
182
177
  // Focus-mode guard (§13.2): refuse actuating subcommands while the
183
- // library holds the line in inclusion mode, so a sibling toggle
184
- // can't write a focus-indistinguishable {enabled} entry. Read-only
185
- // subcommands (status/profile/cookies/bare /web) stay unguarded,
186
- // matching tbox's treatment of its own read-only commands.
187
- if (
188
- ["on", "off", "learn"].includes(cmd) &&
189
- getDefaultResolutionMode() === "inclusion"
190
- ) {
178
+ // library holds the line — either inclusion mode or allowlist focus
179
+ // (an upstream pi-tool-masking consumer). Either way a sibling
180
+ // toggle must not write a focus-indistinguishable {enabled} entry.
181
+ // Read-only subcommands (status/profile/cookies/bare /web) stay
182
+ // unguarded, matching the focus controller's treatment of its own
183
+ // read-only commands.
184
+ if (["on", "off", "learn"].includes(cmd) && isFocusHolding()) {
185
+ const inInclusion = getDefaultResolutionMode() === "inclusion";
191
186
  ctx.ui.notify(
192
- "Another plugin has active inclusion mode — this toolset can't be toggled while inclusion is holding the line. Deactivate it there first.",
187
+ inInclusion
188
+ ? "Another plugin has active inclusion mode — this toolset can't be toggled while inclusion is holding the line. Deactivate it there first."
189
+ : "Focus mode (allowlist) is active — this toolset can't be toggled while focus is holding the line. Exit focus there first.",
193
190
  "warning",
194
191
  );
195
192
  return;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-lean-portal",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Pi extension. Interactive web browsing for Pi — a /web toggle removes the tools from context when switched off, Playwright Chromium/Firefox deliver accessibility-tree snapshots, persistent profiles, cookies, and domain-matched guides; custom/stealth backends (Camoufox) plug in when a site blocks the shipped browsers.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "dependencies": {
58
58
  "node-html-parser": "^6.1.0",
59
- "pi-tool-masking": "^1.0.0",
59
+ "pi-tool-masking": "^1.2.0",
60
60
  "playwright": "^1.60.0",
61
61
  "turndown": "^7.2.0"
62
62
  },
@@ -80,9 +80,7 @@ describe("readSearxngUrl", () => {
80
80
  vi.mocked(existsSync).mockImplementation(
81
81
  (path) => typeof path === "string" && path.includes(".pi"),
82
82
  );
83
- vi.mocked(readFileSync).mockReturnValue(
84
- JSON.stringify({ theme: "dark", browserToggle: {} }),
85
- );
83
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify({ theme: "dark" }));
86
84
  expect(readSearxngUrl()).toBeUndefined();
87
85
  });
88
86
 
@@ -444,6 +442,7 @@ describe("execute answer rendering", () => {
444
442
  beforeEach(() => {
445
443
  _resetStateForTest();
446
444
  delete (globalThis as any)[REGISTRY_KEY];
445
+ delete (globalThis as any)["__piToolMaskingModuleState"];
447
446
  });
448
447
 
449
448
  // ==================================================================
@@ -477,4 +476,24 @@ describe("pi-lean-dimension.web co-activation mirror", () => {
477
476
  expect.arrayContaining(["web-search"]),
478
477
  );
479
478
  });
479
+
480
+ // Allowlist focus (an upstream pi-tool-masking consumer) holds the line —
481
+ // the mirror must not co-activate, so a stale library `doRestore` emitting
482
+ // a web `changed` during resume can't disable search or write a {enabled}
483
+ // entry. Set the shared module state directly.
484
+ it("skips co-activation while allowlist focus is active", async () => {
485
+ const { pi, events } = mockSearchPi(["web-search"]);
486
+ searchExtension(pi);
487
+ (globalThis as any)["__piToolMaskingModuleState"] = {
488
+ defaultResolutionMode: "allowlist",
489
+ };
490
+
491
+ (pi.setActiveTools as any).mockClear();
492
+ events.emit(TOOLSET_EVENTS.changed, {
493
+ id: "pi-lean-dimension.web",
494
+ enabled: false,
495
+ });
496
+
497
+ expect(pi.setActiveTools).not.toHaveBeenCalled();
498
+ });
480
499
  });
@@ -16,7 +16,11 @@ import type {
16
16
  ExtensionContext,
17
17
  ThemeColor,
18
18
  } from "@earendil-works/pi-coding-agent";
19
- import { defineToolset, TOOLSET_EVENTS } from "pi-tool-masking";
19
+ import {
20
+ defineToolset,
21
+ TOOLSET_EVENTS,
22
+ getDefaultResolutionMode,
23
+ } from "pi-tool-masking";
20
24
  import type { ToolsetSpec, ToolsetChangedEvent } from "pi-tool-masking";
21
25
  import { readSearxngUrl } from "./search-config.js";
22
26
  import { webSearchTool } from "./web-search-tool.js";
@@ -199,9 +203,15 @@ export default function (pi: ExtensionAPI) {
199
203
 
200
204
  // ── Co-activation: mirror pi-lean-dimension.web changed events ─
201
205
  // Listen on changed ONLY, not restored (§10.1).
206
+ //
207
+ // Focus-mode guard: while allowlist focus holds the line, skip
208
+ // co-activation. The focus set is authoritative, so a web `changed` event
209
+ // — including one a stale library `doRestore` emits during resume — must
210
+ // not disable search or write a focus-indistinguishable {enabled} entry.
202
211
  pi.events.on(TOOLSET_EVENTS.changed, (data: unknown) => {
203
212
  const event = data as ToolsetChangedEvent;
204
213
  if (event.id === "pi-lean-dimension.web") {
214
+ if (getDefaultResolutionMode() === "allowlist") return;
205
215
  if (event.enabled) {
206
216
  searchToolset.enable(pi);
207
217
  } else {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-lean-search",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Pi extension. SearXNG search tool for Pi — pairs with pi-lean-portal's /web toggle; search-only installs are valid, or add it to a portal install for the full web-tools suite.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -38,7 +38,7 @@
38
38
  "test": "vitest run"
39
39
  },
40
40
  "dependencies": {
41
- "pi-tool-masking": "^1.0.0"
41
+ "pi-tool-masking": "^1.2.0"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@earendil-works/pi-ai": "*",