@narumitw/pi-chrome-devtools 0.53.2 → 0.53.3
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 +1 -0
- package/dist/chunks/{browser-settings-menu-IMB6HXSU.js → browser-settings-menu-SR7KLZT6.js} +16 -42
- package/dist/chunks/browser-settings-menu-SR7KLZT6.js.map +7 -0
- package/dist/chunks/{chunk-GTTYS5GT.js → chunk-4KMN4SFF.js} +2 -2
- package/dist/chunks/chunk-4KMN4SFF.js.map +7 -0
- package/dist/chunks/{chunk-SRT3UUIP.js → chunk-F5VG35UI.js} +23 -74
- package/dist/chunks/chunk-F5VG35UI.js.map +7 -0
- package/dist/chunks/{chunk-LLXOCRNF.js → chunk-MZCGM6D3.js} +12 -33
- package/dist/chunks/chunk-MZCGM6D3.js.map +7 -0
- package/dist/chunks/{menu-UJETYMSB.js → menu-YR4VRT7D.js} +10 -23
- package/dist/chunks/menu-YR4VRT7D.js.map +7 -0
- package/dist/chunks/{tools-6P7EUX37.js → tools-6R7Z6HWJ.js} +17 -59
- package/dist/chunks/tools-6R7Z6HWJ.js.map +7 -0
- package/dist/index.ts +18 -42
- package/dist/index.ts.map +2 -2
- package/package.json +54 -55
- package/src/browser-manager.ts +872 -942
- package/src/browser-settings-menu.ts +315 -350
- package/src/cdp-client.ts +381 -398
- package/src/chrome-devtools.ts +205 -232
- package/src/display.ts +26 -29
- package/src/lazy-tools.ts +158 -181
- package/src/menu.ts +409 -446
- package/src/render.ts +72 -81
- package/src/runtime.ts +137 -140
- package/src/screenshot.ts +170 -186
- package/src/settings.ts +472 -523
- package/src/tool-names.ts +8 -14
- package/src/tool-selector.ts +242 -271
- package/src/tools.ts +218 -226
- package/src/webmcp/discovery.ts +184 -188
- package/src/webmcp/policy.ts +296 -329
- package/src/webmcp/protocol.ts +324 -337
- package/src/webmcp/tools.ts +136 -147
- package/dist/chunks/browser-settings-menu-IMB6HXSU.js.map +0 -7
- package/dist/chunks/chunk-GTTYS5GT.js.map +0 -7
- package/dist/chunks/chunk-LLXOCRNF.js.map +0 -7
- package/dist/chunks/chunk-SRT3UUIP.js.map +0 -7
- package/dist/chunks/menu-UJETYMSB.js.map +0 -7
- package/dist/chunks/tools-6P7EUX37.js.map +0 -7
|
@@ -3,404 +3,369 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-c
|
|
|
3
3
|
import { defineMenu, runMenu } from "@narumitw/pi-tui-kit";
|
|
4
4
|
import { shutdownManagedBrowser, syncManagedBrowserSettings } from "./browser-manager.js";
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
applyAvailableChromeDevtoolsTools,
|
|
7
|
+
configuredChromeDevtoolsTools,
|
|
8
|
+
setChromeDevtoolsSessionOwner,
|
|
9
9
|
} from "./lazy-tools.js";
|
|
10
10
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
applyRuntimeBrowserSettings,
|
|
12
|
+
applyRuntimeWebMcpSetting,
|
|
13
|
+
invalidateWebMcpOperations,
|
|
14
|
+
state,
|
|
15
|
+
webMcpEnabled,
|
|
16
16
|
} from "./runtime.js";
|
|
17
17
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
type BrowserSettingsPatch,
|
|
19
|
+
type BrowserSettingsSource,
|
|
20
|
+
loadSettings,
|
|
21
|
+
parseBrowserEndpoint,
|
|
22
|
+
type SettingsLoadResult,
|
|
23
|
+
saveBrowserSettings,
|
|
24
|
+
saveWebMcpSettings,
|
|
25
25
|
} from "./settings.js";
|
|
26
26
|
import { sanitizeChromeDevtoolsDisplay } from "./tool-selector.js";
|
|
27
27
|
|
|
28
28
|
type BrowserSettingsScreen = "settings" | "endpoint" | "executable" | "extensions";
|
|
29
29
|
type BrowserSettingsAction =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
| "open-endpoint"
|
|
31
|
+
| "save-endpoint"
|
|
32
|
+
| "set-auto-launch"
|
|
33
|
+
| "set-webmcp"
|
|
34
|
+
| "open-executable"
|
|
35
|
+
| "save-executable"
|
|
36
|
+
| "show-extensions";
|
|
37
37
|
|
|
38
38
|
interface BrowserSettingsMenuState {
|
|
39
|
-
|
|
39
|
+
load: SettingsLoadResult;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
type CommandContext = ExtensionCommandContext;
|
|
43
43
|
|
|
44
44
|
export async function showChromeDevtoolsBrowserSettings(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
pi: ExtensionAPI,
|
|
46
|
+
ctx: CommandContext,
|
|
47
|
+
generation: number,
|
|
48
48
|
): Promise<{ closeParent: boolean }> {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
notifyEffectiveSave(
|
|
230
|
-
ctx,
|
|
231
|
-
"browser executable",
|
|
232
|
-
saved.effectiveBrowser.executablePath ?? "automatic discovery",
|
|
233
|
-
saved.effectiveBrowser.executablePathSource,
|
|
234
|
-
);
|
|
235
|
-
return { kind: "back" };
|
|
236
|
-
},
|
|
237
|
-
"show-extensions": () => ({ kind: "to", screen: "extensions" }),
|
|
238
|
-
},
|
|
239
|
-
});
|
|
240
|
-
const result = await runMenu(ctx, menu, {
|
|
241
|
-
getState: async ({ signal }) => {
|
|
242
|
-
signal.throwIfAborted();
|
|
243
|
-
const load = await loadSettings({ cwd: ctx.cwd, projectTrusted });
|
|
244
|
-
signal.throwIfAborted();
|
|
245
|
-
return { load };
|
|
246
|
-
},
|
|
247
|
-
signal: ownerSignal,
|
|
248
|
-
isCurrent,
|
|
249
|
-
onError: (currentCtx, error) =>
|
|
250
|
-
currentCtx.ui.notify(
|
|
251
|
-
sanitizeChromeDevtoolsDisplay(
|
|
252
|
-
`Chrome DevTools browser settings failed: ${formatError(error)}`,
|
|
253
|
-
),
|
|
254
|
-
"error",
|
|
255
|
-
),
|
|
256
|
-
});
|
|
257
|
-
return { closeParent: result.kind === "closed" && result.reason === "close" };
|
|
49
|
+
setChromeDevtoolsSessionOwner(pi, ctx.sessionManager);
|
|
50
|
+
const ownerSignal = state.sessionController.signal;
|
|
51
|
+
const isCurrent = () => generation === state.sessionGeneration && !ownerSignal.aborted;
|
|
52
|
+
const projectTrusted = ctx.isProjectTrusted();
|
|
53
|
+
const menu = defineMenu<BrowserSettingsMenuState, BrowserSettingsScreen, BrowserSettingsAction>({
|
|
54
|
+
start: "settings",
|
|
55
|
+
screens: {
|
|
56
|
+
settings: ({ state: current }) => {
|
|
57
|
+
if (current.load.userFile.kind === "invalid") return invalidSettingsScreen(current.load);
|
|
58
|
+
const browser = current.load.effectiveBrowser;
|
|
59
|
+
return {
|
|
60
|
+
kind: "settings",
|
|
61
|
+
title: "Chrome DevTools Browser settings",
|
|
62
|
+
lines: settingsContextLines(current.load),
|
|
63
|
+
items: [
|
|
64
|
+
{
|
|
65
|
+
id: "endpoint",
|
|
66
|
+
label: "DevTools endpoint",
|
|
67
|
+
description: 'HTTP origin with an explicit port; enter "default" to restore 127.0.0.1:9222.',
|
|
68
|
+
currentValue: sanitizeChromeDevtoolsDisplay(browser.endpoint),
|
|
69
|
+
action: "open-endpoint",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "auto-launch",
|
|
73
|
+
label: "Auto-launch",
|
|
74
|
+
description: "Launch an isolated managed browser when a local endpoint is unavailable.",
|
|
75
|
+
currentValue: browser.autoLaunchEnabled ? "On" : "Off",
|
|
76
|
+
values: ["On", "Off"],
|
|
77
|
+
action: "set-auto-launch",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "executable",
|
|
81
|
+
label: "Browser executable",
|
|
82
|
+
description: 'Absolute executable path; enter "automatic" to restore browser discovery.',
|
|
83
|
+
currentValue: sanitizeChromeDevtoolsDisplay(browser.executablePath ?? "Automatic"),
|
|
84
|
+
action: "open-executable",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "webmcp",
|
|
88
|
+
label: "WebMCP · Experimental",
|
|
89
|
+
description: "Allow user-confirmed calls to page-provided WebMCP tools in this browser session.",
|
|
90
|
+
currentValue: current.load.effectiveWebMcpEnabled ? "On" : "Off",
|
|
91
|
+
values: ["On", "Off"],
|
|
92
|
+
action: "set-webmcp",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "extensions",
|
|
96
|
+
label: "Unpacked extensions",
|
|
97
|
+
description: "Inspect trusted extension paths configured in user or project JSON.",
|
|
98
|
+
currentValue: `${browser.extensionPaths.length} configured`,
|
|
99
|
+
action: "show-extensions",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
endpoint: ({ state: current }) => ({
|
|
105
|
+
kind: "input",
|
|
106
|
+
title: "DevTools endpoint",
|
|
107
|
+
lines: [
|
|
108
|
+
sanitizeChromeDevtoolsDisplay(`Current effective endpoint: ${current.load.effectiveBrowser.endpoint}`),
|
|
109
|
+
'Enter an HTTP origin with an explicit port, or "default".',
|
|
110
|
+
],
|
|
111
|
+
placeholder: "http://127.0.0.1:9222",
|
|
112
|
+
action: "save-endpoint",
|
|
113
|
+
hint: "back",
|
|
114
|
+
}),
|
|
115
|
+
executable: ({ state: current }) => ({
|
|
116
|
+
kind: "input",
|
|
117
|
+
title: "Browser executable",
|
|
118
|
+
lines: [
|
|
119
|
+
sanitizeChromeDevtoolsDisplay(
|
|
120
|
+
`Current effective executable: ${current.load.effectiveBrowser.executablePath ?? "automatic discovery"}`,
|
|
121
|
+
),
|
|
122
|
+
'Enter an absolute path, or "automatic".',
|
|
123
|
+
],
|
|
124
|
+
placeholder: "absolute browser executable path",
|
|
125
|
+
action: "save-executable",
|
|
126
|
+
hint: "back",
|
|
127
|
+
}),
|
|
128
|
+
extensions: ({ state: current }) => ({
|
|
129
|
+
kind: "detail",
|
|
130
|
+
title: "Unpacked extensions",
|
|
131
|
+
lines: extensionDetailLines(current.load),
|
|
132
|
+
hint: "back",
|
|
133
|
+
}),
|
|
134
|
+
},
|
|
135
|
+
actions: {
|
|
136
|
+
"open-endpoint": () => ({ kind: "to", screen: "endpoint" }),
|
|
137
|
+
"save-endpoint": async ({ value, signal }) => {
|
|
138
|
+
if (value === undefined) return { kind: "rejected" };
|
|
139
|
+
let endpoint: string | null;
|
|
140
|
+
try {
|
|
141
|
+
const trimmed = value.trim();
|
|
142
|
+
endpoint = trimmed.toLowerCase() === "default" ? null : parseBrowserEndpoint(trimmed).endpoint;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
notifySaveFailure(ctx, error);
|
|
145
|
+
return { kind: "rejected" };
|
|
146
|
+
}
|
|
147
|
+
const saved = await saveAndApplyBrowserPatch(ctx, generation, ownerSignal, signal, projectTrusted, {
|
|
148
|
+
endpoint,
|
|
149
|
+
});
|
|
150
|
+
if (!saved) return { kind: "rejected" };
|
|
151
|
+
notifyEffectiveSave(ctx, "endpoint", saved.effectiveBrowser.endpoint, saved.effectiveBrowser.endpointSource);
|
|
152
|
+
return { kind: "back" };
|
|
153
|
+
},
|
|
154
|
+
"set-auto-launch": async ({ value, signal }) => {
|
|
155
|
+
if (value !== "On" && value !== "Off") return { kind: "rejected" };
|
|
156
|
+
const saved = await saveAndApplyBrowserPatch(ctx, generation, ownerSignal, signal, projectTrusted, {
|
|
157
|
+
autoLaunch: value === "On",
|
|
158
|
+
});
|
|
159
|
+
if (!saved) return { kind: "rejected" };
|
|
160
|
+
notifyEffectiveSave(
|
|
161
|
+
ctx,
|
|
162
|
+
"auto-launch",
|
|
163
|
+
saved.effectiveBrowser.autoLaunchEnabled ? "On" : "Off",
|
|
164
|
+
saved.effectiveBrowser.autoLaunchSource,
|
|
165
|
+
);
|
|
166
|
+
return { kind: "stay" };
|
|
167
|
+
},
|
|
168
|
+
"set-webmcp": async ({ value, signal }) => {
|
|
169
|
+
if (value !== "On" && value !== "Off") return { kind: "rejected" };
|
|
170
|
+
const saved = await saveAndApplyWebMcpSetting(
|
|
171
|
+
pi,
|
|
172
|
+
ctx,
|
|
173
|
+
generation,
|
|
174
|
+
ownerSignal,
|
|
175
|
+
signal,
|
|
176
|
+
projectTrusted,
|
|
177
|
+
value === "On",
|
|
178
|
+
);
|
|
179
|
+
if (!saved) return { kind: "rejected" };
|
|
180
|
+
ctx.ui.notify(
|
|
181
|
+
saved.effectiveWebMcpEnabled
|
|
182
|
+
? "Experimental WebMCP enabled. Every page-provided tool call requires confirmation. Choose gateway availability under Browser tools."
|
|
183
|
+
: "Experimental WebMCP disabled. Active WebMCP work was aborted and both gateway tools are unavailable.",
|
|
184
|
+
"warning",
|
|
185
|
+
);
|
|
186
|
+
return { kind: "stay" };
|
|
187
|
+
},
|
|
188
|
+
"open-executable": () => ({ kind: "to", screen: "executable" }),
|
|
189
|
+
"save-executable": async ({ value, signal }) => {
|
|
190
|
+
if (value === undefined) return { kind: "rejected" };
|
|
191
|
+
const trimmed = value.trim();
|
|
192
|
+
const reset = ["automatic", "default", "none"].includes(trimmed.toLowerCase());
|
|
193
|
+
if (!reset && (!trimmed || !isAbsolute(trimmed))) {
|
|
194
|
+
notifySaveFailure(ctx, new Error('Enter an absolute path or "automatic".'));
|
|
195
|
+
return { kind: "rejected" };
|
|
196
|
+
}
|
|
197
|
+
const executablePath = reset ? null : trimmed;
|
|
198
|
+
const saved = await saveAndApplyBrowserPatch(ctx, generation, ownerSignal, signal, projectTrusted, {
|
|
199
|
+
executablePath,
|
|
200
|
+
});
|
|
201
|
+
if (!saved) return { kind: "rejected" };
|
|
202
|
+
notifyEffectiveSave(
|
|
203
|
+
ctx,
|
|
204
|
+
"browser executable",
|
|
205
|
+
saved.effectiveBrowser.executablePath ?? "automatic discovery",
|
|
206
|
+
saved.effectiveBrowser.executablePathSource,
|
|
207
|
+
);
|
|
208
|
+
return { kind: "back" };
|
|
209
|
+
},
|
|
210
|
+
"show-extensions": () => ({ kind: "to", screen: "extensions" }),
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
const result = await runMenu(ctx, menu, {
|
|
214
|
+
getState: async ({ signal }) => {
|
|
215
|
+
signal.throwIfAborted();
|
|
216
|
+
const load = await loadSettings({ cwd: ctx.cwd, projectTrusted });
|
|
217
|
+
signal.throwIfAborted();
|
|
218
|
+
return { load };
|
|
219
|
+
},
|
|
220
|
+
signal: ownerSignal,
|
|
221
|
+
isCurrent,
|
|
222
|
+
onError: (currentCtx, error) =>
|
|
223
|
+
currentCtx.ui.notify(
|
|
224
|
+
sanitizeChromeDevtoolsDisplay(`Chrome DevTools browser settings failed: ${formatError(error)}`),
|
|
225
|
+
"error",
|
|
226
|
+
),
|
|
227
|
+
});
|
|
228
|
+
return { closeParent: result.kind === "closed" && result.reason === "close" };
|
|
258
229
|
}
|
|
259
230
|
|
|
260
231
|
async function saveAndApplyBrowserPatch(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
232
|
+
ctx: CommandContext,
|
|
233
|
+
generation: number,
|
|
234
|
+
ownerSignal: AbortSignal,
|
|
235
|
+
actionSignal: AbortSignal,
|
|
236
|
+
projectTrusted: boolean,
|
|
237
|
+
patch: BrowserSettingsPatch,
|
|
267
238
|
) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
239
|
+
try {
|
|
240
|
+
await ctx.waitForIdle();
|
|
241
|
+
if (!isCurrent(generation, ownerSignal, actionSignal)) return false;
|
|
242
|
+
await saveBrowserSettings(patch);
|
|
243
|
+
if (!isCurrent(generation, ownerSignal, actionSignal)) return false;
|
|
244
|
+
invalidateWebMcpOperations(ctx.sessionManager, "Chrome DevTools browser configuration changed");
|
|
245
|
+
await shutdownManagedBrowser(undefined, { owner: ctx.sessionManager });
|
|
246
|
+
if (!isCurrent(generation, ownerSignal, actionSignal)) return false;
|
|
247
|
+
const loaded = await loadSettings({ cwd: ctx.cwd, projectTrusted });
|
|
248
|
+
if (!isCurrent(generation, ownerSignal, actionSignal)) return false;
|
|
249
|
+
applyRuntimeBrowserSettings(loaded.effectiveBrowser, loaded.paths, projectTrusted);
|
|
250
|
+
syncManagedBrowserSettings(ctx.sessionManager, loaded.effectiveBrowser);
|
|
251
|
+
state.settingsNotice = loaded.notice;
|
|
252
|
+
return loaded;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (isCurrent(generation, ownerSignal, actionSignal)) notifySaveFailure(ctx, error);
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
286
257
|
}
|
|
287
258
|
|
|
288
259
|
async function saveAndApplyWebMcpSetting(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
260
|
+
pi: ExtensionAPI,
|
|
261
|
+
ctx: CommandContext,
|
|
262
|
+
generation: number,
|
|
263
|
+
ownerSignal: AbortSignal,
|
|
264
|
+
actionSignal: AbortSignal,
|
|
265
|
+
projectTrusted: boolean,
|
|
266
|
+
enabled: boolean,
|
|
296
267
|
) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
268
|
+
const previousEnabled = webMcpEnabled(ctx.sessionManager);
|
|
269
|
+
const previousTools = configuredChromeDevtoolsTools(pi);
|
|
270
|
+
try {
|
|
271
|
+
await ctx.waitForIdle();
|
|
272
|
+
if (!isCurrent(generation, ownerSignal, actionSignal)) return false;
|
|
273
|
+
applyRuntimeWebMcpSetting(enabled, ctx.sessionManager);
|
|
274
|
+
applyAvailableChromeDevtoolsTools(pi, previousTools);
|
|
275
|
+
await saveWebMcpSettings(enabled);
|
|
276
|
+
if (!isOwnerCurrent(generation, ownerSignal)) return false;
|
|
277
|
+
const loaded = await loadSettings({ cwd: ctx.cwd, projectTrusted });
|
|
278
|
+
if (!isOwnerCurrent(generation, ownerSignal)) return false;
|
|
279
|
+
state.settingsNotice = loaded.notice;
|
|
280
|
+
return loaded;
|
|
281
|
+
} catch (error) {
|
|
282
|
+
if (!isOwnerCurrent(generation, ownerSignal)) return false;
|
|
283
|
+
let rollbackError: unknown;
|
|
284
|
+
try {
|
|
285
|
+
applyRuntimeWebMcpSetting(previousEnabled, ctx.sessionManager);
|
|
286
|
+
applyAvailableChromeDevtoolsTools(pi, previousTools);
|
|
287
|
+
} catch (caught) {
|
|
288
|
+
rollbackError = caught;
|
|
289
|
+
}
|
|
290
|
+
if (isCurrent(generation, ownerSignal, actionSignal)) {
|
|
291
|
+
notifySaveFailure(
|
|
292
|
+
ctx,
|
|
293
|
+
rollbackError
|
|
294
|
+
? new Error(`${formatError(error)}; WebMCP rollback failed: ${formatError(rollbackError)}`)
|
|
295
|
+
: error,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
331
300
|
}
|
|
332
301
|
|
|
333
302
|
function isOwnerCurrent(generation: number, ownerSignal: AbortSignal) {
|
|
334
|
-
|
|
303
|
+
return generation === state.sessionGeneration && !ownerSignal.aborted;
|
|
335
304
|
}
|
|
336
305
|
|
|
337
306
|
function isCurrent(generation: number, ownerSignal: AbortSignal, actionSignal: AbortSignal) {
|
|
338
|
-
|
|
307
|
+
return isOwnerCurrent(generation, ownerSignal) && !actionSignal.aborted;
|
|
339
308
|
}
|
|
340
309
|
|
|
341
310
|
function invalidSettingsScreen(load: SettingsLoadResult) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
],
|
|
354
|
-
hint: "back" as const,
|
|
355
|
-
};
|
|
311
|
+
const reason = load.userFile.kind === "invalid" ? load.userFile.reason : "The user settings file is unavailable.";
|
|
312
|
+
return {
|
|
313
|
+
kind: "detail" as const,
|
|
314
|
+
title: "Chrome DevTools Browser settings · Read only",
|
|
315
|
+
lines: [
|
|
316
|
+
sanitizeChromeDevtoolsDisplay(
|
|
317
|
+
`Fix the active user settings before saving; the file will not be overwritten. ${reason}`,
|
|
318
|
+
),
|
|
319
|
+
],
|
|
320
|
+
hint: "back" as const,
|
|
321
|
+
};
|
|
356
322
|
}
|
|
357
323
|
|
|
358
324
|
function settingsContextLines(load: SettingsLoadResult) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
325
|
+
const browser = load.effectiveBrowser;
|
|
326
|
+
return [
|
|
327
|
+
`User settings · ${load.paths.user}`,
|
|
328
|
+
`Effective sources: endpoint ${browser.endpointSource} · auto-launch ${browser.autoLaunchSource} · executable ${browser.executablePathSource}`,
|
|
329
|
+
...(load.notice ? [`Warning: ${load.notice}`] : []),
|
|
330
|
+
].map((line) => sanitizeChromeDevtoolsDisplay(line));
|
|
365
331
|
}
|
|
366
332
|
|
|
367
333
|
function extensionDetailLines(load: SettingsLoadResult) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
334
|
+
const browser = load.effectiveBrowser;
|
|
335
|
+
return [
|
|
336
|
+
`Effective source: ${browser.extensionPathsSource}`,
|
|
337
|
+
...(browser.extensionPaths.length > 0
|
|
338
|
+
? browser.extensionPaths.map((extensionPath) => `- ${extensionPath}`)
|
|
339
|
+
: ["No unpacked extensions are configured."]),
|
|
340
|
+
"Edit browser.extensionPaths in the user JSON or a trusted project JSON, then run /reload.",
|
|
341
|
+
"Unpacked extensions execute privileged browser code; load only paths you trust.",
|
|
342
|
+
].map((line) => sanitizeChromeDevtoolsDisplay(line));
|
|
377
343
|
}
|
|
378
344
|
|
|
379
345
|
function notifyEffectiveSave(
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
346
|
+
ctx: CommandContext,
|
|
347
|
+
setting: string,
|
|
348
|
+
effectiveValue: string,
|
|
349
|
+
source: BrowserSettingsSource,
|
|
384
350
|
) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
);
|
|
351
|
+
const overrideNotice = source === "environment" ? " The deprecated environment override remains effective." : "";
|
|
352
|
+
ctx.ui.notify(
|
|
353
|
+
sanitizeChromeDevtoolsDisplay(
|
|
354
|
+
`Chrome DevTools ${setting} saved. Effective ${setting}: ${effectiveValue} (${source}).${overrideNotice}`,
|
|
355
|
+
),
|
|
356
|
+
"info",
|
|
357
|
+
);
|
|
393
358
|
}
|
|
394
359
|
|
|
395
360
|
function notifySaveFailure(ctx: CommandContext, error: unknown) {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
361
|
+
ctx.ui.notify(
|
|
362
|
+
sanitizeChromeDevtoolsDisplay(
|
|
363
|
+
`Chrome DevTools browser settings save failed; previous settings remain active: ${formatError(error)}`,
|
|
364
|
+
),
|
|
365
|
+
"warning",
|
|
366
|
+
);
|
|
402
367
|
}
|
|
403
368
|
|
|
404
369
|
function formatError(error: unknown) {
|
|
405
|
-
|
|
370
|
+
return error instanceof Error ? error.message : String(error);
|
|
406
371
|
}
|