@narumitw/pi-chrome-devtools 0.53.4 → 0.54.1

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/CHANGELOG.md +109 -0
  2. package/README.md +34 -16
  3. package/dist/chunks/{browser-settings-menu-FVMCCUPM.ts → browser-settings-menu-7M3VM7QN.ts} +25 -3
  4. package/dist/chunks/browser-settings-menu-7M3VM7QN.ts.map +7 -0
  5. package/dist/chunks/{chunk-V7N3WI5B.ts → chunk-JKPJYUVG.ts} +340 -108
  6. package/dist/chunks/chunk-JKPJYUVG.ts.map +7 -0
  7. package/dist/chunks/{chunk-4B3EQHOH.ts → chunk-JLEJ6WFO.ts} +2 -2
  8. package/dist/chunks/{chunk-LSJSDY2M.ts → chunk-WTMZID3G.ts} +382 -246
  9. package/dist/chunks/chunk-WTMZID3G.ts.map +7 -0
  10. package/dist/chunks/{menu-MTPSLDHZ.ts → menu-HGG26E3X.ts} +7 -7
  11. package/dist/chunks/{menu-MTPSLDHZ.ts.map → menu-HGG26E3X.ts.map} +1 -1
  12. package/dist/chunks/{tools-OVNH45IJ.ts → tools-O5N4KIAK.ts} +3 -3
  13. package/dist/index.ts +45 -21
  14. package/dist/index.ts.map +2 -2
  15. package/docs/browser-setup.md +7 -1
  16. package/package.json +6 -5
  17. package/src/browser-candidates.ts +184 -0
  18. package/src/browser-manager.ts +93 -276
  19. package/src/browser-settings-menu.ts +23 -0
  20. package/src/browser-startup.ts +122 -0
  21. package/src/chrome-devtools.ts +48 -15
  22. package/src/lazy-tools.ts +358 -48
  23. package/src/menu.ts +3 -3
  24. package/src/runtime.ts +2 -2
  25. package/src/settings.ts +65 -1
  26. package/src/tool-selector.ts +95 -40
  27. package/dist/chunks/browser-settings-menu-FVMCCUPM.ts.map +0 -7
  28. package/dist/chunks/chunk-LSJSDY2M.ts.map +0 -7
  29. package/dist/chunks/chunk-V7N3WI5B.ts.map +0 -7
  30. /package/dist/chunks/{chunk-4B3EQHOH.ts.map → chunk-JLEJ6WFO.ts.map} +0 -0
  31. /package/dist/chunks/{tools-OVNH45IJ.ts.map → tools-O5N4KIAK.ts.map} +0 -0
@@ -3,6 +3,7 @@
3
3
  import {
4
4
  CHROME_DEVTOOLS_TOOL_NAMES,
5
5
  CORE_CHROME_DEVTOOLS_TOOL_NAMES,
6
+ ToolCatalogApplicationError,
6
7
  browserCandidateHint,
7
8
  browserLifecycleState,
8
9
  browserSettingsForOwner,
@@ -20,10 +21,42 @@ import {
20
21
  settingsFilePath,
21
22
  state,
22
23
  webMcpEnabled
23
- } from "./chunk-LSJSDY2M.ts";
24
+ } from "./chunk-WTMZID3G.ts";
25
+
26
+ // src/display.ts
27
+ var MAX_SANITIZER_INPUT_CODE_UNITS = 5e4;
28
+ var graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
29
+ function sanitizeChromeDevtoolsDisplay(value, maxCharacters = 5e4) {
30
+ const inputWasTruncated = value.length > MAX_SANITIZER_INPUT_CODE_UNITS;
31
+ const boundedInput = inputWasTruncated ? truncateAtGraphemeBoundary(value, MAX_SANITIZER_INPUT_CODE_UNITS) : value;
32
+ const normalizedLineEndings = boundedInput.replace(/\r\n/g, "\n");
33
+ const sanitized = Array.from(normalizedLineEndings, (character) => {
34
+ const codePoint = character.codePointAt(0) ?? 0;
35
+ const unsafeControl = codePoint >= 0 && codePoint <= 8 || codePoint >= 11 && codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
36
+ const unsafeBidi = codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8294 && codePoint <= 8297;
37
+ const loneSurrogate = character.length === 1 && codePoint >= 55296 && codePoint <= 57343;
38
+ return unsafeControl || unsafeBidi || loneSurrogate ? "\uFFFD" : character;
39
+ }).join("");
40
+ const outputLimit = Math.min(maxCharacters, MAX_SANITIZER_INPUT_CODE_UNITS);
41
+ if (!inputWasTruncated && sanitized.length <= outputLimit) return sanitized;
42
+ return `${truncateAtGraphemeBoundary(sanitized, Math.max(0, outputLimit - 1))}\u2026`;
43
+ }
44
+ function truncateAtGraphemeBoundary(value, maxCodeUnits) {
45
+ if (value.length <= maxCodeUnits) return value;
46
+ const boundedLookahead = value.slice(0, Math.max(0, maxCodeUnits) + 2);
47
+ let safeEnd = 0;
48
+ for (const { index, segment } of graphemeSegmenter.segment(boundedLookahead)) {
49
+ const segmentEnd = index + segment.length;
50
+ if (segmentEnd > maxCodeUnits) break;
51
+ safeEnd = segmentEnd;
52
+ }
53
+ return value.slice(0, safeEnd);
54
+ }
24
55
 
