@narumitw/pi-chrome-devtools 0.53.2 → 0.53.4

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 (40) hide show
  1. package/README.md +1 -0
  2. package/dist/chunks/{browser-settings-menu-IMB6HXSU.js → browser-settings-menu-FVMCCUPM.ts} +16 -42
  3. package/dist/chunks/browser-settings-menu-FVMCCUPM.ts.map +7 -0
  4. package/dist/chunks/{chunk-GTTYS5GT.js → chunk-4B3EQHOH.ts} +2 -2
  5. package/dist/chunks/chunk-4B3EQHOH.ts.map +7 -0
  6. package/dist/chunks/{chunk-SRT3UUIP.js → chunk-LSJSDY2M.ts} +23 -74
  7. package/dist/chunks/chunk-LSJSDY2M.ts.map +7 -0
  8. package/dist/chunks/{chunk-LLXOCRNF.js → chunk-V7N3WI5B.ts} +12 -33
  9. package/dist/chunks/chunk-V7N3WI5B.ts.map +7 -0
  10. package/dist/chunks/{menu-UJETYMSB.js → menu-MTPSLDHZ.ts} +10 -23
  11. package/dist/chunks/menu-MTPSLDHZ.ts.map +7 -0
  12. package/dist/chunks/{tools-6P7EUX37.js → tools-OVNH45IJ.ts} +17 -59
  13. package/dist/chunks/tools-OVNH45IJ.ts.map +7 -0
  14. package/dist/index.ts +18 -42
  15. package/dist/index.ts.map +2 -2
  16. package/package.json +54 -55
  17. package/src/browser-manager.ts +872 -942
  18. package/src/browser-settings-menu.ts +315 -350
  19. package/src/cdp-client.ts +381 -398
  20. package/src/chrome-devtools.ts +205 -232
  21. package/src/display.ts +26 -29
  22. package/src/lazy-tools.ts +158 -181
  23. package/src/menu.ts +409 -446
  24. package/src/render.ts +72 -81
  25. package/src/runtime.ts +137 -140
  26. package/src/screenshot.ts +170 -186
  27. package/src/settings.ts +472 -523
  28. package/src/tool-names.ts +8 -14
  29. package/src/tool-selector.ts +242 -271
  30. package/src/tools.ts +218 -226
  31. package/src/webmcp/discovery.ts +184 -188
  32. package/src/webmcp/policy.ts +296 -329
  33. package/src/webmcp/protocol.ts +324 -337
  34. package/src/webmcp/tools.ts +136 -147
  35. package/dist/chunks/browser-settings-menu-IMB6HXSU.js.map +0 -7
  36. package/dist/chunks/chunk-GTTYS5GT.js.map +0 -7
  37. package/dist/chunks/chunk-LLXOCRNF.js.map +0 -7
  38. package/dist/chunks/chunk-SRT3UUIP.js.map +0 -7
  39. package/dist/chunks/menu-UJETYMSB.js.map +0 -7
  40. package/dist/chunks/tools-6P7EUX37.js.map +0 -7
