@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
package/src/chrome-devtools.ts
CHANGED
|
@@ -1,285 +1,258 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
3
|
-
shutdownManagedBrowser,
|
|
4
|
-
startManagedBrowserSession,
|
|
5
|
-
syncManagedBrowserSettings,
|
|
6
|
-
} from "./browser-manager.js";
|
|
2
|
+
import { shutdownManagedBrowser, startManagedBrowserSession, syncManagedBrowserSettings } from "./browser-manager.js";
|
|
7
3
|
import { setActivePageId } from "./cdp-client.js";
|
|
8
4
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
availableChromeDevtoolsTools,
|
|
6
|
+
configureChromeDevtoolsToolExposure,
|
|
7
|
+
createChromeDevtoolsLoadTool,
|
|
8
|
+
initializeAvailableChromeDevtoolsTools,
|
|
9
|
+
requireEagerChromeDevtoolsToolExposure,
|
|
10
|
+
setChromeDevtoolsSessionOwner,
|
|
11
|
+
supportsNativeDeferredToolLoading,
|
|
16
12
|
} from "./lazy-tools.js";
|
|
17
13
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
applyRuntimeBrowserSettings,
|
|
15
|
+
applyRuntimeWebMcpSetting,
|
|
16
|
+
invalidateWebMcpOperations,
|
|
17
|
+
setWebMcpSessionOwner,
|
|
18
|
+
state,
|
|
19
|
+
webMcpEnabled,
|
|
24
20
|
} from "./runtime.js";
|
|
25
21
|
import { loadSettings, waitForSettingsWrites } from "./settings.js";
|
|
26
22
|
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
allChromeDevtoolsTools,
|
|
24
|
+
buildCommandGuide,
|
|
25
|
+
buildQuickstartMessage,
|
|
26
|
+
buildToolStatusMessage,
|
|
27
|
+
sanitizeChromeDevtoolsDisplay,
|
|
28
|
+
updateChromeDevtoolsTools,
|
|
29
|
+
waitForChromeDevtoolsSettings,
|
|
34
30
|
} from "./tool-selector.js";
|
|
35
31
|
import {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
32
|
+
evaluateTool,
|
|
33
|
+
listPagesTool,
|
|
34
|
+
navigateTool,
|
|
35
|
+
screenshotTool,
|
|
36
|
+
selectPageTool,
|
|
37
|
+
webMcpCallTool,
|
|
38
|
+
webMcpListToolsTool,
|
|
43
39
|
} from "./tools.js";
|
|
44
40
|
|
|
45
|
-
type CommandAction =
|
|
46
|
-
| "menu"
|
|
47
|
-
| "help"
|
|
48
|
-
| "quickstart"
|
|
49
|
-
| "status"
|
|
50
|
-
| "settings"
|
|
51
|
-
| "tools"
|
|
52
|
-
| "enable"
|
|
53
|
-
| "disable";
|
|
41
|
+
type CommandAction = "menu" | "help" | "quickstart" | "status" | "settings" | "tools" | "enable" | "disable";
|
|
54
42
|
type CommandContext = ExtensionCommandContext;
|
|
55
43
|
const STATUS_KEY = "chrome-devtools";
|
|
56
44
|
const COMMAND_COMPLETIONS = [
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
45
|
+
{ value: "help", label: "help", description: "Show command usage" },
|
|
46
|
+
{ value: "quickstart", label: "quickstart", description: "Show endpoint and launch help" },
|
|
47
|
+
{ value: "status", label: "status", description: "Show tool and settings status" },
|
|
48
|
+
{ value: "settings", label: "settings", description: "Edit browser connection settings" },
|
|
49
|
+
{ value: "tools", label: "tools", description: "Choose available Chrome DevTools tools" },
|
|
50
|
+
{ value: "toggle", label: "toggle", description: "Alias for tools" },
|
|
51
|
+
{ value: "select", label: "select", description: "Compatibility alias for tools" },
|
|
52
|
+
{ value: "enable", label: "enable", description: "Make all Chrome DevTools tools available" },
|
|
53
|
+
{ value: "on", label: "on", description: "Compatibility alias for enable" },
|
|
54
|
+
{ value: "disable", label: "disable", description: "Make all Chrome DevTools tools unavailable" },
|
|
55
|
+
{ value: "off", label: "off", description: "Compatibility alias for disable" },
|
|
68
56
|
];
|
|
69
57
|
export default function chromeDevtools(pi: ExtensionAPI) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
58
|
+
pi.registerTool(listPagesTool);
|
|
59
|
+
pi.registerTool(selectPageTool);
|
|
60
|
+
pi.registerTool(navigateTool);
|
|
61
|
+
pi.registerTool(evaluateTool);
|
|
62
|
+
pi.registerTool(screenshotTool);
|
|
63
|
+
pi.registerTool(webMcpListToolsTool);
|
|
64
|
+
pi.registerTool(webMcpCallTool);
|
|
65
|
+
pi.registerTool(createChromeDevtoolsLoadTool(pi));
|
|
78
66
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
67
|
+
pi.registerCommand("chrome-devtools", {
|
|
68
|
+
description: "Open Chrome DevTools help and tool controls",
|
|
69
|
+
getArgumentCompletions: (prefix) => commandCompletions(prefix),
|
|
70
|
+
handler: async (args, ctx) => {
|
|
71
|
+
setChromeDevtoolsSessionOwner(pi, ctx.sessionManager);
|
|
72
|
+
initializeAvailableChromeDevtoolsTools(pi);
|
|
73
|
+
const generation = state.sessionGeneration;
|
|
74
|
+
await handleChromeDevtoolsCommand(pi, args, ctx, generation);
|
|
75
|
+
},
|
|
76
|
+
});
|
|
89
77
|
|
|
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
|
-
|
|
78
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
79
|
+
const generation = ++state.sessionGeneration;
|
|
80
|
+
setChromeDevtoolsSessionOwner(pi, ctx.sessionManager);
|
|
81
|
+
initializeAvailableChromeDevtoolsTools(pi);
|
|
82
|
+
setWebMcpSessionOwner(ctx.sessionManager);
|
|
83
|
+
replaceSessionController("Chrome DevTools session replaced");
|
|
84
|
+
invalidateWebMcpOperations(ctx.sessionManager, "Chrome DevTools session replaced");
|
|
85
|
+
state.shuttingDown = false;
|
|
86
|
+
state.settingsNotice = undefined;
|
|
87
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
88
|
+
await shutdownManagedBrowser(undefined, {
|
|
89
|
+
cancelLaunch: true,
|
|
90
|
+
owner: ctx.sessionManager,
|
|
91
|
+
});
|
|
92
|
+
startManagedBrowserSession(ctx.sessionManager);
|
|
93
|
+
if (generation !== state.sessionGeneration) return;
|
|
94
|
+
setActivePageId(ctx.sessionManager, undefined);
|
|
95
|
+
state.activePageId = undefined;
|
|
96
|
+
state.lastLaunchAttempt = undefined;
|
|
97
|
+
const projectTrusted = ctx.isProjectTrusted();
|
|
98
|
+
const settings = await loadSettings({ cwd: ctx.cwd, projectTrusted });
|
|
99
|
+
if (generation !== state.sessionGeneration) return;
|
|
100
|
+
applyRuntimeBrowserSettings(settings.effectiveBrowser, settings.paths, projectTrusted);
|
|
101
|
+
syncManagedBrowserSettings(ctx.sessionManager, settings.effectiveBrowser);
|
|
102
|
+
applyRuntimeWebMcpSetting(settings.effectiveWebMcpEnabled, ctx.sessionManager);
|
|
103
|
+
state.settingsNotice = settings.notice;
|
|
104
|
+
for (const warning of settings.warnings) {
|
|
105
|
+
ctx.ui.notify(sanitizeChromeDevtoolsDisplay(warning), "warning");
|
|
106
|
+
}
|
|
107
|
+
if (webMcpEnabled(ctx.sessionManager)) {
|
|
108
|
+
ctx.ui.notify(
|
|
109
|
+
"Experimental WebMCP is enabled. Page-provided tools use the visible browser session and require confirmation for every call.",
|
|
110
|
+
"warning",
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
const availableTools =
|
|
114
|
+
settings.kind === "loaded" && settings.settings.tools
|
|
115
|
+
? settings.settings.tools
|
|
116
|
+
: availableChromeDevtoolsTools(pi);
|
|
117
|
+
configureChromeDevtoolsToolExposure(pi, availableTools, ctx.model);
|
|
118
|
+
});
|
|
131
119
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
requireEagerChromeDevtoolsToolExposure(pi);
|
|
139
|
-
}
|
|
140
|
-
});
|
|
120
|
+
pi.on("model_select", (event, ctx) => {
|
|
121
|
+
invalidateWebMcpOperations(ctx.sessionManager, "Chrome DevTools model and tool exposure changed");
|
|
122
|
+
if (!supportsNativeDeferredToolLoading(event.model)) {
|
|
123
|
+
requireEagerChromeDevtoolsToolExposure(pi);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
141
126
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
127
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
128
|
+
state.sessionGeneration += 1;
|
|
129
|
+
replaceSessionController("Chrome DevTools session shut down");
|
|
130
|
+
invalidateWebMcpOperations(ctx.sessionManager, "Chrome DevTools session shut down");
|
|
131
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
132
|
+
const browserShutdown = shutdownManagedBrowser(undefined, {
|
|
133
|
+
cancelLaunch: true,
|
|
134
|
+
owner: ctx.sessionManager,
|
|
135
|
+
});
|
|
136
|
+
await waitForChromeDevtoolsSettings();
|
|
137
|
+
await waitForSettingsWrites();
|
|
138
|
+
await browserShutdown;
|
|
139
|
+
});
|
|
155
140
|
}
|
|
156
141
|
|
|
157
|
-
async function handleChromeDevtoolsCommand(
|
|
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
|
-
pi,
|
|
204
|
-
ctx,
|
|
205
|
-
allChromeDevtoolsTools(ctx.sessionManager),
|
|
206
|
-
"made all available",
|
|
207
|
-
);
|
|
208
|
-
return;
|
|
209
|
-
case "disable":
|
|
210
|
-
await updateChromeDevtoolsTools(pi, ctx, [], "made all unavailable");
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
142
|
+
async function handleChromeDevtoolsCommand(pi: ExtensionAPI, args: string, ctx: CommandContext, generation: number) {
|
|
143
|
+
const command = parseCommand(args);
|
|
144
|
+
switch (command) {
|
|
145
|
+
case "menu":
|
|
146
|
+
await showMenu(pi, ctx, generation);
|
|
147
|
+
return;
|
|
148
|
+
case "help":
|
|
149
|
+
requireObservableUi(ctx, "help");
|
|
150
|
+
ctx.ui.notify(buildCommandGuide(ctx.sessionManager), "info");
|
|
151
|
+
return;
|
|
152
|
+
case "quickstart":
|
|
153
|
+
requireObservableUi(ctx, "quickstart");
|
|
154
|
+
ctx.ui.notify(buildQuickstartMessage(ctx.sessionManager), "info");
|
|
155
|
+
return;
|
|
156
|
+
case "status": {
|
|
157
|
+
requireObservableUi(ctx, "status");
|
|
158
|
+
const status = await buildToolStatusMessage(pi, ctx.sessionManager);
|
|
159
|
+
if (generation !== state.sessionGeneration) return;
|
|
160
|
+
ctx.ui.notify(status, "info");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
case "settings": {
|
|
164
|
+
if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) {
|
|
165
|
+
throw new Error("/chrome-devtools settings requires TUI or RPC mode");
|
|
166
|
+
}
|
|
167
|
+
const { showChromeDevtoolsBrowserSettings } = await import("./browser-settings-menu.js");
|
|
168
|
+
if (generation !== state.sessionGeneration) return;
|
|
169
|
+
await showChromeDevtoolsBrowserSettings(pi, ctx, generation);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
case "tools": {
|
|
173
|
+
if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) {
|
|
174
|
+
throw new Error("/chrome-devtools tools requires TUI or RPC mode");
|
|
175
|
+
}
|
|
176
|
+
const { showChromeDevtoolsToolWorkflow } = await import("./menu.js");
|
|
177
|
+
if (generation !== state.sessionGeneration) return;
|
|
178
|
+
await showChromeDevtoolsToolWorkflow(pi, ctx, generation);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
case "enable":
|
|
182
|
+
await updateChromeDevtoolsTools(pi, ctx, allChromeDevtoolsTools(ctx.sessionManager), "made all available");
|
|
183
|
+
return;
|
|
184
|
+
case "disable":
|
|
185
|
+
await updateChromeDevtoolsTools(pi, ctx, [], "made all unavailable");
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
213
188
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
189
|
+
if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) {
|
|
190
|
+
throw new Error(`Unknown /chrome-devtools command: ${args.trim()}`);
|
|
191
|
+
}
|
|
192
|
+
ctx.ui.notify(
|
|
193
|
+
`Unknown /chrome-devtools command: ${args.trim()}
|
|
219
194
|
|
|
220
195
|
${buildCommandGuide(ctx.sessionManager)}`,
|
|
221
|
-
|
|
222
|
-
|
|
196
|
+
"warning",
|
|
197
|
+
);
|
|
223
198
|
}
|
|
224
199
|
|
|
225
200
|
function requireObservableUi(ctx: CommandContext, route: string) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
201
|
+
if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) {
|
|
202
|
+
throw new Error(`/chrome-devtools ${route} requires TUI or RPC mode`);
|
|
203
|
+
}
|
|
229
204
|
}
|
|
230
205
|
|
|
231
206
|
async function showMenu(pi: ExtensionAPI, ctx: CommandContext, generation: number) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
207
|
+
if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) {
|
|
208
|
+
throw new Error("/chrome-devtools menu requires TUI or RPC mode; use a direct subcommand");
|
|
209
|
+
}
|
|
210
|
+
const { showChromeDevtoolsMenu } = await import("./menu.js");
|
|
211
|
+
if (generation !== state.sessionGeneration) return;
|
|
212
|
+
await showChromeDevtoolsMenu(pi, ctx, generation);
|
|
238
213
|
}
|
|
239
214
|
|
|
240
215
|
function replaceSessionController(reason: string) {
|
|
241
|
-
|
|
242
|
-
|
|
216
|
+
state.sessionController.abort(new DOMException(reason, "AbortError"));
|
|
217
|
+
state.sessionController = new AbortController();
|
|
243
218
|
}
|
|
244
219
|
|
|
245
220
|
export function parseCommand(args: string): CommandAction | "unknown" {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
221
|
+
const command = args.trim().toLowerCase();
|
|
222
|
+
if (!command) return "menu";
|
|
223
|
+
if (command === "help") return "help";
|
|
224
|
+
if (command === "quickstart") return "quickstart";
|
|
225
|
+
if (command === "status") return "status";
|
|
226
|
+
if (command === "settings") return "settings";
|
|
227
|
+
if (command === "tools" || command === "select" || command === "toggle") return "tools";
|
|
228
|
+
if (command === "enable" || command === "on") return "enable";
|
|
229
|
+
if (command === "disable" || command === "off") return "disable";
|
|
230
|
+
return "unknown";
|
|
256
231
|
}
|
|
257
232
|
|
|
258
233
|
export function commandCompletions(prefix: string) {
|
|
259
|
-
|
|
260
|
-
|
|
234
|
+
const normalized = prefix.trimStart().toLowerCase();
|
|
235
|
+
if (/\s/.test(normalized)) return null;
|
|
261
236
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
);
|
|
265
|
-
return matches.length > 0 ? matches : null;
|
|
237
|
+
const matches = COMMAND_COMPLETIONS.filter((completion) => completion.value.startsWith(normalized));
|
|
238
|
+
return matches.length > 0 ? matches : null;
|
|
266
239
|
}
|
|
267
240
|
|
|
268
241
|
export {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
242
|
+
formatHostForUrl,
|
|
243
|
+
isLocalDevToolsHost,
|
|
244
|
+
quoteCommandPart,
|
|
272
245
|
} from "./browser-manager.js";
|
|
273
246
|
export { CHROME_DEVTOOLS_LOAD_TOOL_NAME } from "./lazy-tools.js";
|
|
274
247
|
export { parseConfiguredPort } from "./runtime.js";
|
|
275
248
|
export {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
249
|
+
hasParentPathSegment,
|
|
250
|
+
isPathInsideRoot,
|
|
251
|
+
resolveScreenshotPath,
|
|
252
|
+
selectAllowedRoot,
|
|
280
253
|
} from "./screenshot.js";
|
|
281
254
|
export { normalizeChromeDevtoolsSettings } from "./settings.js";
|
|
282
255
|
export {
|
|
283
|
-
|
|
284
|
-
|
|
256
|
+
orderedChromeDevtoolsTools,
|
|
257
|
+
sanitizeChromeDevtoolsDisplay,
|
|
285
258
|
} from "./tool-selector.js";
|
package/src/display.ts
CHANGED
|
@@ -2,37 +2,34 @@ const MAX_SANITIZER_INPUT_CODE_UNITS = 50_000;
|
|
|
2
2
|
const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
3
3
|
|
|
4
4
|
export function sanitizeChromeDevtoolsDisplay(value: string, maxCharacters = 50_000) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}).join("");
|
|
22
|
-
const outputLimit = Math.min(maxCharacters, MAX_SANITIZER_INPUT_CODE_UNITS);
|
|
23
|
-
if (!inputWasTruncated && sanitized.length <= outputLimit) return sanitized;
|
|
5
|
+
const inputWasTruncated = value.length > MAX_SANITIZER_INPUT_CODE_UNITS;
|
|
6
|
+
const boundedInput = inputWasTruncated ? truncateAtGraphemeBoundary(value, MAX_SANITIZER_INPUT_CODE_UNITS) : value;
|
|
7
|
+
const normalizedLineEndings = boundedInput.replace(/\r\n/g, "\n");
|
|
8
|
+
// Neutralize controls without parsing untrusted terminal sequences; keep printable payload visible.
|
|
9
|
+
const sanitized = Array.from(normalizedLineEndings, (character) => {
|
|
10
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
11
|
+
const unsafeControl =
|
|
12
|
+
(codePoint >= 0 && codePoint <= 8) ||
|
|
13
|
+
(codePoint >= 11 && codePoint <= 31) ||
|
|
14
|
+
(codePoint >= 127 && codePoint <= 159);
|
|
15
|
+
const unsafeBidi = (codePoint >= 0x202a && codePoint <= 0x202e) || (codePoint >= 0x2066 && codePoint <= 0x2069);
|
|
16
|
+
const loneSurrogate = character.length === 1 && codePoint >= 0xd800 && codePoint <= 0xdfff;
|
|
17
|
+
return unsafeControl || unsafeBidi || loneSurrogate ? "�" : character;
|
|
18
|
+
}).join("");
|
|
19
|
+
const outputLimit = Math.min(maxCharacters, MAX_SANITIZER_INPUT_CODE_UNITS);
|
|
20
|
+
if (!inputWasTruncated && sanitized.length <= outputLimit) return sanitized;
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
return `${truncateAtGraphemeBoundary(sanitized, Math.max(0, outputLimit - 1))}…`;
|
|
26
23
|
}
|
|
27
24
|
|
|
28
25
|
function truncateAtGraphemeBoundary(value: string, maxCodeUnits: number) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
if (value.length <= maxCodeUnits) return value;
|
|
27
|
+
const boundedLookahead = value.slice(0, Math.max(0, maxCodeUnits) + 2);
|
|
28
|
+
let safeEnd = 0;
|
|
29
|
+
for (const { index, segment } of graphemeSegmenter.segment(boundedLookahead)) {
|
|
30
|
+
const segmentEnd = index + segment.length;
|
|
31
|
+
if (segmentEnd > maxCodeUnits) break;
|
|
32
|
+
safeEnd = segmentEnd;
|
|
33
|
+
}
|
|
34
|
+
return value.slice(0, safeEnd);
|
|
38
35
|
}
|