@narumitw/pi-chrome-devtools 0.53.4 → 0.54.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.
@@ -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-TVVYQ5KM.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,44 +222,87 @@ 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
308
  if (!model) return false;
@@ -106,6 +329,11 @@ function availableChromeDevtoolsTools(pi) {
106
329
  const available = effectiveAvailableTools(pi);
107
330
  return CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
108
331
  }
332
+ function previousChromeDevtoolsTools(pi) {
333
+ if (availableToolsByApi.has(pi)) return configuredChromeDevtoolsTools(pi);
334
+ const active = new Set(pi.getActiveTools());
335
+ return CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => active.has(name));
336
+ }
109
337
  function configuredChromeDevtoolsTools(pi) {
110
338
  const available = availableToolsByApi.get(pi) ?? /* @__PURE__ */ new Set();
111
339
  return CHROME_DEVTOOLS_TOOL_NAMES.filter((name) => available.has(name));
@@ -140,13 +368,23 @@ function createChromeDevtoolsLoadTool(pi) {
140
368
  const active = pi.getActiveTools();
141
369
  const activeSet = new Set(active);
142
370
  const added = matches.filter((name) => !activeSet.has(name));
143
- if (added.length > 0) {
144
- pi.setActiveTools(unique([...active, ...added]));
371
+ let provenanceWarning;
372
+ try {
373
+ if (added.length > 0) {
374
+ publishActiveTools(pi, unique([...active, ...added]), active);
375
+ } else {
376
+ const ownership = ownedBySession.get(ctx.sessionManager);
377
+ if (ownership && ownership.serialized === void 0) recordOwnership(pi, active);
378
+ }
379
+ } catch (error) {
380
+ if (!(error instanceof ActivationProvenanceError)) throw error;
381
+ provenanceWarning = formatProvenanceWarning(error);
145
382
  }
146
383
  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
384
  return {
148
- content: [{ type: "text", text }],
149
- details: { matches, added }
385
+ content: [{ type: "text", text: provenanceWarning ? `${text}
386
+ Warning: ${provenanceWarning}` : text }],
387
+ details: { matches, added, ...provenanceWarning ? { provenanceWarning } : {} }
150
388
  };
151
389
  }
152
390
  });
@@ -183,44 +421,21 @@ function compatString(value, key) {
183
421
  const record = value;
184
422
  return typeof record[key] === "string" ? record[key] : void 0;
185
423
  }
424
+ function restoreCapabilityPositions(current, previous, available) {
425
+ const restored = [...current];
426
+ for (const [index, name] of previous.entries()) {
427
+ if (!available.has(name) || restored.includes(name)) continue;
428
+ const following = previous.slice(index + 1).find((candidate) => restored.includes(candidate));
429
+ const insertion = following === void 0 ? restored.length : restored.indexOf(following);
430
+ restored.splice(insertion, 0, name);
431
+ }
432
+ return restored;
433
+ }
186
434
  function unique(values) {
187
435
  return [...new Set(values)];
188
436
  }
189
437
 
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
438
  // src/tool-selector.ts
221
- function unique2(values) {
222
- return Array.from(new Set(values));
223
- }
224
439
  function formatError(error) {
225
440
  return error instanceof Error ? error.message : String(error);
226
441
  }
@@ -242,8 +457,10 @@ async function waitForChromeDevtoolsSettings() {
242
457
  await toolTransactionQueue;
243
458
  }