package/src/lazy-tools.ts CHANGED
@@ -1,240 +1,217 @@
1
- import {
2
- defineTool,
3
- type ExtensionAPI,
4
- type ExtensionContext,
5
- } from "@earendil-works/pi-coding-agent";
1
+ import { defineTool, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
6
2
  import { Type } from "typebox";
7
3
  import { webMcpEnabled } from "./runtime.js";
8
- import {
9
- CHROME_DEVTOOLS_TOOL_NAMES,
10
- type ChromeDevToolsToolName,
11
- isWebMcpToolName,
12
- } from "./tool-names.js";
4
+ import { CHROME_DEVTOOLS_TOOL_NAMES, type ChromeDevToolsToolName, isWebMcpToolName } from "./tool-names.js";
13
5
 
14
6
  export const CHROME_DEVTOOLS_LOAD_TOOL_NAME = "chrome_devtools_load";
15
7
 
16
8
  const AVAILABLE_TOOLS_STORE = Symbol.for("@narumitw/pi-chrome-devtools.available-tools-store");
17
9
  type ChromeDevtoolsGlobal = typeof globalThis & {
18
- [AVAILABLE_TOOLS_STORE]?: WeakMap<ExtensionAPI, Set<ChromeDevToolsToolName>>;
10
+ [AVAILABLE_TOOLS_STORE]?: WeakMap<ExtensionAPI, Set<ChromeDevToolsToolName>>;
19
11
  };
20
12
  const sharedGlobal = globalThis as ChromeDevtoolsGlobal;
21
13
  const existingAvailableToolsStore = sharedGlobal[AVAILABLE_TOOLS_STORE];
22
- const availableToolsByApi =
23
- existingAvailableToolsStore ?? new WeakMap<ExtensionAPI, Set<ChromeDevToolsToolName>>();
14
+ const availableToolsByApi = existingAvailableToolsStore ?? new WeakMap<ExtensionAPI, Set<ChromeDevToolsToolName>>();
24
15
  if (!existingAvailableToolsStore) sharedGlobal[AVAILABLE_TOOLS_STORE] = availableToolsByApi;
25
16
  const lazyExposureByApi = new WeakMap<ExtensionAPI, boolean>();
26
17
  const sessionOwnerByApi = new WeakMap<ExtensionAPI, object>();
27
18
 
28
19
  const SEARCH_TEXT: Record<ChromeDevToolsToolName, string> = {
29
- chrome_devtools_list_pages: "list open inspectable chrome browser pages tabs targets",
30
- chrome_devtools_select_page: "select choose active chrome browser page tab target",
31
- chrome_devtools_navigate: "navigate open create chrome browser page url website",
32
- chrome_devtools_evaluate: "evaluate run javascript expression dom inspect chrome browser page",
33
- chrome_devtools_screenshot: "capture screenshot png image visual chrome browser page",
34
- chrome_devtools_webmcp_list_tools:
35
- "list discover page provided website webmcp tools capabilities experimental",
36
- chrome_devtools_webmcp_call_tool:
37
- "call invoke page provided website webmcp tool confirmation experimental",
20
+ chrome_devtools_list_pages: "list open inspectable chrome browser pages tabs targets",
21
+ chrome_devtools_select_page: "select choose active chrome browser page tab target",
22
+ chrome_devtools_navigate: "navigate open create chrome browser page url website",
23
+ chrome_devtools_evaluate: "evaluate run javascript expression dom inspect chrome browser page",
24
+ chrome_devtools_screenshot: "capture screenshot png image visual chrome browser page",
25
+ chrome_devtools_webmcp_list_tools: "list discover page provided website webmcp tools capabilities experimental",
26
+ chrome_devtools_webmcp_call_tool: "call invoke page provided website webmcp tool confirmation experimental",
38
27
  };
39
28
 
40
29
  export function setChromeDevtoolsSessionOwner(pi: ExtensionAPI, owner: object) {
41
- sessionOwnerByApi.set(pi, owner);
30
+ sessionOwnerByApi.set(pi, owner);
42
31
  }
43
32
 
44
33
  export function initializeAvailableChromeDevtoolsTools(pi: ExtensionAPI) {
45
- if (availableToolsByApi.has(pi)) return;
46
- const activeTools = new Set(pi.getActiveTools());
47
- setAvailableTools(
48
- pi,
49
- CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => activeTools.has(name)),
50
- );
34
+ if (availableToolsByApi.has(pi)) return;
35
+ const activeTools = new Set(pi.getActiveTools());
36
+ setAvailableTools(
37
+ pi,
38
+ CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => activeTools.has(name)),
39
+ );
51
40
  }
52
41
 
53
42
  export function configureChromeDevtoolsToolExposure(
54
- pi: ExtensionAPI,
55
- availableTools: readonly ChromeDevToolsToolName[],
56
- model?: ExtensionContext["model"],
43
+ pi: ExtensionAPI,
44
+ availableTools: readonly ChromeDevToolsToolName[],
45
+ model?: ExtensionContext["model"],
57
46
  ) {
58
- setAvailableTools(pi, availableTools);
59
- const available = effectiveAvailableTools(pi);
60
- const lazyExposure = supportsNativeDeferredToolLoading(model);
61
- lazyExposureByApi.set(pi, lazyExposure);
62
- const exposedTools = lazyExposure
63
- ? []
64
- : CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
65
- const nonCapabilityTools = pi
66
- .getActiveTools()
67
- .filter((name) => !CHROME_DEVTOOLS_TOOL_NAMES.includes(name as ChromeDevToolsToolName));
68
- pi.setActiveTools(
69
- unique([...nonCapabilityTools, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...exposedTools]),
70
- );
47
+ setAvailableTools(pi, availableTools);
48
+ const available = effectiveAvailableTools(pi);
49
+ const lazyExposure = supportsNativeDeferredToolLoading(model);
50
+ lazyExposureByApi.set(pi, lazyExposure);
51
+ const exposedTools = lazyExposure ? [] : CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
52
+ const nonCapabilityTools = pi
53
+ .getActiveTools()
54
+ .filter((name) => !CHROME_DEVTOOLS_TOOL_NAMES.includes(name as ChromeDevToolsToolName));
55
+ pi.setActiveTools(unique([...nonCapabilityTools, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...exposedTools]));
71
56
  }
