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.
- package/README.md +18 -3
- package/node_modules/pi-lean-portal/README.md +21 -6
- package/node_modules/pi-lean-portal/__tests__/browser-toggle.test.ts +24 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-312.pyc +0 -0
- package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-313.pyc +0 -0
- package/node_modules/pi-lean-portal/browser-toggle.ts +23 -26
- package/node_modules/pi-lean-portal/package.json +2 -2
- package/node_modules/pi-lean-search/__tests__/web-search.test.ts +22 -3
- package/node_modules/pi-lean-search/index.ts +11 -1
- package/node_modules/pi-lean-search/package.json +2 -2
- package/node_modules/pi-tool-masking/LICENSE +21 -661
- package/node_modules/pi-tool-masking/README.md +104 -14
- package/node_modules/pi-tool-masking/index.ts +767 -43
- package/node_modules/pi-tool-masking/package.json +6 -4
- package/node_modules/playwright/lib/common/index.js +3 -6
- package/node_modules/playwright/lib/common/index.js.txt +2 -2
- package/node_modules/playwright/lib/transform/esmLoader.js +3 -6
- package/node_modules/playwright/lib/transform/esmLoader.js.txt +2 -2
- package/node_modules/playwright/package.json +2 -2
- package/node_modules/playwright-core/lib/coreBundle.js +1 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-By56iMx7.js → codeMirrorModule-rXmQmLUY.js} +1 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-B-dXF5JN.js +181 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/{xtermModule-COQkjINf.js → xtermModule-BuZfJS5v.js} +1 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/{index.Dl36UVQT.js → index.KZ4wOW1K.js} +1 -1
- package/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.D962mr9b.js → uiMode.Dzuouizj.js} +2 -2
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright-core/types/structs.d.ts +8 -3
- package/package.json +3 -3
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-B34OrIms.js +0 -181
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A library for pi plugin developers that groups tools into toggleable **toolsets** with persistent state and cross-extension events — eliminating the boilerplate every pi extension repeats when it wants to let users disable tools cleanly.
|
|
4
4
|
|
|
5
|
-
`pi-tool-masking` is **not** a pi extension itself. It is a
|
|
5
|
+
`pi-tool-masking` is **not** a pi extension itself. It is a dependency that your extension imports. It owns the toggle logic, the session-restore path, and the event bus. Your extension owns the commands, the status bar, and the user-facing surfaces.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -16,7 +16,7 @@ Without `pi-tool-masking`, every pi extension that toggles tools reimplements th
|
|
|
16
16
|
4. On `session_start` / `session_tree`, walk the branch for persisted entries and re-apply state.
|
|
17
17
|
5. Emit events so side-effect owners (status bars, pickers) can re-render.
|
|
18
18
|
|
|
19
|
-
`pi-tool-masking` does all of that in one call: `defineToolset(pi, spec)`. It also adds dependency cascading (enabling a toolset auto-enables its dependencies) and reverse cascading (disabling a toolset auto-disables dependents).
|
|
19
|
+
`pi-tool-masking` does all of that in one call: `defineToolset(pi, spec)`. It also adds dependency cascading (enabling a toolset auto-enables its dependencies) and reverse cascading (disabling a toolset auto-disables dependents), plus a settings tier for per-scope defaults and allowlist mode for reliable focus across reloads.
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -89,19 +89,24 @@ Register a toolset and receive a `Toolset` handle (`enable`, `disable`, `isEnabl
|
|
|
89
89
|
|
|
90
90
|
**Idempotent re-registration:** calling `defineToolset` with the same `spec.id` and an unchanged spec returns the existing toolset. This is safe across `/reload`.
|
|
91
91
|
|
|
92
|
-
### `setDefaultResolutionMode(pi, mode)`
|
|
92
|
+
### `setDefaultResolutionMode(pi, mode, allowlist?)`
|
|
93
93
|
|
|
94
|
-
Switch how toolsets with no persisted state resolve on restore.
|
|
94
|
+
Switch how toolsets with no persisted state resolve on restore. Three modes:
|
|
95
95
|
|
|
96
96
|
| Mode | Behavior on restore (no persisted entry) |
|
|
97
97
|
|---|---|
|
|
98
98
|
| `"exclusion"` (default) | Toolsets default **on** if `defaultEnabled` is true, **off** otherwise |
|
|
99
|
-
| `"inclusion"` | All unknown toolsets default **off** —
|
|
99
|
+
| `"inclusion"` (@deprecated since 1.2.0) | All unknown toolsets default **off** — a weaker, unbounded floor. Use `"allowlist"` instead for focus-style "only these tools" suppression |
|
|
100
|
+
| `"allowlist"` | Only the listed toolset ids are **on**, everything else **off** — a finite, branch-persisted set whose complement is computed at restore, resilient to toolsets installed later. Pass the array as the third argument: `setDefaultResolutionMode(pi, "allowlist", ["my-plugin.web"])` |
|
|
100
101
|
|
|
101
102
|
### `getDefaultResolutionMode()`
|
|
102
103
|
|
|
103
104
|
Read the current default resolution mode. Returns `"exclusion"` until a session restore loads the persisted mode.
|
|
104
105
|
|
|
106
|
+
### `getActiveAllowlist()`
|
|
107
|
+
|
|
108
|
+
Read the live allowlist array from module state. Returns the `string[]` when the active mode is `"allowlist"`, otherwise `undefined`. Parameterless (like `getDefaultResolutionMode`) — the downstream call site receives `ExtensionAPI`, which doesn't expose `sessionManager`, so the branch can't be read there. The branch remains the source of truth; module state is the live mirror, snapped from the last mode branch entry by `doRestore` (and by `setDefaultResolutionMode` when it appends the entry). A consumer consults this to keep toolsets registered *after* focus was entered off.
|
|
109
|
+
|
|
105
110
|
### `getRegisteredToolsets()`
|
|
106
111
|
|
|
107
112
|
Return a read-only snapshot of every registered toolset (`{ spec, toolset }`). No `pi` argument needed — pure registry read.
|
|
@@ -121,10 +126,59 @@ Return a read-only snapshot of every registered toolset (`{ spec, toolset }`). N
|
|
|
121
126
|
| `Toolset` | Handle returned by `defineToolset` |
|
|
122
127
|
| `ToolsetChangedEvent` | Shape of events emitted by `TOOLSET_EVENTS` |
|
|
123
128
|
| `RegistryEntry` | `{ spec: ToolsetSpec; toolset: Toolset }` — a single registered toolset |
|
|
124
|
-
| `DefaultResolutionMode` | `"exclusion" \| "inclusion"` |
|
|
129
|
+
| `DefaultResolutionMode` | `"exclusion" \| "inclusion" \| "allowlist"` |
|
|
130
|
+
| `MalformedSettingsError` | Thrown by `writeToolsetDefaults` / `clearToolsetDefaults` when settings.json is corrupt or non-object (never silently overwritten). Catch with `instanceof`. |
|
|
125
131
|
|
|
126
132
|
---
|
|
127
133
|
|
|
134
|
+
## Toolset defaults (settings tier)
|
|
135
|
+
|
|
136
|
+
A toolset's fresh-session default is no longer locked to its packaged `spec.defaultEnabled`. Users can pin `{ enabled: boolean }` under a reserved `toolsetDefaults` key in pi-core settings, keyed by the toolset's full `persistKey` — without toggling (which writes a session-scoped chat-branch entry). The library reads both files itself inside `doRestore`, fresh on each `/reload`, so downstream consumers no longer need to reinvent a settings reader to inject values into `spec.defaultEnabled` before `defineToolset`.
|
|
137
|
+
|
|
138
|
+
Restore resolves each toolset's default in this order (first hit wins):
|
|
139
|
+
|
|
140
|
+
1. **Chat-branch entry** — the last `appendEntry(persistKey, …)` on this branch. A `null` tombstone (see [`clearToolsetEntry`](#tombstone-helpers)) falls through to tier 2.
|
|
141
|
+
2. **Settings pin** — `toolsetDefaults[persistKey].enabled`, merged global → project (project wins per entry). Mode-agnostic.
|
|
142
|
+
3. **Packaged default** — `spec.defaultEnabled ?? true`, filtered by resolution mode for unpinned toolsets only.
|
|
143
|
+
|
|
144
|
+
Settings pins are honored in all three modes, mirroring how chat-branch entries are honored — only unpinned toolsets consult mode for the floor.
|
|
145
|
+
|
|
146
|
+
### `readMergedToolsetDefaults()`
|
|
147
|
+
|
|
148
|
+
Read and merge `toolsetDefaults` from global (`~/.pi/agent/settings.json`, or `$PI_CODING_AGENT_DIR/settings.json`) and project (`<cwd>/.pi/settings.json`) settings. Project overrides global per entry. Missing/unreadable/malformed files contribute `{}`. Never throws. Returns `Record<persistKey, { enabled: boolean }>`. Read once per loop and pass the snapshot to `getEffectiveDefault` to avoid re-reading disk per toolset.
|
|
149
|
+
|
|
150
|
+
### `readToolsetDefaults(scope)`
|
|
151
|
+
|
|
152
|
+
Read one scope's raw `toolsetDefaults` block (no merge). `scope` is `"global"` or `"project"`. Same never-throw policy. Use for `defaults show`-style commands that need per-scope attribution.
|
|
153
|
+
|
|
154
|
+
### `writeToolsetDefaults(entries, scope)`
|
|
155
|
+
|
|
156
|
+
Merge a batch of `{ [persistKey]: { enabled } }` entries into one scope's `toolsetDefaults`, preserving every other top-level key and every existing entry not in `entries`. A write where every entry already matches its on-disk value is a no-op (no reformat, no mtime bump). Returns the settings file path. **Throws `MalformedSettingsError`** if the file exists but parses to a non-object or is unparsable — a corrupt file is never silently overwritten.
|
|
157
|
+
|
|
158
|
+
### `clearToolsetDefaults(scope)`
|
|
159
|
+
|
|
160
|
+
Remove the `toolsetDefaults` wrapper key entirely from one scope, preserving every other top-level key. Returns the path removed from, or `null` if the key was already absent (or the file missing). No per-entry clear by design — write an `entries` map without the unwanted keys via `writeToolsetDefaults`. Same `MalformedSettingsError` guard.
|
|
161
|
+
|
|
162
|
+
### `getEffectiveDefault(spec, snapshot?)`
|
|
163
|
+
|
|
164
|
+
Resolve a toolset's effective fresh-session default: settings tier (2) then packaged `spec.defaultEnabled ?? true` (3). **Ignores resolution mode** — callers needing mode-aware behavior must consult `getDefaultResolutionMode()` themselves. Pass an explicit `snapshot` (from `readMergedToolsetDefaults()`) when looping over multiple toolsets; omit it for a one-off (it performs its own read).
|
|
165
|
+
|
|
166
|
+
## Tombstone helpers
|
|
167
|
+
|
|
168
|
+
Within pi-core's append-only `SessionManager`, a toolset's chat-branch entry can't be deleted — but a `null` tombstone appended after the last entry makes `doRestore` fall through to the settings tier so settings re-assert. Tombstones are dedup'd (no-op when the last entry is already cleared) and never written for never-toggled toolsets. A later manual toggle appends after the tombstone and supersedes it.
|
|
169
|
+
|
|
170
|
+
### `clearToolsetEntry(pi, persistKey, branch)`
|
|
171
|
+
|
|
172
|
+
Append a `null` tombstone for one toolset's branch entry (dedup'd). `branch` is the caller's `ctx.sessionManager.getBranch()` snapshot — `ExtensionAPI` exposes `appendEntry` but not `sessionManager`, so the dedup read comes from the caller.
|
|
173
|
+
|
|
174
|
+
### `clearAllToolsetEntries(pi, branch)`
|
|
175
|
+
|
|
176
|
+
Tombstone every registered toolset's branch entry (dedup'd per toolset). Covers exactly the toolsets in the global registry.
|
|
177
|
+
|
|
178
|
+
### `applyToolsetEnabled(pi, spec, enabled)`
|
|
179
|
+
|
|
180
|
+
Apply a toolset's enabled state via `setActiveTools` and emit `TOOLSET_EVENTS.changed` **without** writing a branch entry — the live-apply half of a settings restore (pull a toolset to its settings/packaged default without persisting a chat-branch pin).
|
|
181
|
+
|
|
128
182
|
## ToolsetSpec fields
|
|
129
183
|
|
|
130
184
|
```ts
|
|
@@ -206,15 +260,18 @@ pi.events.on(TOOLSET_EVENTS.restored, (event) => {
|
|
|
206
260
|
});
|
|
207
261
|
```
|
|
208
262
|
|
|
209
|
-
### Focus mode (
|
|
263
|
+
### Focus mode (allowlist resolution)
|
|
210
264
|
|
|
211
265
|
```ts
|
|
212
266
|
import { setDefaultResolutionMode, getRegisteredToolsets } from "pi-tool-masking";
|
|
213
267
|
|
|
214
|
-
// Enter focus:
|
|
215
|
-
|
|
268
|
+
// Enter focus: allowlist mode keeps only the listed toolsets on — restore
|
|
269
|
+
// applies it on the next /reload, and the loop below applies it live.
|
|
270
|
+
// "inclusion" (deprecated) cannot guarantee this: a toolset installed after
|
|
271
|
+
// focus leaks on, because the set of "on" toolsets was never recorded.
|
|
272
|
+
setDefaultResolutionMode(pi, "allowlist", ["my-plugin.web"]);
|
|
216
273
|
|
|
217
|
-
//
|
|
274
|
+
// Apply live: enable only the allowlisted toolsets
|
|
218
275
|
const allowlist = new Set(["my-plugin.web"]);
|
|
219
276
|
for (const entry of getRegisteredToolsets()) {
|
|
220
277
|
if (allowlist.has(entry.spec.id)) {
|
|
@@ -228,8 +285,8 @@ for (const entry of getRegisteredToolsets()) {
|
|
|
228
285
|
### Dependent toolsets
|
|
229
286
|
|
|
230
287
|
```ts
|
|
231
|
-
//
|
|
232
|
-
const
|
|
288
|
+
// Web tools are on by default; learn tools depend on web
|
|
289
|
+
const webSpec: ToolsetSpec = {
|
|
233
290
|
id: "my-plugin.web",
|
|
234
291
|
names: new Set(["web-fetch", "web-snapshot"]),
|
|
235
292
|
persistKey: "toolset-state:my-plugin.web",
|
|
@@ -247,11 +304,44 @@ const learnSpec: ToolsetSpec = {
|
|
|
247
304
|
|
|
248
305
|
---
|
|
249
306
|
|
|
307
|
+
## Toolset naming
|
|
308
|
+
|
|
309
|
+
`defineToolset` can't tell which extension is calling it — pi's `ExtensionAPI`
|
|
310
|
+
doesn't expose the caller — so error messages can't name the responsible
|
|
311
|
+
extension directly. The toolset id is the only traceability signal, which is
|
|
312
|
+
why a stable, attributable id convention matters.
|
|
313
|
+
|
|
314
|
+
### Convention (recommended, not enforced)
|
|
315
|
+
|
|
316
|
+
Prefix toolset ids with a stable namespace: `<product-family>.<subset>`, e.g.
|
|
317
|
+
`my-plugin.web`. The family may span multiple npm packages, and nothing checks
|
|
318
|
+
that the prefix matches a real package — it's for human traceability in
|
|
319
|
+
`/tbox list` and collision errors, not verification.
|
|
320
|
+
|
|
321
|
+
### Enforcement floor
|
|
322
|
+
|
|
323
|
+
`defineToolset` enforces one naming invariant: **no two toolsets may claim the
|
|
324
|
+
same tool name.** Overlap is essentially always an authoring mistake and throws
|
|
325
|
+
at load time:
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
[pi-tool-masking] name overlap: toolset "foo.search" claims tools already
|
|
329
|
+
owned by another toolset:
|
|
330
|
+
- tool "x" already claimed by toolset "bar.web" (registered from
|
|
331
|
+
/home/u/.pi/.../bar/index.ts, source: bar)
|
|
332
|
+
Each tool may belong to only one toolset. Naming convention: prefix toolset
|
|
333
|
+
ids with a stable namespace (<product-family>.<subset>, e.g. "foo.web").
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
250
338
|
## How it works (for the curious)
|
|
251
339
|
|
|
252
340
|
- **Registration:** `defineToolset` stores the spec and handle in a global registry (shared across module instances, so multiple extensions see the same toolsets).
|
|
253
341
|
- **Persistence:** each toolset writes `{ enabled }` entries under its `persistKey` on the session branch. On `session_start` or `session_tree`, the library re-reads the branch and applies the last persisted state.
|
|
254
|
-
- **Default resolution:** a
|
|
342
|
+
- **Default resolution:** a `toolset-resolution-mode` entry on the branch controls how toolsets with no persisted state resolve on restore — `exclusion` (on/off by `defaultEnabled`), `inclusion` (deprecated unbounded floor), or `allowlist` (a finite branch-persisted array whose complement is computed at restore). Set by `setDefaultResolutionMode`, persists across reloads.
|
|
343
|
+
- **Defaults tiers:** each toolset's restore default resolves chat-branch entry → `toolsetDefaults` settings pin → packaged `spec.defaultEnabled`, filtered by resolution mode for unpinned toolsets only. Settings pins are read fresh from disk on each restore.
|
|
344
|
+
- **Null-tombstone-aware restore:** a `null` last branch entry (written by `clearToolsetEntry`) falls through to the settings tier instead of any stale prior entry; mode resolution is likewise null-tombstone-aware (`branchMode ?? "exclusion"`). Tombstones aren't sticky — a later toggle supersedes them.
|
|
255
345
|
- **Events:** a live toggle emits only when state actually changes (no-op toggles are suppressed); restore always emits, so side-effect owners stay in sync across reloads and tree navigations.
|
|
256
346
|
|
|
257
347
|
---
|
|
@@ -267,4 +357,4 @@ This package is used by:
|
|
|
267
357
|
|
|
268
358
|
## License
|
|
269
359
|
|
|
270
|
-
|
|
360
|
+
MIT. See [LICENSE](./LICENSE).
|