244
459
  function transactSelectedTools(pi, ctx, selectedTools, expectedGeneration, expectedActiveTools) {
460
+ const acceptedTools = [...selectedTools];
461
+ const expectedTools = expectedActiveTools ? [...expectedActiveTools] : void 0;
245
462
  const operation = toolTransactionQueue.then(
246
- () => transactSelectedToolsNow(pi, ctx, selectedTools, expectedGeneration, expectedActiveTools)
463
+ () => transactSelectedToolsNow(pi, ctx, acceptedTools, expectedGeneration, expectedTools)
247
464
  );
248
465
  toolTransactionQueue = operation.then(
249
466
  () => void 0,
@@ -252,7 +469,25 @@ function transactSelectedTools(pi, ctx, selectedTools, expectedGeneration, expec
252
469
  return operation;
253
470
  }
254
471
  async function transactSelectedToolsNow(pi, ctx, selectedTools, expectedGeneration, expectedActiveTools) {
255
- if (expectedGeneration !== state.sessionGeneration) return "failed";
472
+ if (expectedGeneration !== state.sessionGeneration) {
473
+ try {
474
+ if (expectedActiveTools) {
475
+ const settings = await loadSettings();
476
+ if (settings.userFile.kind !== "invalid") {
477
+ const catalog = new Set(
478
+ settings.kind === "loaded" && settings.settings.tools ? settings.settings.tools : CHROME_DEVTOOLS_TOOL_NAMES
479
+ );
480
+ const current = CHROME_DEVTOOLS_TOOL_NAMES.filter(
481
+ (name) => catalog.has(name) && (settings.effectiveWebMcpEnabled || !isWebMcpToolName(name))
482
+ );
483
+ if (!arraysEqual(current, expectedActiveTools)) return "active-tools-changed";
484
+ }
485
+ }
486
+ await persistSettings(selectedTools);
487
+ } catch {
488
+ }
489
+ return "failed";
490
+ }
256
491
  if (expectedActiveTools && !arraysEqual(availableChromeDevtoolsTools(pi), expectedActiveTools)) {
257
492
  ctx.ui.notify(
258
493
  "Browser tool selection changed while review was open. Review the current state, then apply again.",
@@ -260,34 +495,38 @@ async function transactSelectedToolsNow(pi, ctx, selectedTools, expectedGenerati
260
495
  );
261
496
  return "active-tools-changed";
262
497
  }
263
- const previousActiveTools = pi.getActiveTools();
264
- const previousAvailableTools = availableChromeDevtoolsTools(pi);
265
498
  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);
499
+ await persistSettings(selectedTools, () => {
500
+ if (expectedGeneration !== state.sessionGeneration) return;
501
+ const previousActiveTools = pi.getActiveTools();
502
+ const previousAvailableTools = availableChromeDevtoolsTools(pi);
503
+ const previousConfiguredTools = configuredChromeDevtoolsTools(pi);
504
+ try {
505
+ const previousWebMcpTools = previousAvailableTools.filter(isWebMcpToolName);
506
+ applyChromeDevtoolsTools(pi, selectedTools);
507
+ const publishedWebMcpTools = availableChromeDevtoolsTools(pi).filter(isWebMcpToolName);
508
+ if (!arraysEqual(previousWebMcpTools, publishedWebMcpTools)) {
509
+ invalidateWebMcpOperations(ctx.sessionManager, "Chrome DevTools WebMCP gateway availability changed");
510
+ }
511
+ } catch (error) {
512
+ if (expectedGeneration === state.sessionGeneration) {
513
+ try {
514
+ applyAvailableChromeDevtoolsTools(pi, previousConfiguredTools, previousActiveTools);
515
+ } catch (rollbackError) {
516
+ throw new Error(`${formatError(error)}; active-tool rollback failed: ${formatError(rollbackError)}`, {
517
+ cause: error
518
+ });
519
+ }
520
+ }
521
+ throw error;
522
+ }
523
+ });
273
524
  return expectedGeneration === state.sessionGeneration ? "saved" : "failed";
274
525
  } 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
526
  if (expectedGeneration !== state.sessionGeneration) return "failed";
288
527
  ctx.ui.notify(
289
528
  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)}`
529
+ error instanceof ToolCatalogApplicationError ? `Chrome DevTools tool application failed: ${formatError(error)}` : `Chrome DevTools settings save failed; active tools unchanged: ${formatError(error)}`
291
530
  ),
292
531
  "warning"
293
532
  );
@@ -319,12 +558,21 @@ function getToolStatusSummary(pi, owner) {
319
558
  };
320
559
  }
321
560
  async function buildToolStatusMessage(pi, owner) {
561
+ const generation = state.sessionGeneration;
562
+ await waitForChromeDevtoolsSettings();
563
+ if (generation !== state.sessionGeneration) return "";
564
+ const settings = await loadSettings();
565
+ if (generation !== state.sessionGeneration) return "";
322
566
  const summary = getToolStatusSummary(pi, owner);
323
- const persistedSetting = await persistedSettingLabel();
567
+ const persistedSetting = persistedSettingLabel(settings);
568
+ const savedMode = settings.userFile.kind === "invalid" ? void 0 : settings.kind === "loaded" ? settings.settings.toolMode : "codemode";
324
569
  return sanitizeChromeDevtoolsDisplay(
325
570
  [
326
571
  `Chrome DevTools tools available: ${formatRuntimeStatus(summary)}`,
572
+ `Running tool mode: ${chromeDevtoolsToolMode(pi)}`,
327
573
  `Tool exposure: ${chromeDevtoolsToolExposureMode(pi)}`,
574
+ `Saved tool mode: ${savedMode ?? "unavailable (invalid user settings)"}`,
575
+ ...savedMode !== void 0 && savedMode !== chromeDevtoolsToolMode(pi) ? ["Tool mode change pending; /reload or session replacement required."] : [],
328
576
  `Loaded capability tools this session: ${summary.loadedChromeToolCount}/${summary.capabilityCount}`,
329
577
  `WebMCP: ${webMcpEnabled(owner) ? "enabled \xB7 experimental \xB7 confirmation required for every call" : "disabled \xB7 experimental"}`,
330
578
  `Loader: ${pi.getActiveTools().includes(CHROME_DEVTOOLS_LOAD_TOOL_NAME) ? "active" : "inactive"}`,
@@ -417,15 +665,14 @@ function allChromeDevtoolsTools(owner) {
417
665
  function formatRuntimeStatus(summary) {
418
666
  return `${summary.availabilityStatus} (${summary.availableChromeToolCount}/${summary.capabilityCount} available)`;
419
667
  }
420
- async function persistedSettingLabel() {
421
- const settings = await loadSettings();
668
+ function persistedSettingLabel(settings) {
422
669
  if (settings.kind === "loaded" && settings.settings.tools) {
423
670
  return formatPersistedSelection(settings.settings.tools);
424
671
  }
425
672
  if (settings.kind === "invalid") {
426
673
  return `none; current active-tool policy preserved (invalid settings ignored: ${settings.reason})`;
427
674
  }
428
- return "none; current active-tool policy preserved";
675
+ return "none; default catalog restored on /reload or session replacement";
429
676
  }
430
677
  function formatPersistedSelection(tools) {
431
678
  if (tools.length === CHROME_DEVTOOLS_TOOL_NAMES.length) {
@@ -437,11 +684,14 @@ function formatPersistedSelection(tools) {
437
684
  function effectiveCatalog(owner) {
438
685
  return webMcpEnabled(owner) ? [...CHROME_DEVTOOLS_TOOL_NAMES] : [...CORE_CHROME_DEVTOOLS_TOOL_NAMES];
439
686
  }
440
- async function persistSettings(selectedTools) {
441
- await saveSettings({ tools: [...selectedTools], updatedAt: Date.now() });
687
+ async function persistSettings(selectedTools, applyAfterSave) {
688
+ await saveSettings({ tools: [...selectedTools], updatedAt: Date.now() }, {}, applyAfterSave);
442
689
  }
443
690
 
444
691
  export {
692
+ sanitizeChromeDevtoolsDisplay,
693
+ registerChromeDevtoolsCapabilities,
694
+ chromeDevtoolsToolMode,
445
695
  setChromeDevtoolsSessionOwner,
446
696
  initializeAvailableChromeDevtoolsTools,
447
697
  configureChromeDevtoolsToolExposure,
@@ -449,9 +699,8 @@ export {
449
699
  applyAvailableChromeDevtoolsTools,
450
700
  supportsNativeDeferredToolLoading,
451
701
  availableChromeDevtoolsTools,
702
+ previousChromeDevtoolsTools,
452
703
  configuredChromeDevtoolsTools,
453
- createChromeDevtoolsLoadTool,
454
- sanitizeChromeDevtoolsDisplay,
455
704
  updateChromeDevtoolsTools,
456
705
  setSelectedChromeDevtoolsTools,
457
706
  waitForChromeDevtoolsSettings,
@@ -461,4 +710,4 @@ export {
461
710
  buildCommandGuide,
462
711
  allChromeDevtoolsTools
463
712
  };
464
- //# sourceMappingURL=chunk-V7N3WI5B.ts.map
713
+ //# sourceMappingURL=chunk-WOFI2A72.ts.map