72
57
 
73
58
  export function requireEagerChromeDevtoolsToolExposure(pi: ExtensionAPI) {
74
- lazyExposureByApi.set(pi, false);
75
- const active = pi.getActiveTools();
76
- const available = availableChromeDevtoolsTools(pi);
77
- pi.setActiveTools(unique([...active, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...available]));
78
- }
79
-
80
- export function applyAvailableChromeDevtoolsTools(
81
- pi: ExtensionAPI,
82
- availableTools: readonly ChromeDevToolsToolName[],
83
- ) {
84
- setAvailableTools(pi, availableTools);
85
- const available = effectiveAvailableTools(pi);
86
- const lazyExposure = lazyExposureByApi.get(pi) === true;
87
- const active = pi
88
- .getActiveTools()
89
- .filter(
90
- (name) =>
91
- !CHROME_DEVTOOLS_TOOL_NAMES.includes(name as ChromeDevToolsToolName) ||
92
- (lazyExposure && available.has(name as ChromeDevToolsToolName)),
93
- );
94
- const eagerTools = lazyExposure
95
- ? []
96
- : CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
97
- pi.setActiveTools(unique([...active, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...eagerTools]));
59
+ lazyExposureByApi.set(pi, false);
60
+ const active = pi.getActiveTools();
61
+ const available = availableChromeDevtoolsTools(pi);
62
+ pi.setActiveTools(unique([...active, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...available]));
63
+ }
64
+
65
+ export function applyAvailableChromeDevtoolsTools(pi: ExtensionAPI, availableTools: readonly ChromeDevToolsToolName[]) {
66
+ setAvailableTools(pi, availableTools);
67
+ const available = effectiveAvailableTools(pi);
68
+ const lazyExposure = lazyExposureByApi.get(pi) === true;
69
+ const active = pi
70
+ .getActiveTools()
71
+ .filter(
72
+ (name) =>
73
+ !CHROME_DEVTOOLS_TOOL_NAMES.includes(name as ChromeDevToolsToolName) ||
74
+ (lazyExposure && available.has(name as ChromeDevToolsToolName)),
75
+ );
76
+ const eagerTools = lazyExposure ? [] : CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
77
+ pi.setActiveTools(unique([...active, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...eagerTools]));
98
78
  }
99
79
 
100
80
  export function chromeDevtoolsToolExposureMode(pi: ExtensionAPI) {
101
- return lazyExposureByApi.get(pi) === true ? "native deferred" : "eager";
81
+ return lazyExposureByApi.get(pi) === true ? "native deferred" : "eager";
102
82
  }
103
83
 
104
84
  export function supportsNativeDeferredToolLoading(model: ExtensionContext["model"]): boolean {
105
- if (!model) return false;
106
- if (model.api === "anthropic-messages") {
107
- const configured = compatBoolean(model.compat, "supportsToolReferences");
108
- if (configured !== undefined) return configured;
109
- if (model.provider !== "anthropic" || model.id.includes("haiku")) return false;
110
- const version = model.id.match(/^claude-(?:opus|sonnet|fable)-(\d+)(?:-(\d+))?(?:-|$)/);
111
- if (!version) return false;
112
- const major = Number(version[1]);
113
- const minor = version[2] && version[2].length < 8 ? Number(version[2]) : 0;
114
- return major > 4 || (major === 4 && minor >= 5);
115
- }
116
- if (model.api === "openai-completions") {
117
- return compatString(model.compat, "deferredToolsMode") === "kimi";
118
- }
119
- if (model.api === "openai-responses" || model.api === "openai-codex-responses") {
120
- return (
121
- compatBoolean(model.compat, "supportsAdditionalTools") === true ||
122
- compatBoolean(model.compat, "supportsToolSearch") === true
123
- );
124
- }
125
- return false;
85
+ if (!model) return false;
86
+ if (model.api === "anthropic-messages") {
87
+ // Fireworks Messages requires the canonical ToolSearch/tool_search loader name.
88
+ // chrome_devtools_load stays package-specific so independently installed loaders cannot collide.
89
+ if (model.provider === "fireworks") return false;
90
+ const configured = compatBoolean(model.compat, "supportsToolReferences");
91
+ if (configured !== undefined) return configured;
92
+ if (model.provider !== "anthropic" || model.id.includes("haiku")) return false;
93
+ const version = model.id.match(/^claude-(?:opus|sonnet|fable)-(\d+)(?:-(\d+))?(?:-|$)/);
94
+ if (!version) return false;
95
+ const major = Number(version[1]);
96
+ const minor = version[2] && version[2].length < 8 ? Number(version[2]) : 0;
97
+ return major > 4 || (major === 4 && minor >= 5);
98
+ }
99
+ if (model.api === "openai-completions") {
100
+ return compatString(model.compat, "deferredToolsMode") === "kimi";
101
+ }
102
+ if (model.api === "openai-responses" || model.api === "openai-codex-responses") {
103
+ return (
104
+ compatBoolean(model.compat, "supportsAdditionalTools") === true ||
105
+ compatBoolean(model.compat, "supportsToolSearch") === true
106
+ );
107
+ }
108
+ return false;
126
109
  }