25
56
  // src/lazy-tools.ts
26
- import { defineTool } from "@earendil-works/pi-coding-agent";
57
+ import {
58
+ defineTool
59
+ } from "@earendil-works/pi-coding-agent";
27
60
  import { Type } from "typebox";
28
61
  var CHROME_DEVTOOLS_LOAD_TOOL_NAME = "chrome_devtools_load";
29
62
  var AVAILABLE_TOOLS_STORE = /* @__PURE__ */ Symbol.for("@narumitw/pi-chrome-devtools.available-tools-store");
@@ -33,6 +66,153 @@ var availableToolsByApi = existingAvailableToolsStore ?? /* @__PURE__ */ new Wea
33
66
  if (!existingAvailableToolsStore) sharedGlobal[AVAILABLE_TOOLS_STORE] = availableToolsByApi;
34
67
  var lazyExposureByApi = /* @__PURE__ */ new WeakMap();
35
68
  var sessionOwnerByApi = /* @__PURE__ */ new WeakMap();
69
+ var PROVENANCE_STORE = /* @__PURE__ */ Symbol.for("@narumitw/pi-chrome-devtools.activation-provenance");
70
+ var PROVENANCE_ENTRY = "chrome-devtools.activation-provenance";
71
+ var provenanceGlobal = globalThis;
72
+ var ownedBySession = provenanceGlobal[PROVENANCE_STORE] ?? /* @__PURE__ */ new WeakMap();
73
+ provenanceGlobal[PROVENANCE_STORE] = ownedBySession;
74
+ var modeByApi = /* @__PURE__ */ new WeakMap();
75
+ var loaderRegistered = /* @__PURE__ */ new WeakSet();
76
+ function retainedExplicitTools(pi, before) {
77
+ const owner = sessionOwnerByApi.get(pi);
78
+ const ownership = owner ? ownedBySession.get(owner) : void 0;
79
+ const observed = new Set(before);
80
+ return CHROME_DEVTOOLS_TOOL_NAMES.filter(
81
+ (name) => ownership?.explicit.has(name) && (observed.has(name) || ownership.published?.has(name) === false)
82
+ );
83
+ }
84
+ function publishActiveTools(pi, names, before) {
85
+ pi.setActiveTools(names);
86
+ recordOwnership(pi, before);
87
+ }
88
+ function recordOwnership(pi, before) {
89
+ const names = pi.getActiveTools();
90
+ const owner = sessionOwnerByApi.get(pi);
91
+ if (!owner) return;
92
+ const ownership = ownedBySession.get(owner) ?? {
93
+ explicit: /* @__PURE__ */ new Set(),
94
+ owned: /* @__PURE__ */ new Set()
95
+ };
96
+ const observed = new Set(before);
97
+ for (const name of ownership.explicit) {
98
+ if (!observed.has(name) && (ownership.published?.has(name) ?? true))
99
+ ownership.explicit.delete(name);
100
+ }
101
+ for (const name of before)
102
+ if (CHROME_DEVTOOLS_TOOL_NAMES.includes(name) && !ownership.owned.has(name))
103
+ ownership.explicit.add(name);
104
+ if (chromeDevtoolsToolMode(pi) !== "codemode") {
105
+ for (const name of names)
106
+ if (CHROME_DEVTOOLS_TOOL_NAMES.includes(name) && !ownership.explicit.has(name))
107
+ ownership.owned.add(name);
108
+ }
109
+ if (chromeDevtoolsToolMode(pi) === "codemode") ownership.owned.clear();
110
+ const published = new Set(names);
111
+ ownership.published = new Set(CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => published.has(name)));
112
+ ownedBySession.set(owner, ownership);
113
+ persistOwnership(pi, ownership);
114
+ }
115
+ function ownershipData(ownership) {
116
+ return {
117
+ version: 1,
118
+ explicit: CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => ownership.explicit.has(name)),
119
+ owned: CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => ownership.owned.has(name)),
120
+ available: ownership.available ? CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => ownership.available?.has(name)) : void 0,
121
+ mode: ownership.mode,
122
+ published: ownership.published ? CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => ownership.published?.has(name)) : void 0
123
+ };
124
+ }
125
+ var ActivationProvenanceError = class extends Error {
126
+ constructor(cause) {
127
+ super(cause instanceof Error ? cause.message : String(cause), { cause });
128
+ }
129
+ };
130
+ function formatProvenanceWarning(error) {
131
+ return sanitizeChromeDevtoolsDisplay(
132
+ `Activation ownership could not be saved: ${error.message}. Activation is not rolled back; durable ownership may be incomplete until a later loader call or lifecycle update retries successfully.`
133
+ );
134
+ }
135
+ function publishLifecycleTools(pi, names, before) {
136
+ try {
137
+ publishActiveTools(pi, names, before);
138
+ } catch (error) {
139
+ if (!(error instanceof ActivationProvenanceError)) throw error;
140
+ return formatProvenanceWarning(error);
141
+ }
142
+ }
143
+ function persistOwnership(pi, ownership) {
144
+ ownership.available = new Set(configuredChromeDevtoolsTools(pi));
145
+ ownership.mode = chromeDevtoolsToolMode(pi);
146
+ const data = ownershipData(ownership);
147
+ const serialized = JSON.stringify(data);
148
+ if (ownership.serialized === serialized) return;
149
+ try {
150
+ pi.appendEntry(PROVENANCE_ENTRY, data);
151
+ ownership.serialized = serialized;
152
+ } catch (error) {
153
+ ownership.serialized = void 0;
154
+ throw new ActivationProvenanceError(error);
155
+ }
156
+ }
157
+ function restoredOwnership(entries) {
158
+ for (let index = entries.length - 1; index >= 0; index--) {
159
+ const entry = entries[index];
160
+ if (entry.type !== "custom" || entry.customType !== PROVENANCE_ENTRY) continue;
161
+ const data = entry.data;
162
+ if (typeof data !== "object" || data === null || Array.isArray(data)) return void 0;
163
+ const record = data;
164
+ const validNames = (value) => Array.isArray(value) && value.length <= CHROME_DEVTOOLS_TOOL_NAMES.length && value.every((name) => CHROME_DEVTOOLS_TOOL_NAMES.includes(name));
165
+ if (record.version !== 1 || !validNames(record.explicit) || !validNames(record.owned) || record.available !== void 0 && !validNames(record.available) || record.published !== void 0 && !validNames(record.published) || record.mode !== void 0 && record.mode !== "codemode" && record.mode !== "lazy" && record.mode !== "direct" || record.owned.some((name) => record.explicit.includes(name)))
166
+ return void 0;
167
+ const restored = {
168
+ explicit: new Set(record.explicit),
169
+ owned: new Set(record.owned),
170
+ available: validNames(record.available) ? new Set(record.available) : void 0,
171
+ mode: record.mode,
172
+ published: validNames(record.published) ? new Set(record.published) : void 0
173
+ };
174
+ return { ...restored, serialized: JSON.stringify(ownershipData(restored)) };
175
+ }
176
+ return void 0;
177
+ }
178
+ var definitionsByApi = /* @__PURE__ */ new WeakMap();
179
+ function registerChromeDevtoolsCapabilities(pi, tools) {
180
+ definitionsByApi.set(pi, tools);
181
+ for (const tool of tools) pi.registerTool({ ...tool, exposure: "codemode", defaultActive: false });
182
+ pi.registerTool({
183
+ ...createChromeDevtoolsLoadTool(pi),
184
+ exposure: "codemode",
185
+ defaultActive: false,
186
+ promptSnippet: void 0,
187
+ promptGuidelines: void 0
188
+ });
189
+ loaderRegistered.add(pi);
190
+ }
191
+ function chromeDevtoolsToolMode(pi) {
192
+ return modeByApi.get(pi) ?? "codemode";
193
+ }
194
+ function refreshExposure(pi) {
195
+ const available = effectiveAvailableTools(pi);
196
+ const mode = chromeDevtoolsToolMode(pi);
197
+ for (const tool of definitionsByApi.get(pi) ?? []) {
198
+ pi.registerTool({
199
+ ...tool,
200
+ defaultActive: false,
201
+ exposure: available.has(tool.name) ? mode === "codemode" ? "codemode" : "direct" : "hidden"
202
+ });
203
+ }
204
+ if (mode === "lazy") {
205
+ pi.registerTool({ ...createChromeDevtoolsLoadTool(pi), defaultActive: false });
206
+ loaderRegistered.add(pi);
207
+ } else if (loaderRegistered.has(pi)) {
208
+ pi.registerTool({
209
+ ...createChromeDevtoolsLoadTool(pi),
210
+ exposure: "hidden",
211
+ promptSnippet: void 0,
212
+ promptGuidelines: void 0
213
+ });
214
+ }
215
+ }
36
216
  var SEARCH_TEXT = {
37
217
  chrome_devtools_list_pages: "list open inspectable chrome browser pages tabs targets",
38
218
  chrome_devtools_select_page: "select choose active chrome browser page tab target",
@@ -42,62 +222,93 @@ var SEARCH_TEXT = {
42
222
  chrome_devtools_webmcp_list_tools: "list discover page provided website webmcp tools capabilities experimental",
43
223
  chrome_devtools_webmcp_call_tool: "call invoke page provided website webmcp tool confirmation experimental"
44
224
  };
45
- function setChromeDevtoolsSessionOwner(pi, owner) {
225
+ function setChromeDevtoolsSessionOwner(pi, owner, entries) {
46
226
  sessionOwnerByApi.set(pi, owner);
227
+ if (entries) {
228
+ const ownership2 = restoredOwnership(entries);
229
+ if (ownership2) {
230
+ const pending = ownedBySession.get(owner);
231
+ if (pending && pending.serialized === void 0 && ownership2.serialized === JSON.stringify(ownershipData(pending)))
232
+ ownership2.serialized = void 0;
233
+ ownedBySession.set(owner, ownership2);
234
+ } else {
235
+ ownedBySession.delete(owner);
236
+ availableToolsByApi.delete(pi);
237
+ modeByApi.delete(pi);
238
+ }
239
+ }
240
+ const ownership = ownedBySession.get(owner);
241
+ if (ownership?.available) setAvailableTools(pi, [...ownership.available]);
242
+ if (ownership?.mode) modeByApi.set(pi, ownership.mode);
47
243
  }
48
244
  function initializeAvailableChromeDevtoolsTools(pi) {
49
245
  if (availableToolsByApi.has(pi)) return;
50
- const activeTools = new Set(pi.getActiveTools());
51
- setAvailableTools(
52
- pi,
53
- CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => activeTools.has(name))
54
- );
246
+ setAvailableTools(pi, CHROME_DEVTOOLS_TOOL_NAMES);
55
247
  }
56
- function configureChromeDevtoolsToolExposure(pi, availableTools, model) {
248
+ function configureChromeDevtoolsToolExposure(pi, availableTools, model, mode = "codemode") {
249
+ modeByApi.set(pi, mode);
57
250
  setAvailableTools(pi, availableTools);
58
251
  const available = effectiveAvailableTools(pi);
59
- const lazyExposure = supportsNativeDeferredToolLoading(model);
252
+ const lazyExposure = mode === "lazy" && supportsNativeDeferredToolLoading(model);
60
253
  lazyExposureByApi.set(pi, lazyExposure);
61
- const exposedTools = lazyExposure ? [] : CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
254
+ const before = pi.getActiveTools();
255
+ const owner = sessionOwnerByApi.get(pi);
256
+ let ownership = owner ? ownedBySession.get(owner) : void 0;
257
+ if (owner && !ownership && before.includes(CHROME_DEVTOOLS_LOAD_TOOL_NAME)) {
258
+ ownership = {
259
+ explicit: /* @__PURE__ */ new Set(),
260
+ owned: new Set(before.filter((name) => CHROME_DEVTOOLS_TOOL_NAMES.includes(name)))
261
+ };
262
+ ownedBySession.set(owner, ownership);
263
+ }
264
+ const owned = ownership?.owned;
265
+ const exposedTools = mode === "codemode" ? unique([...before.filter((name) => !owned?.has(name)), ...retainedExplicitTools(pi, before)]).filter(
266
+ (name) => available.has(name)
267
+ ) : lazyExposure ? [] : CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
62
268
  const nonCapabilityTools = pi.getActiveTools().filter((name) => !CHROME_DEVTOOLS_TOOL_NAMES.includes(name));
63
- pi.setActiveTools(unique([...nonCapabilityTools, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...exposedTools]));
269
+ refreshExposure(pi);
270
+ const target = /* @__PURE__ */ new Set([
271
+ ...nonCapabilityTools.filter((name) => name !== CHROME_DEVTOOLS_LOAD_TOOL_NAME),
272
+ ...mode === "lazy" ? [CHROME_DEVTOOLS_LOAD_TOOL_NAME] : [],
273
+ ...exposedTools
274
+ ]);
275
+ return publishLifecycleTools(pi, unique([...before.filter((name) => target.has(name)), ...target]), before);
64
276
  }
65
277
  function requireEagerChromeDevtoolsToolExposure(pi) {
278
+ if (chromeDevtoolsToolMode(pi) !== "lazy") return;
66
279
  lazyExposureByApi.set(pi, false);
67
280
  const active = pi.getActiveTools();
68
281
  const available = availableChromeDevtoolsTools(pi);
69
- pi.setActiveTools(unique([...active, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...available]));
282
+ return publishLifecycleTools(pi, unique([...active, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...available]), active);
70
283
  }
71
- function applyAvailableChromeDevtoolsTools(pi, availableTools) {
284
+ function applyAvailableChromeDevtoolsTools(pi, availableTools, restoredActiveTools) {
72
285
  setAvailableTools(pi, availableTools);
73
286
  const available = effectiveAvailableTools(pi);
74
287
  const lazyExposure = lazyExposureByApi.get(pi) === true;
75
- const active = pi.getActiveTools().filter(
288
+ const before = restoredActiveTools ? restoreCapabilityPositions(pi.getActiveTools(), restoredActiveTools, available) : pi.getActiveTools();
289
+ const active = before.filter(
76
290
  (name) => !CHROME_DEVTOOLS_TOOL_NAMES.includes(name) || lazyExposure && available.has(name)
77
291
  );
78
- const eagerTools = lazyExposure ? [] : CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
79
- pi.setActiveTools(unique([...active, CHROME_DEVTOOLS_LOAD_TOOL_NAME, ...eagerTools]));
292
+ const mode = chromeDevtoolsToolMode(pi);
293
+ const eagerTools = mode === "codemode" ? unique([...before, ...retainedExplicitTools(pi, before)]).filter(
294
+ (name) => available.has(name)
295
+ ) : lazyExposure ? [] : CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
296
+ refreshExposure(pi);
297
+ const target = /* @__PURE__ */ new Set([
298
+ ...active.filter((name) => name !== CHROME_DEVTOOLS_LOAD_TOOL_NAME),
299
+ ...mode === "lazy" ? [CHROME_DEVTOOLS_LOAD_TOOL_NAME] : [],
300
+ ...eagerTools
301
+ ]);
302
+ publishActiveTools(pi, unique([...before.filter((name) => target.has(name)), ...target]), before);
80
303
  }
81
304
  function chromeDevtoolsToolExposureMode(pi) {
82
- return lazyExposureByApi.get(pi) === true ? "native deferred" : "eager";
305
+ return chromeDevtoolsToolMode(pi) === "codemode" ? "codemode" : lazyExposureByApi.get(pi) === true ? "native deferred" : "eager";
83
306
  }
84
307
  function supportsNativeDeferredToolLoading(model) {
85
- if (!model) return false;
86
- if (model.api === "anthropic-messages") {
87
- if (model.provider === "fireworks") return false;
88
- const configured = compatBoolean(model.compat, "supportsToolReferences");
89
- if (configured !== void 0) return configured;
90
- if (model.provider !== "anthropic" || model.id.includes("haiku")) return false;
91
- const version = model.id.match(/^claude-(?:opus|sonnet|fable)-(\d+)(?:-(\d+))?(?:-|$)/);
92
- if (!version) return false;
93
- const major = Number(version[1]);
94
- const minor = version[2] && version[2].length < 8 ? Number(version[2]) : 0;
95
- return major > 4 || major === 4 && minor >= 5;
96
- }
97
- if (model.api === "openai-completions") {
98
- return compatString(model.compat, "deferredToolsMode") === "kimi";
99
- }
100
- if (model.api === "openai-responses" || model.api === "openai-codex-responses") {
308
+ if (!model || compatBoolean(model.compat, "supportsMidConvoSystemMessages") !== true) return false;
309
+ if (model.api === "anthropic-messages") return compatBoolean(model.compat, "supportsMidConvoToolChanges") === true;
310
+ if (model.api === "openai-completions") return compatBoolean(model.compat, "supportsMidConvoToolAdditions") === true;
311
+ if (model.api === "openai-responses" || model.api === "openai-codex-responses" || model.api === "azure-openai-responses") {
101
312
  return compatBoolean(model.compat, "supportsAdditionalTools") === true || compatBoolean(model.compat, "supportsToolSearch") === true;
102
313
  }
103
314
  return false;
@@ -106,6 +317,11 @@ function availableChromeDevtoolsTools(pi) {
106
317
  const available = effectiveAvailableTools(pi);
107
318
  return CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
108
319
  }
320
+ function previousChromeDevtoolsTools(pi) {
321
+ if (availableToolsByApi.has(pi)) return configuredChromeDevtoolsTools(pi);
322
+ const active = new Set(pi.getActiveTools());
323
+ return CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => active.has(name));
324
+ }
109
325
  function configuredChromeDevtoolsTools(pi) {
110
326
  const available = availableToolsByApi.get(pi) ?? /* @__PURE__ */ new Set();
111
327
  return CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
@@ -140,13 +356,23 @@ function createChromeDevtoolsLoadTool(pi) {
140
356
  const active = pi.getActiveTools();
141
357
  const activeSet = new Set(active);
142
358
  const added = matches.filter((name) => !activeSet.has(name));
143
- if (added.length > 0) {
144
- pi.setActiveTools(unique([...active, ...added]));
359
+ let provenanceWarning;
360
+ try {
361
+ if (added.length > 0) {
362
+ publishActiveTools(pi, unique([...active, ...added]), active);
363
+ } else {
364
+ const ownership = ownedBySession.get(ctx.sessionManager);
365
+ if (ownership && ownership.serialized === void 0) recordOwnership(pi, active);
366
+ }
367
+ } catch (error) {
368
+ if (!(error instanceof ActivationProvenanceError)) throw error;
369
+ provenanceWarning = formatProvenanceWarning(error);
145
370
  }
146
371
  const text = matches.length === 0 ? "No available Chrome DevTools tools matched the query." : added.length > 0 ? `Loaded Chrome DevTools tools: ${added.join(", ")}` : `Matching Chrome DevTools tools are already loaded: ${matches.join(", ")}`;
147
372
  return {
148
- content: [{ type: "text", text }],
149
- details: { matches, added }
373
+ content: [{ type: "text", text: provenanceWarning ? `${text}
374
+ Warning: ${provenanceWarning}` : text }],
375
+ details: { matches, added, ...provenanceWarning ? { provenanceWarning } : {} }
150
376
  };
151
377
  }
152
378
  });
@@ -178,49 +404,21 @@ function compatBoolean(value, key) {
178
404
  const record = value;
179
405
  return typeof record[key] === "boolean" ? record[key] : void 0;
180
406
  }
181
- function compatString(value, key) {
182
- if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
183
- const record = value;
184
- return typeof record[key] === "string" ? record[key] : void 0;
407
+ function restoreCapabilityPositions(current, previous, available) {
408
+ const restored = [...current];
409
+ for (const [index, name] of previous.entries()) {
410
+ if (!available.has(name) || restored.includes(name)) continue;
411
+ const following = previous.slice(index + 1).find((candidate) => restored.includes(candidate));
412
+ const insertion = following === void 0 ? restored.length : restored.indexOf(following);
413
+ restored.splice(insertion, 0, name);
414
+ }
415
+ return restored;
185
416
  }
186
417
  function unique(values) {
187
418
  return [...new Set(values)];
188
419
  }
189
420
 
190
- // src/display.ts
191
- var MAX_SANITIZER_INPUT_CODE_UNITS = 5e4;
192
- var graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
193
- function sanitizeChromeDevtoolsDisplay(value, maxCharacters = 5e4) {
194
- const inputWasTruncated = value.length > MAX_SANITIZER_INPUT_CODE_UNITS;
195
- const boundedInput = inputWasTruncated ? truncateAtGraphemeBoundary(value, MAX_SANITIZER_INPUT_CODE_UNITS) : value;
196
- const normalizedLineEndings = boundedInput.replace(/\r\n/g, "\n");
197
- const sanitized = Array.from(normalizedLineEndings, (character) => {
198
- const codePoint = character.codePointAt(0) ?? 0;
199
- const unsafeControl = codePoint >= 0 && codePoint <= 8 || codePoint >= 11 && codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
200
- const unsafeBidi = codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8294 && codePoint <= 8297;
201
- const loneSurrogate = character.length === 1 && codePoint >= 55296 && codePoint <= 57343;
202
- return unsafeControl || unsafeBidi || loneSurrogate ? "\uFFFD" : character;
203
- }).join("");
204
- const outputLimit = Math.min(maxCharacters, MAX_SANITIZER_INPUT_CODE_UNITS);
205
- if (!inputWasTruncated && sanitized.length <= outputLimit) return sanitized;
206
- return `${truncateAtGraphemeBoundary(sanitized, Math.max(0, outputLimit - 1))}\u2026`;
207
- }
208
- function truncateAtGraphemeBoundary(value, maxCodeUnits) {
209
- if (value.length <= maxCodeUnits) return value;
210
- const boundedLookahead = value.slice(0, Math.max(0, maxCodeUnits) + 2);
211
- let safeEnd = 0;
212
- for (const { index, segment } of graphemeSegmenter.segment(boundedLookahead)) {
213
- const segmentEnd = index + segment.length;
214
- if (segmentEnd > maxCodeUnits) break;
215
- safeEnd = segmentEnd;
216
- }
217
- return value.slice(0, safeEnd);
218
- }
219
-
220
421
  // src/tool-selector.ts
221
- function unique2(values) {
222
- return Array.from(new Set(values));
223
- }
224
422
  function formatError(error) {
225
423
  return error instanceof Error ? error.message : String(error);
226
424
  }
@@ -242,8 +440,10 @@ async function waitForChromeDevtoolsSettings() {
242
440
  await toolTransactionQueue;
243
441
  }
244
442
  function transactSelectedTools(pi, ctx, selectedTools, expectedGeneration, expectedActiveTools) {
443
+ const acceptedTools = [...selectedTools];
444
+ const expectedTools = expectedActiveTools ? [...expectedActiveTools] : void 0;
245
445
  const operation = toolTransactionQueue.then(
246
- () => transactSelectedToolsNow(pi, ctx, selectedTools, expectedGeneration, expectedActiveTools)
446
+ () => transactSelectedToolsNow(pi, ctx, acceptedTools, expectedGeneration, expectedTools)
247
447
  );
248
448
  toolTransactionQueue = operation.then(
249
449
  () => void 0,
@@ -252,7 +452,25 @@ function transactSelectedTools(pi, ctx, selectedTools, expectedGeneration, expec
252
452
  return operation;
253
453
  }
254
454
  async function transactSelectedToolsNow(pi, ctx, selectedTools, expectedGeneration, expectedActiveTools) {
255
- if (expectedGeneration !== state.sessionGeneration) return "failed";
455
+ if (expectedGeneration !== state.sessionGeneration) {
456
+ try {
457
+ if (expectedActiveTools) {
458
+ const settings = await loadSettings();
459
+ if (settings.userFile.kind !== "invalid") {
460
+ const catalog = new Set(
461
+ settings.kind === "loaded" && settings.settings.tools ? settings.settings.tools : CHROME_DEVTOOLS_TOOL_NAMES
462
+ );
463
+ const current = CHROME_DEVTOOLS_TOOL_NAMES.filter(
464
+ (name) => catalog.has(name) && (settings.effectiveWebMcpEnabled || !isWebMcpToolName(name))
465
+ );
466
+ if (!arraysEqual(current, expectedActiveTools)) return "active-tools-changed";
467
+ }
468
+ }
469
+ await persistSettings(selectedTools);
470
+ } catch {
471
+ }
472
+ return "failed";
473
+ }
256
474
  if (expectedActiveTools && !arraysEqual(availableChromeDevtoolsTools(pi), expectedActiveTools)) {
257
475
  ctx.ui.notify(
258
476
  "Browser tool selection changed while review was open. Review the current state, then apply again.",
@@ -260,34 +478,38 @@ async function transactSelectedToolsNow(pi, ctx, selectedTools, expectedGenerati
260
478
  );
261
479
  return "active-tools-changed";
262
480
  }
263
- const previousActiveTools = pi.getActiveTools();
264
- const previousAvailableTools = availableChromeDevtoolsTools(pi);
265
481
  try {
266
- const previousWebMcpTools = previousAvailableTools.filter(isWebMcpToolName);
267
- const selectedWebMcpTools = selectedTools.filter(isWebMcpToolName);
268
- if (!arraysEqual(previousWebMcpTools, selectedWebMcpTools)) {
269
- invalidateWebMcpOperations(ctx.sessionManager, "Chrome DevTools WebMCP gateway availability changed");
270
- }
271
- applyChromeDevtoolsTools(pi, selectedTools);
272
- await persistSettings(selectedTools);
482
+ await persistSettings(selectedTools, () => {
483
+ if (expectedGeneration !== state.sessionGeneration) return;
484
+ const previousActiveTools = pi.getActiveTools();
485
+ const previousAvailableTools = availableChromeDevtoolsTools(pi);
486
+ const previousConfiguredTools = configuredChromeDevtoolsTools(pi);
487
+ try {
488
+ const previousWebMcpTools = previousAvailableTools.filter(isWebMcpToolName);
489
+ applyChromeDevtoolsTools(pi, selectedTools);
490
+ const publishedWebMcpTools = availableChromeDevtoolsTools(pi).filter(isWebMcpToolName);
491
+ if (!arraysEqual(previousWebMcpTools, publishedWebMcpTools)) {
492
+ invalidateWebMcpOperations(ctx.sessionManager, "Chrome DevTools WebMCP gateway availability changed");
493
+ }
494
+ } catch (error) {
495
+ if (expectedGeneration === state.sessionGeneration) {
496
+ try {
497
+ applyAvailableChromeDevtoolsTools(pi, previousConfiguredTools, previousActiveTools);
498
+ } catch (rollbackError) {
499
+ throw new Error(`${formatError(error)}; active-tool rollback failed: ${formatError(rollbackError)}`, {
500
+ cause: error
501
+ });
502
+ }
503
+ }
504
+ throw error;
505
+ }
506
+ });
273
507
  return expectedGeneration === state.sessionGeneration ? "saved" : "failed";
274
508
  } catch (error) {
275
- let rollbackError;
276
- try {
277
- applyAvailableChromeDevtoolsTools(pi, previousAvailableTools);
278
- const currentNonChromeTools = pi.getActiveTools().filter((name) => !CHROME_DEVTOOLS_TOOL_NAMES.includes(name));
279
- const previousLoadedChromeTools = previousActiveTools.filter(
280
- (name) => CHROME_DEVTOOLS_TOOL_NAMES.includes(name)
281
- );
282
- const restoredChromeTools = chromeDevtoolsToolExposureMode(pi) === "eager" ? previousAvailableTools : previousLoadedChromeTools;
283
- pi.setActiveTools(unique2([...currentNonChromeTools, ...restoredChromeTools]));
284
- } catch (caught) {
285
- rollbackError = caught;
286
- }
287
509
  if (expectedGeneration !== state.sessionGeneration) return "failed";
288
510
  ctx.ui.notify(
289
511
  sanitizeChromeDevtoolsDisplay(
290
- rollbackError ? `Chrome DevTools settings save failed: ${formatError(error)}; active-tool rollback failed: ${formatError(rollbackError)}` : `Chrome DevTools settings save failed; active tools restored: ${formatError(error)}`
512
+ error instanceof ToolCatalogApplicationError ? `Chrome DevTools tool application failed: ${formatError(error)}` : `Chrome DevTools settings save failed; active tools unchanged: ${formatError(error)}`
291
513
  ),
292
514
  "warning"
293
515
  );
@@ -319,12 +541,21 @@ function getToolStatusSummary(pi, owner) {
319
541
  };
320
542
  }
321
543
  async function buildToolStatusMessage(pi, owner) {
544
+ const generation = state.sessionGeneration;
545
+ await waitForChromeDevtoolsSettings();
546
+ if (generation !== state.sessionGeneration) return "";
547
+ const settings = await loadSettings();
548
+ if (generation !== state.sessionGeneration) return "";
322
549
  const summary = getToolStatusSummary(pi, owner);
323
- const persistedSetting = await persistedSettingLabel();
550
+ const persistedSetting = persistedSettingLabel(settings);
551
+ const savedMode = settings.userFile.kind === "invalid" ? void 0 : settings.kind === "loaded" ? settings.settings.toolMode : "codemode";
324
552
  return sanitizeChromeDevtoolsDisplay(
325
553
  [
326
554
  `Chrome DevTools tools available: ${formatRuntimeStatus(summary)}`,
555
+ `Running tool mode: ${chromeDevtoolsToolMode(pi)}`,
327
556
  `Tool exposure: ${chromeDevtoolsToolExposureMode(pi)}`,
557
+ `Saved tool mode: ${savedMode ?? "unavailable (invalid user settings)"}`,
558
+ ...savedMode !== void 0 && savedMode !== chromeDevtoolsToolMode(pi) ? ["Tool mode change pending; /reload or session replacement required."] : [],
328
559
  `Loaded capability tools this session: ${summary.loadedChromeToolCount}/${summary.capabilityCount}`,
329
560
  `WebMCP: ${webMcpEnabled(owner) ? "enabled \xB7 experimental \xB7 confirmation required for every call" : "disabled \xB7 experimental"}`,
330
561
  `Loader: ${pi.getActiveTools().includes(CHROME_DEVTOOLS_LOAD_TOOL_NAME) ? "active" : "inactive"}`,
@@ -417,15 +648,14 @@ function allChromeDevtoolsTools(owner) {
417
648
  function formatRuntimeStatus(summary) {
418
649
  return `${summary.availabilityStatus} (${summary.availableChromeToolCount}/${summary.capabilityCount} available)`;
419
650
  }
420
- async function persistedSettingLabel() {
421
- const settings = await loadSettings();
651
+ function persistedSettingLabel(settings) {
422
652
  if (settings.kind === "loaded" && settings.settings.tools) {
423
653
  return formatPersistedSelection(settings.settings.tools);
424
654
  }
425
655
  if (settings.kind === "invalid") {
426
656
  return `none; current active-tool policy preserved (invalid settings ignored: ${settings.reason})`;
427
657
  }
428
- return "none; current active-tool policy preserved";
658
+ return "none; default catalog restored on /reload or session replacement";
429
659
  }
430
660
  function formatPersistedSelection(tools) {
431
661
  if (tools.length === CHROME_DEVTOOLS_TOOL_NAMES.length) {
@@ -437,11 +667,14 @@ function formatPersistedSelection(tools) {
437
667
  function effectiveCatalog(owner) {
438
668
  return webMcpEnabled(owner) ? [...CHROME_DEVTOOLS_TOOL_NAMES] : [...CORE_CHROME_DEVTOOLS_TOOL_NAMES];
439
669
  }
440
- async function persistSettings(selectedTools) {
441
- await saveSettings({ tools: [...selectedTools], updatedAt: Date.now() });
670
+ async function persistSettings(selectedTools, applyAfterSave) {
671
+ await saveSettings({ tools: [...selectedTools], updatedAt: Date.now() }, {}, applyAfterSave);
442
672
  }
443
673
 
444
674
  export {
675
+ sanitizeChromeDevtoolsDisplay,
676
+ registerChromeDevtoolsCapabilities,
677
+ chromeDevtoolsToolMode,
445
678
  setChromeDevtoolsSessionOwner,
446
679
  initializeAvailableChromeDevtoolsTools,
447
680
  configureChromeDevtoolsToolExposure,
@@ -449,9 +682,8 @@ export {
449
682
  applyAvailableChromeDevtoolsTools,
450
683
  supportsNativeDeferredToolLoading,
451
684
  availableChromeDevtoolsTools,
685
+ previousChromeDevtoolsTools,
452
686
  configuredChromeDevtoolsTools,
453
- createChromeDevtoolsLoadTool,
454
- sanitizeChromeDevtoolsDisplay,
455
687
  updateChromeDevtoolsTools,
456
688
  setSelectedChromeDevtoolsTools,
457
689
  waitForChromeDevtoolsSettings,
@@ -461,4 +693,4 @@ export {
461
693
  buildCommandGuide,
462
694
  allChromeDevtoolsTools
463
695
  };
464
- //# sourceMappingURL=chunk-V7N3WI5B.ts.map
696
+ //# sourceMappingURL=chunk-JKPJYUVG.ts.map