127
110
 
128
111
  export function availableChromeDevtoolsTools(pi: ExtensionAPI) {
129
- const available = effectiveAvailableTools(pi);
130
- return CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
112
+ const available = effectiveAvailableTools(pi);
113
+ return CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
131
114
  }
132
115
 
133
116
  export function configuredChromeDevtoolsTools(pi: ExtensionAPI) {
134
- const available = availableToolsByApi.get(pi) ?? new Set();
135
- return CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
117
+ const available = availableToolsByApi.get(pi) ?? new Set();
118
+ return CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
136
119
  }
137
120
 
138
121
  export function createChromeDevtoolsLoadTool(pi: ExtensionAPI) {
139
- return defineTool({
140
- name: CHROME_DEVTOOLS_LOAD_TOOL_NAME,
141
- label: "Chrome DevTools: Load Tools",
142
- description:
143
- "Find and enable Chrome DevTools browser tools relevant to a task. Loaded tools remain available for the session.",
144
- promptSnippet: "Load Chrome DevTools browser capabilities on demand",
145
- promptGuidelines: [
146
- "Use chrome_devtools_load when a task requires inspecting or controlling a Chrome browser and the needed chrome_devtools_* capability is not active.",
147
- ],
148
- parameters: Type.Object({
149
- query: Type.String({
150
- description: "Browser capability or task to find tools for.",
151
- maxLength: 500,
152
- }),
153
- limit: Type.Optional(
154
- Type.Integer({
155
- description: "Maximum tools to load. Defaults to 3.",
156
- minimum: 1,
157
- maximum: 5,
158
- }),
159
- ),
160
- }),
161
- async execute(_toolCallId, params, signal, _onUpdate, ctx) {
162
- signal?.throwIfAborted();
163
- setChromeDevtoolsSessionOwner(pi, ctx.sessionManager);
164
- const available = new Set(availableChromeDevtoolsTools(pi));
165
- const matches = matchChromeDevtoolsTools(params.query, params.limit ?? 3, available);
166
- const active = pi.getActiveTools();
167
- const activeSet = new Set(active);
168
- const added = matches.filter((name) => !activeSet.has(name));
169
- if (added.length > 0) {
170
- pi.setActiveTools(unique([...active, ...added]));
171
- }
172
-
173
- const text =
174
- matches.length === 0
175
- ? "No available Chrome DevTools tools matched the query."
176
- : added.length > 0
177
- ? `Loaded Chrome DevTools tools: ${added.join(", ")}`
178
- : `Matching Chrome DevTools tools are already loaded: ${matches.join(", ")}`;
179
- return {
180
- content: [{ type: "text" as const, text }],
181
- details: { matches, added },
182
- };
183
- },
184
- });
185
- }
186
-
187
- function matchChromeDevtoolsTools(
188
- query: string,
189
- limit: number,
190
- available: ReadonlySet<ChromeDevToolsToolName>,
191
- ) {
192
- const terms = query
193
- .toLowerCase()
194
- .split(/[^a-z0-9]+/)
195
- .filter((term) => term.length >= 2);
196
- if (terms.length === 0) return [];
197
- return CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name))
198
- .map((name, index) => ({
199
- name,
200
- index,
201
- score: terms.reduce((score, term) => score + (SEARCH_TEXT[name].includes(term) ? 1 : 0), 0),
202
- }))
203
- .filter((match) => match.score > 0)
204
- .sort((left, right) => right.score - left.score || left.index - right.index)
205
- .slice(0, limit)
206
- .map((match) => match.name);
122
+ return defineTool({
123
+ name: CHROME_DEVTOOLS_LOAD_TOOL_NAME,
124
+ label: "Chrome DevTools: Load Tools",
125
+ description:
126
+ "Find and enable Chrome DevTools browser tools relevant to a task. Loaded tools remain available for the session.",
127
+ promptSnippet: "Load Chrome DevTools browser capabilities on demand",
128
+ promptGuidelines: [
129
+ "Use chrome_devtools_load when a task requires inspecting or controlling a Chrome browser and the needed chrome_devtools_* capability is not active.",
130
+ ],
131
+ parameters: Type.Object({
132
+ query: Type.String({
133
+ description: "Browser capability or task to find tools for.",
134
+ maxLength: 500,
135
+ }),
136
+ limit: Type.Optional(
137
+ Type.Integer({
138
+ description: "Maximum tools to load. Defaults to 3.",
139
+ minimum: 1,
140
+ maximum: 5,
141
+ }),
142
+ ),
143
+ }),
144
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
145
+ signal?.throwIfAborted();
146
+ setChromeDevtoolsSessionOwner(pi, ctx.sessionManager);
147
+ const available = new Set(availableChromeDevtoolsTools(pi));
148
+ const matches = matchChromeDevtoolsTools(params.query, params.limit ?? 3, available);
149
+ const active = pi.getActiveTools();
150
+ const activeSet = new Set(active);
151
+ const added = matches.filter((name) => !activeSet.has(name));
152
+ if (added.length > 0) {
153
+ pi.setActiveTools(unique([...active, ...added]));
154
+ }
155
+
156
+ const text =
157
+ matches.length === 0
158
+ ? "No available Chrome DevTools tools matched the query."
159
+ : added.length > 0
160
+ ? `Loaded Chrome DevTools tools: ${added.join(", ")}`
161
+ : `Matching Chrome DevTools tools are already loaded: ${matches.join(", ")}`;
162
+ return {
163
+ content: [{ type: "text" as const, text }],
164
+ details: { matches, added },
165
+ };
166
+ },
167
+ });
168
+ }
169
+
170
+ function matchChromeDevtoolsTools(query: string, limit: number, available: ReadonlySet<ChromeDevToolsToolName>) {
171
+ const terms = query
172
+ .toLowerCase()
173
+ .split(/[^a-z0-9]+/)
174
+ .filter((term) => term.length >= 2);
175
+ if (terms.length === 0) return [];
176
+ return CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name))
177
+ .map((name, index) => ({
178
+ name,
179
+ index,
180
+ score: terms.reduce((score, term) => score + (SEARCH_TEXT[name].includes(term) ? 1 : 0), 0),
181
+ }))
182
+ .filter((match) => match.score > 0)
183
+ .sort((left, right) => right.score - left.score || left.index - right.index)
184
+ .slice(0, limit)
185
+ .map((match) => match.name);
207
186
  }
208
187
 
209
188
  function setAvailableTools(pi: ExtensionAPI, availableTools: readonly ChromeDevToolsToolName[]) {
210
- const available = new Set(availableTools);
211
- availableToolsByApi.set(pi, available);
212
- return available;
189
+ const available = new Set(availableTools);
190
+ availableToolsByApi.set(pi, available);
191
+ return available;
213
192
  }
214
193
 
215
194
  function effectiveAvailableTools(pi: ExtensionAPI) {
216
- const configured = availableToolsByApi.get(pi) ?? new Set();
217
- const owner = sessionOwnerByApi.get(pi);
218
- const enabled = owner !== undefined && webMcpEnabled(owner);
219
- return new Set(
220
- CHROME_DEVTOOLS_TOOL_NAMES.filter(
221
- (name) => configured.has(name) && (enabled || !isWebMcpToolName(name)),
222
- ),
223
- );
195
+ const configured = availableToolsByApi.get(pi) ?? new Set();
196
+ const owner = sessionOwnerByApi.get(pi);
197
+ const enabled = owner !== undefined && webMcpEnabled(owner);
198
+ return new Set(
199
+ CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => configured.has(name) && (enabled || !isWebMcpToolName(name))),
200
+ );
224
201
  }
225
202
 
226
203
  function compatBoolean(value: unknown, key: string) {
227
- if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
228
- const record = value as Record<string, unknown>;
229
- return typeof record[key] === "boolean" ? record[key] : undefined;
204
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
205
+ const record = value as Record<string, unknown>;
206
+ return typeof record[key] === "boolean" ? record[key] : undefined;
230
207
  }
231
208
 
232
209
  function compatString(value: unknown, key: string) {
233
- if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
234
- const record = value as Record<string, unknown>;
235
- return typeof record[key] === "string" ? record[key] : undefined;
210
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
211
+ const record = value as Record<string, unknown>;
212
+ return typeof record[key] === "string" ? record[key] : undefined;
236
213
  }
237
214
 
238
215
  function unique(values: readonly string[]) {
239
- return [...new Set(values)];
216
+ return [...new Set(values)];
240
217
  }