@mcp-b/chrome-devtools-mcp 2.0.2 → 2.0.6
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 +120 -10
- package/build/src/McpContext.js +69 -5
- package/build/src/browser.js +157 -51
- package/build/src/cli.js +11 -5
- package/build/src/formatters/IssueFormatter.js +190 -0
- package/build/src/main.js +83 -2
- package/build/src/telemetry/clearcut-logger.js +102 -0
- package/build/src/telemetry/flag-utils.js +45 -0
- package/build/src/telemetry/metric-utils.js +14 -0
- package/build/src/telemetry/persistence.js +53 -0
- package/build/src/telemetry/types.js +33 -0
- package/build/src/telemetry/watchdog/clearcut-sender.js +201 -0
- package/build/src/telemetry/watchdog/main.js +127 -0
- package/build/src/telemetry/watchdog-client.js +60 -0
- package/build/src/third_party/devtools-formatter-worker.js +7 -0
- package/build/src/third_party/index.js +1 -1
- package/build/src/tools/browser.js +92 -0
- package/build/src/tools/extension.js +31 -0
- package/build/src/tools/extensions.js +79 -0
- package/build/src/tools/input.js +6 -1
- package/build/src/tools/pages.js +7 -1
- package/build/src/tools/script.js +31 -4
- package/build/src/tools/tools.js +4 -0
- package/build/src/transports/CDPClientTransport.js +184 -0
- package/build/src/transports/WebMCPBridgeScript.js +11 -2
- package/build/src/utils/ExtensionRegistry.js +35 -0
- package/build/src/utils/string.js +36 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/common/Base64.js +20 -2
- package/build/vendor/chrome-devtools-frontend/front_end/core/common/Debouncer.js +8 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/common/Gzip.js +11 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/common/Object.js +6 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/common/ParsedURL.js +3 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/common/ResourceType.js +6 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/common/Revealer.js +0 -5
- package/build/vendor/chrome-devtools-frontend/front_end/core/common/Settings.js +18 -8
- package/build/vendor/chrome-devtools-frontend/front_end/core/host/AidaClient.js +24 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHostStub.js +11 -3
- package/build/vendor/chrome-devtools-frontend/front_end/core/host/ResourceLoader.js +1 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +27 -20
- package/build/vendor/chrome-devtools-frontend/front_end/core/i18n/collect-ui-strings.js +7 -8
- package/build/vendor/chrome-devtools-frontend/front_end/core/i18n/generate-locales-js.js +4 -5
- package/build/vendor/chrome-devtools-frontend/front_end/core/platform/ArrayUtilities.js +10 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/platform/StringUtilities.js +63 -12
- package/build/vendor/chrome-devtools-frontend/front_end/core/protocol_client/CDPConnection.js +1 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/protocol_client/InspectorBackend.js +4 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/root/ExperimentNames.js +30 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/root/root.js +2 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/AnimationModel.js +0 -4
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSMatchedStyles.js +69 -9
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSMetadata.js +6 -6
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSModel.js +28 -13
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSProperty.js +1 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSPropertyParserMatchers.js +6 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/ConsoleModel.js +0 -2
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CookieModel.js +1 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/DOMModel.js +170 -13
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/DebuggerModel.js +5 -39
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/HeapProfilerModel.js +8 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/NetworkManager.js +20 -5
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/NetworkRequest.js +12 -21
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/OverlayModel.js +19 -6
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/RehydratingConnection.js +5 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/ResourceTreeModel.js +8 -5
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/SourceMap.js +15 -10
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/SourceMapManager.js +1 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/SourceMapScopesInfo.js +13 -27
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/Target.js +3 -1
- package/build/vendor/chrome-devtools-frontend/front_end/generated/ARIAProperties.js +1 -7
- package/build/vendor/chrome-devtools-frontend/front_end/generated/Deprecation.js +1 -16
- package/build/vendor/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +82 -22
- package/build/vendor/chrome-devtools-frontend/front_end/generated/SupportedCSSProperties.js +265 -123
- package/build/vendor/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.js +2 -1
- package/build/vendor/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js +10 -16
- package/build/vendor/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js +97 -26
- package/build/vendor/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AICallTree.js +35 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/annotations/AnnotationRepository.js +163 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/annotations/AnnotationType.js +10 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/annotations/annotations.js +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/bindings/CompilerScriptMapping.js +5 -3
- package/build/vendor/chrome-devtools-frontend/front_end/models/bindings/DebuggerLanguagePlugins.js +29 -58
- package/build/vendor/chrome-devtools-frontend/front_end/models/bindings/DebuggerWorkspaceBinding.js +7 -45
- package/build/vendor/chrome-devtools-frontend/front_end/models/emulation/DeviceModeModel.js +1 -1
- package/build/vendor/chrome-devtools-frontend/front_end/models/emulation/EmulatedDevices.js +14 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/formatter/FormatterWorkerPool.js +8 -5
- package/build/vendor/chrome-devtools-frontend/front_end/models/greendev/Prototypes.js +33 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/greendev/greendev.js +4 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/ContrastCheckTrigger.js +2 -2
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/CookieIssue.js +0 -21
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/CorsIssue.js +1 -38
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/IssueAggregator.js +8 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/IssuesManager.js +6 -12
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/PermissionElementIssue.js +243 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementActivationDisabled.md +7 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementActivationDisabledWithOccluder.md +9 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementActivationDisabledWithOccluderParent.md +9 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementCspFrameAncestorsMissing.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementFencedFrameDisallowed.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementFontSizeTooLarge.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementFontSizeTooSmall.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementGeolocationDeprecated.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInsetBoxShadowUnsupported.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInvalidDisplayStyle.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInvalidSizeValue.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInvalidType.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInvalidTypeActivation.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementLowContrast.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementNonOpaqueColor.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementPaddingBottomUnsupported.md +6 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementPaddingRightUnsupported.md +6 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementPermissionsPolicyBlocked.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementRegistrationFailed.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementRequestInProgress.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementSecurityChecksFailed.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementTypeNotSupported.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementUntrustedEvent.md +7 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/issues_manager.js +2 -1
- package/build/vendor/chrome-devtools-frontend/front_end/models/logs/NetworkLog.js +0 -8
- package/build/vendor/chrome-devtools-frontend/front_end/models/source_map_scopes/NamesResolver.js +4 -8
- package/build/vendor/chrome-devtools-frontend/front_end/models/stack_trace/StackTrace.js +30 -1
- package/build/vendor/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceImpl.js +70 -1
- package/build/vendor/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceModel.js +82 -30
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/EventsSerializer.js +10 -2
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/LanternComputationData.js +2 -2
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/ModelImpl.js +0 -3
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/Processor.js +18 -19
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/Styles.js +12 -4
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/extras/Initiators.js +46 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/extras/TraceTree.js +4 -3
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/extras/extras.js +1 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/LargestImagePaintHandler.js +2 -2
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/LayoutShiftsHandler.js +1 -1
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/MetaHandler.js +6 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/NetworkRequestsHandler.js +10 -1
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/PageLoadMetricsHandler.js +44 -27
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/helpers/Timing.js +9 -2
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/Common.js +1 -6
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/LCPBreakdown.js +2 -2
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/LCPDiscovery.js +2 -4
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/NetworkDependencyTree.js +3 -2
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/RenderBlocking.js +1 -1
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.js +33 -11
- package/build/vendor/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js +51 -18
- package/build/vendor/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js +1 -1
- package/build/vendor/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/scopes.js +4 -0
- package/build/vendor/chrome-devtools-frontend/mcp/HostBindings.js +4 -0
- package/build/vendor/chrome-devtools-frontend/mcp/mcp.js +4 -0
- package/package.json +28 -21
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/SameSiteInvalidSameParty.md +0 -8
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/SameSiteSamePartyCrossPartyContextSet.md +0 -10
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import crypto from 'node:crypto';
|
|
7
|
+
import { logger } from '../../logger.js';
|
|
8
|
+
const MAX_BUFFER_SIZE = 1000;
|
|
9
|
+
const DEFAULT_CLEARCUT_ENDPOINT = 'https://play.googleapis.com/log?format=json_proto';
|
|
10
|
+
const DEFAULT_FLUSH_INTERVAL_MS = 15 * 60 * 1000;
|
|
11
|
+
const LOG_SOURCE = 2839;
|
|
12
|
+
const CLIENT_TYPE = 47;
|
|
13
|
+
const MIN_RATE_LIMIT_WAIT_MS = 30_000;
|
|
14
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
15
|
+
const SHUTDOWN_TIMEOUT_MS = 5_000;
|
|
16
|
+
const SESSION_ROTATION_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
17
|
+
export class ClearcutSender {
|
|
18
|
+
#appVersion;
|
|
19
|
+
#osType;
|
|
20
|
+
#clearcutEndpoint;
|
|
21
|
+
#flushIntervalMs;
|
|
22
|
+
#includePidHeader;
|
|
23
|
+
#sessionId;
|
|
24
|
+
#sessionCreated;
|
|
25
|
+
#buffer = [];
|
|
26
|
+
#flushTimer = null;
|
|
27
|
+
#isFlushing = false;
|
|
28
|
+
#timerStarted = false;
|
|
29
|
+
constructor(config) {
|
|
30
|
+
this.#appVersion = config.appVersion;
|
|
31
|
+
this.#osType = config.osType;
|
|
32
|
+
this.#clearcutEndpoint =
|
|
33
|
+
config.clearcutEndpoint ?? DEFAULT_CLEARCUT_ENDPOINT;
|
|
34
|
+
this.#flushIntervalMs =
|
|
35
|
+
config.forceFlushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
36
|
+
this.#includePidHeader = config.includePidHeader ?? false;
|
|
37
|
+
this.#sessionId = crypto.randomUUID();
|
|
38
|
+
this.#sessionCreated = Date.now();
|
|
39
|
+
}
|
|
40
|
+
enqueueEvent(event) {
|
|
41
|
+
if (Date.now() - this.#sessionCreated > SESSION_ROTATION_INTERVAL_MS) {
|
|
42
|
+
this.#sessionId = crypto.randomUUID();
|
|
43
|
+
this.#sessionCreated = Date.now();
|
|
44
|
+
}
|
|
45
|
+
logger('Enqueing telemetry event', JSON.stringify(event, null, 2));
|
|
46
|
+
this.#addToBuffer({
|
|
47
|
+
...event,
|
|
48
|
+
session_id: this.#sessionId,
|
|
49
|
+
app_version: this.#appVersion,
|
|
50
|
+
os_type: this.#osType,
|
|
51
|
+
});
|
|
52
|
+
if (!this.#timerStarted) {
|
|
53
|
+
this.#timerStarted = true;
|
|
54
|
+
this.#scheduleFlush(this.#flushIntervalMs);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async sendShutdownEvent() {
|
|
58
|
+
if (this.#flushTimer) {
|
|
59
|
+
clearTimeout(this.#flushTimer);
|
|
60
|
+
this.#flushTimer = null;
|
|
61
|
+
}
|
|
62
|
+
const shutdownEvent = {
|
|
63
|
+
server_shutdown: {},
|
|
64
|
+
};
|
|
65
|
+
this.enqueueEvent(shutdownEvent);
|
|
66
|
+
try {
|
|
67
|
+
await Promise.race([
|
|
68
|
+
this.#finalFlush(),
|
|
69
|
+
new Promise(resolve => setTimeout(resolve, SHUTDOWN_TIMEOUT_MS)),
|
|
70
|
+
]);
|
|
71
|
+
logger('Final flush completed');
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
logger('Final flush failed:', error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async #flush() {
|
|
78
|
+
if (this.#isFlushing) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (this.#buffer.length === 0) {
|
|
82
|
+
this.#scheduleFlush(this.#flushIntervalMs);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
this.#isFlushing = true;
|
|
86
|
+
let nextDelayMs = this.#flushIntervalMs;
|
|
87
|
+
// Optimistically remove events from buffer before sending.
|
|
88
|
+
// This prevents race conditions where a simultaneous #finalFlush would include these same events.
|
|
89
|
+
const eventsToSend = [...this.#buffer];
|
|
90
|
+
this.#buffer = [];
|
|
91
|
+
try {
|
|
92
|
+
const result = await this.#sendBatch(eventsToSend);
|
|
93
|
+
if (result.success) {
|
|
94
|
+
if (result.nextRequestWaitMs !== undefined) {
|
|
95
|
+
nextDelayMs = Math.max(result.nextRequestWaitMs, MIN_RATE_LIMIT_WAIT_MS);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else if (result.isPermanentError) {
|
|
99
|
+
logger('Permanent error, dropped batch of', eventsToSend.length, 'events');
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// Transient error: Requeue events at the front of the buffer
|
|
103
|
+
// to maintain order and retry them later.
|
|
104
|
+
this.#buffer = [...eventsToSend, ...this.#buffer];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
// Safety catch for unexpected errors, requeue events
|
|
109
|
+
this.#buffer = [...eventsToSend, ...this.#buffer];
|
|
110
|
+
logger('Flush failed unexpectedly:', error);
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
this.#isFlushing = false;
|
|
114
|
+
this.#scheduleFlush(nextDelayMs);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
#addToBuffer(event) {
|
|
118
|
+
if (this.#buffer.length >= MAX_BUFFER_SIZE) {
|
|
119
|
+
this.#buffer.shift();
|
|
120
|
+
logger('Telemetry buffer overflow: dropped oldest event');
|
|
121
|
+
}
|
|
122
|
+
this.#buffer.push({
|
|
123
|
+
event,
|
|
124
|
+
timestamp: Date.now(),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
#scheduleFlush(delayMs) {
|
|
128
|
+
if (this.#flushTimer) {
|
|
129
|
+
clearTimeout(this.#flushTimer);
|
|
130
|
+
}
|
|
131
|
+
this.#flushTimer = setTimeout(() => {
|
|
132
|
+
this.#flush().catch(err => {
|
|
133
|
+
logger('Flush error:', err);
|
|
134
|
+
});
|
|
135
|
+
}, delayMs);
|
|
136
|
+
}
|
|
137
|
+
async #sendBatch(events) {
|
|
138
|
+
const requestBody = {
|
|
139
|
+
log_source: LOG_SOURCE,
|
|
140
|
+
request_time_ms: Date.now().toString(),
|
|
141
|
+
client_info: {
|
|
142
|
+
client_type: CLIENT_TYPE,
|
|
143
|
+
},
|
|
144
|
+
log_event: events.map(({ event, timestamp }) => ({
|
|
145
|
+
event_time_ms: timestamp.toString(),
|
|
146
|
+
source_extension_json: JSON.stringify(event),
|
|
147
|
+
})),
|
|
148
|
+
};
|
|
149
|
+
const controller = new AbortController();
|
|
150
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
151
|
+
try {
|
|
152
|
+
const response = await fetch(this.#clearcutEndpoint, {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
headers: {
|
|
155
|
+
'Content-Type': 'application/json',
|
|
156
|
+
// Used in E2E tests to confirm that the watchdog process is killed
|
|
157
|
+
...(this.#includePidHeader
|
|
158
|
+
? { 'X-Watchdog-Pid': process.pid.toString() }
|
|
159
|
+
: {}),
|
|
160
|
+
},
|
|
161
|
+
body: JSON.stringify(requestBody),
|
|
162
|
+
signal: controller.signal,
|
|
163
|
+
});
|
|
164
|
+
clearTimeout(timeoutId);
|
|
165
|
+
if (response.ok) {
|
|
166
|
+
const data = (await response.json());
|
|
167
|
+
return {
|
|
168
|
+
success: true,
|
|
169
|
+
nextRequestWaitMs: data.next_request_wait_millis,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const status = response.status;
|
|
173
|
+
if (status >= 500 || status === 429) {
|
|
174
|
+
return { success: false };
|
|
175
|
+
}
|
|
176
|
+
logger('Telemetry permanent error:', status);
|
|
177
|
+
return { success: false, isPermanentError: true };
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
clearTimeout(timeoutId);
|
|
181
|
+
return { success: false };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async #finalFlush() {
|
|
185
|
+
if (this.#buffer.length === 0) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const eventsToSend = [...this.#buffer];
|
|
189
|
+
await this.#sendBatch(eventsToSend);
|
|
190
|
+
}
|
|
191
|
+
stopForTesting() {
|
|
192
|
+
if (this.#flushTimer) {
|
|
193
|
+
clearTimeout(this.#flushTimer);
|
|
194
|
+
this.#flushTimer = null;
|
|
195
|
+
}
|
|
196
|
+
this.#timerStarted = false;
|
|
197
|
+
}
|
|
198
|
+
get bufferSizeForTesting() {
|
|
199
|
+
return this.#buffer.length;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import process from 'node:process';
|
|
7
|
+
import readline from 'node:readline';
|
|
8
|
+
import { parseArgs } from 'node:util';
|
|
9
|
+
import { logger, flushLogs, saveLogsToFile } from '../../logger.js';
|
|
10
|
+
import { WatchdogMessageType } from '../types.js';
|
|
11
|
+
import { ClearcutSender } from './clearcut-sender.js';
|
|
12
|
+
function parseWatchdogArgs() {
|
|
13
|
+
const { values } = parseArgs({
|
|
14
|
+
options: {
|
|
15
|
+
'parent-pid': { type: 'string' },
|
|
16
|
+
'app-version': { type: 'string' },
|
|
17
|
+
'os-type': { type: 'string' },
|
|
18
|
+
'log-file': { type: 'string' },
|
|
19
|
+
'clearcut-endpoint': { type: 'string' },
|
|
20
|
+
'clearcut-force-flush-interval-ms': { type: 'string' },
|
|
21
|
+
'clearcut-include-pid-header': { type: 'boolean' },
|
|
22
|
+
},
|
|
23
|
+
strict: true,
|
|
24
|
+
});
|
|
25
|
+
// Verify required arguments
|
|
26
|
+
const parentPid = parseInt(values['parent-pid'] ?? '', 10);
|
|
27
|
+
const appVersion = values['app-version'];
|
|
28
|
+
const osType = parseInt(values['os-type'] ?? '', 10);
|
|
29
|
+
if (isNaN(parentPid) || !appVersion || isNaN(osType)) {
|
|
30
|
+
console.error('Invalid arguments provided for watchdog process: ', JSON.stringify({ parentPid, appVersion, osType }));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
// Parse Optional Arguments
|
|
34
|
+
const logFile = values['log-file'];
|
|
35
|
+
const clearcutEndpoint = values['clearcut-endpoint'];
|
|
36
|
+
const clearcutIncludePidHeader = values['clearcut-include-pid-header'];
|
|
37
|
+
let clearcutForceFlushIntervalMs;
|
|
38
|
+
if (values['clearcut-force-flush-interval-ms']) {
|
|
39
|
+
const parsed = parseInt(values['clearcut-force-flush-interval-ms'], 10);
|
|
40
|
+
if (!isNaN(parsed)) {
|
|
41
|
+
clearcutForceFlushIntervalMs = parsed;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
parentPid,
|
|
46
|
+
appVersion,
|
|
47
|
+
osType,
|
|
48
|
+
logFile,
|
|
49
|
+
clearcutEndpoint,
|
|
50
|
+
clearcutForceFlushIntervalMs,
|
|
51
|
+
clearcutIncludePidHeader,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function main() {
|
|
55
|
+
const { parentPid, appVersion, osType, logFile, clearcutEndpoint, clearcutForceFlushIntervalMs, clearcutIncludePidHeader, } = parseWatchdogArgs();
|
|
56
|
+
let logStream;
|
|
57
|
+
if (logFile) {
|
|
58
|
+
logStream = saveLogsToFile(logFile);
|
|
59
|
+
}
|
|
60
|
+
const exit = (code) => {
|
|
61
|
+
if (!logStream) {
|
|
62
|
+
process.exit(code);
|
|
63
|
+
}
|
|
64
|
+
void flushLogs(logStream).finally(() => {
|
|
65
|
+
process.exit(code);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
logger('Watchdog started', JSON.stringify({
|
|
69
|
+
pid: process.pid,
|
|
70
|
+
parentPid,
|
|
71
|
+
version: appVersion,
|
|
72
|
+
osType,
|
|
73
|
+
}, null, 2));
|
|
74
|
+
const sender = new ClearcutSender({
|
|
75
|
+
appVersion,
|
|
76
|
+
osType: osType,
|
|
77
|
+
clearcutEndpoint,
|
|
78
|
+
forceFlushIntervalMs: clearcutForceFlushIntervalMs,
|
|
79
|
+
includePidHeader: clearcutIncludePidHeader,
|
|
80
|
+
});
|
|
81
|
+
let isShuttingDown = false;
|
|
82
|
+
function onParentDeath(reason) {
|
|
83
|
+
if (isShuttingDown) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
isShuttingDown = true;
|
|
87
|
+
logger(`Parent death detected (${reason}). Sending shutdown event...`);
|
|
88
|
+
sender
|
|
89
|
+
.sendShutdownEvent()
|
|
90
|
+
.then(() => {
|
|
91
|
+
logger('Shutdown event sent. Exiting.');
|
|
92
|
+
exit(0);
|
|
93
|
+
})
|
|
94
|
+
.catch(err => {
|
|
95
|
+
logger('Failed to send shutdown event', err);
|
|
96
|
+
exit(1);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
process.stdin.on('end', () => onParentDeath('stdin end'));
|
|
100
|
+
process.stdin.on('close', () => onParentDeath('stdin close'));
|
|
101
|
+
process.on('disconnect', () => onParentDeath('ipc disconnect'));
|
|
102
|
+
const rl = readline.createInterface({
|
|
103
|
+
input: process.stdin,
|
|
104
|
+
terminal: false,
|
|
105
|
+
});
|
|
106
|
+
rl.on('line', line => {
|
|
107
|
+
try {
|
|
108
|
+
if (!line.trim()) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const msg = JSON.parse(line);
|
|
112
|
+
if (msg.type === WatchdogMessageType.LOG_EVENT && msg.payload) {
|
|
113
|
+
sender.enqueueEvent(msg.payload);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
logger('Failed to parse IPC message', err);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
main();
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
console.error('Watchdog fatal error:', err);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { logger } from '../logger.js';
|
|
9
|
+
export class WatchdogClient {
|
|
10
|
+
#childProcess;
|
|
11
|
+
constructor(config, options) {
|
|
12
|
+
const watchdogPath = fileURLToPath(new URL('./watchdog/main.js', import.meta.url));
|
|
13
|
+
const args = [
|
|
14
|
+
watchdogPath,
|
|
15
|
+
`--parent-pid=${config.parentPid}`,
|
|
16
|
+
`--app-version=${config.appVersion}`,
|
|
17
|
+
`--os-type=${config.osType}`,
|
|
18
|
+
];
|
|
19
|
+
if (config.logFile) {
|
|
20
|
+
args.push(`--log-file=${config.logFile}`);
|
|
21
|
+
}
|
|
22
|
+
if (config.clearcutEndpoint) {
|
|
23
|
+
args.push(`--clearcut-endpoint=${config.clearcutEndpoint}`);
|
|
24
|
+
}
|
|
25
|
+
if (config.clearcutForceFlushIntervalMs) {
|
|
26
|
+
args.push(`--clearcut-force-flush-interval-ms=${config.clearcutForceFlushIntervalMs}`);
|
|
27
|
+
}
|
|
28
|
+
if (config.clearcutIncludePidHeader) {
|
|
29
|
+
args.push('--clearcut-include-pid-header');
|
|
30
|
+
}
|
|
31
|
+
const spawner = options?.spawn ?? spawn;
|
|
32
|
+
this.#childProcess = spawner(process.execPath, args, {
|
|
33
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
34
|
+
detached: true,
|
|
35
|
+
});
|
|
36
|
+
this.#childProcess.unref();
|
|
37
|
+
this.#childProcess.on('error', err => {
|
|
38
|
+
logger('Watchdog process error:', err);
|
|
39
|
+
});
|
|
40
|
+
this.#childProcess.on('exit', (code, signal) => {
|
|
41
|
+
logger(`Watchdog exited with code ${code} and signal ${signal}`);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
send(message) {
|
|
45
|
+
if (this.#childProcess.stdin &&
|
|
46
|
+
!this.#childProcess.stdin.destroyed &&
|
|
47
|
+
this.#childProcess.pid) {
|
|
48
|
+
try {
|
|
49
|
+
const line = JSON.stringify(message) + '\n';
|
|
50
|
+
this.#childProcess.stdin.write(line);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
logger('Failed to write to watchdog stdin', err);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
logger('Watchdog stdin not available, dropping message');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -11,6 +11,6 @@ export { default as debug } from 'debug';
|
|
|
11
11
|
export { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
12
12
|
export { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
13
13
|
export { SetLevelRequestSchema, ToolListChangedNotificationSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
14
|
-
export { z as zod } from 'zod
|
|
14
|
+
export { z as zod } from 'zod';
|
|
15
15
|
export { Locator, PredefinedNetworkConditions, CDPSessionEvent, } from 'puppeteer-core';
|
|
16
16
|
export { default as puppeteer } from 'puppeteer-core';
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { zod } from '../third_party/index.js';
|
|
7
|
+
import { ToolCategory } from './categories.js';
|
|
8
|
+
import { defineTool } from './ToolDefinition.js';
|
|
9
|
+
export const connectToBrowser = defineTool({
|
|
10
|
+
name: 'connect_to_browser',
|
|
11
|
+
description: 'Connect to a different Chrome browser instance at runtime. ' +
|
|
12
|
+
'Disconnects from the current browser (without closing it) and attaches to the new one. ' +
|
|
13
|
+
'Useful for WXT extension development: run `wxt dev`, parse the debug URL from stdout, ' +
|
|
14
|
+
'then call this tool to attach. Retries automatically if the browser is still starting up.',
|
|
15
|
+
annotations: {
|
|
16
|
+
title: 'Connect to Browser',
|
|
17
|
+
category: ToolCategory.NAVIGATION,
|
|
18
|
+
readOnlyHint: false,
|
|
19
|
+
},
|
|
20
|
+
schema: {
|
|
21
|
+
browserUrl: zod
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('HTTP URL to the browser\'s DevTools endpoint, e.g. "http://127.0.0.1:9222". ' +
|
|
25
|
+
'Exactly one of browserUrl or wsEndpoint is required.'),
|
|
26
|
+
wsEndpoint: zod
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('WebSocket URL to connect to, e.g. "ws://127.0.0.1:9222/devtools/browser/...". ' +
|
|
30
|
+
'Exactly one of browserUrl or wsEndpoint is required.'),
|
|
31
|
+
wsHeaders: zod
|
|
32
|
+
.record(zod.string())
|
|
33
|
+
.optional()
|
|
34
|
+
.describe('Optional HTTP headers for WebSocket connection (only used with wsEndpoint).'),
|
|
35
|
+
timeout: zod
|
|
36
|
+
.number()
|
|
37
|
+
.int()
|
|
38
|
+
.positive()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('Maximum time in milliseconds to wait for the browser to become available. ' +
|
|
41
|
+
'Defaults to 30000 (30 seconds). The tool retries every 2 seconds within this window.'),
|
|
42
|
+
},
|
|
43
|
+
handler: async (request, response, context) => {
|
|
44
|
+
const { browserUrl, wsEndpoint, wsHeaders, timeout } = request.params;
|
|
45
|
+
// Validate: exactly one connection method required
|
|
46
|
+
if (!browserUrl && !wsEndpoint) {
|
|
47
|
+
throw new Error('Either browserUrl or wsEndpoint is required. ' +
|
|
48
|
+
'Example: browserUrl "http://127.0.0.1:9222" or wsEndpoint "ws://127.0.0.1:9222/devtools/browser/..."');
|
|
49
|
+
}
|
|
50
|
+
if (browserUrl && wsEndpoint) {
|
|
51
|
+
throw new Error('Provide only one of browserUrl or wsEndpoint, not both.');
|
|
52
|
+
}
|
|
53
|
+
// Validate URL protocols
|
|
54
|
+
if (browserUrl && !browserUrl.startsWith('http://') && !browserUrl.startsWith('https://')) {
|
|
55
|
+
throw new Error(`Invalid browserUrl: "${browserUrl}". Must start with http:// or https://`);
|
|
56
|
+
}
|
|
57
|
+
if (wsEndpoint && !wsEndpoint.startsWith('ws://') && !wsEndpoint.startsWith('wss://')) {
|
|
58
|
+
throw new Error(`Invalid wsEndpoint: "${wsEndpoint}". Must start with ws:// or wss://`);
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const result = await context.reconnectBrowser({
|
|
62
|
+
browserURL: browserUrl,
|
|
63
|
+
wsEndpoint,
|
|
64
|
+
wsHeaders,
|
|
65
|
+
timeout,
|
|
66
|
+
});
|
|
67
|
+
response.appendResponseLine(`Connected to browser at ${wsEndpoint ?? browserUrl}`);
|
|
68
|
+
response.appendResponseLine(`WebSocket endpoint: ${result.wsEndpoint}`);
|
|
69
|
+
response.appendResponseLine('');
|
|
70
|
+
response.appendResponseLine('Pages:');
|
|
71
|
+
for (const page of result.pages) {
|
|
72
|
+
const marker = page.selected ? ' (selected)' : '';
|
|
73
|
+
response.appendResponseLine(` [${page.index}] ${page.url}${marker}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
response.setIsError(true);
|
|
78
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
79
|
+
response.appendResponseLine(`Failed to connect: ${message}`);
|
|
80
|
+
response.appendResponseLine('');
|
|
81
|
+
response.appendResponseLine('Troubleshooting:');
|
|
82
|
+
response.appendResponseLine(' - Ensure Chrome was launched with --remote-debugging-port=<port>');
|
|
83
|
+
response.appendResponseLine(' - Verify the URL is reachable: curl ' + (browserUrl ?? wsEndpoint));
|
|
84
|
+
if (timeout) {
|
|
85
|
+
response.appendResponseLine(' - Try increasing the timeout (current: ' + timeout + 'ms)');
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
response.appendResponseLine(' - Try setting a longer timeout (default is 30s)');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { zod } from '../third_party/index.js';
|
|
7
|
+
import { ToolCategory } from './categories.js';
|
|
8
|
+
import { defineTool } from './ToolDefinition.js';
|
|
9
|
+
export const evaluateInExtensionWorker = defineTool({
|
|
10
|
+
name: 'evaluate_in_extension_worker',
|
|
11
|
+
description: 'Execute a JavaScript expression in a Chrome extension background service worker. ' +
|
|
12
|
+
'Useful for inspecting extension state (e.g., McpHub connections, offscreen status) ' +
|
|
13
|
+
'that is not accessible from page contexts.',
|
|
14
|
+
annotations: {
|
|
15
|
+
category: ToolCategory.DEBUGGING,
|
|
16
|
+
readOnlyHint: false,
|
|
17
|
+
},
|
|
18
|
+
schema: {
|
|
19
|
+
expression: zod
|
|
20
|
+
.string()
|
|
21
|
+
.describe('JavaScript expression to evaluate in the service worker.'),
|
|
22
|
+
extensionId: zod
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Extension ID to target. If omitted, the first extension service worker found is used.'),
|
|
26
|
+
},
|
|
27
|
+
handler: async (request, response, context) => {
|
|
28
|
+
const result = await context.evaluateInExtensionWorker(request.params.expression, request.params.extensionId);
|
|
29
|
+
response.appendResponseLine(JSON.stringify(result, null, 2));
|
|
30
|
+
},
|
|
31
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { zod } from '../third_party/index.js';
|
|
7
|
+
import { ToolCategory } from './categories.js';
|
|
8
|
+
import { defineTool } from './ToolDefinition.js';
|
|
9
|
+
const EXTENSIONS_CONDITION = 'experimentalExtensionSupport';
|
|
10
|
+
export const installExtension = defineTool({
|
|
11
|
+
name: 'install_extension',
|
|
12
|
+
description: 'Installs a Chrome extension from the given path.',
|
|
13
|
+
annotations: {
|
|
14
|
+
category: ToolCategory.EXTENSIONS,
|
|
15
|
+
readOnlyHint: false,
|
|
16
|
+
conditions: [EXTENSIONS_CONDITION],
|
|
17
|
+
},
|
|
18
|
+
schema: {
|
|
19
|
+
path: zod
|
|
20
|
+
.string()
|
|
21
|
+
.describe('Absolute path to the unpacked extension folder.'),
|
|
22
|
+
},
|
|
23
|
+
handler: async (request, response, context) => {
|
|
24
|
+
const { path } = request.params;
|
|
25
|
+
const id = await context.installExtension(path);
|
|
26
|
+
response.appendResponseLine(`Extension installed. Id: ${id}`);
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
export const uninstallExtension = defineTool({
|
|
30
|
+
name: 'uninstall_extension',
|
|
31
|
+
description: 'Uninstalls a Chrome extension by its ID.',
|
|
32
|
+
annotations: {
|
|
33
|
+
category: ToolCategory.EXTENSIONS,
|
|
34
|
+
readOnlyHint: false,
|
|
35
|
+
conditions: [EXTENSIONS_CONDITION],
|
|
36
|
+
},
|
|
37
|
+
schema: {
|
|
38
|
+
id: zod.string().describe('ID of the extension to uninstall.'),
|
|
39
|
+
},
|
|
40
|
+
handler: async (request, response, context) => {
|
|
41
|
+
const { id } = request.params;
|
|
42
|
+
await context.uninstallExtension(id);
|
|
43
|
+
response.appendResponseLine(`Extension uninstalled. Id: ${id}`);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
export const listExtensions = defineTool({
|
|
47
|
+
name: 'list_extensions',
|
|
48
|
+
description: 'Lists all extensions via this server, including their name, ID, version, and enabled status.',
|
|
49
|
+
annotations: {
|
|
50
|
+
category: ToolCategory.EXTENSIONS,
|
|
51
|
+
readOnlyHint: true,
|
|
52
|
+
conditions: [EXTENSIONS_CONDITION],
|
|
53
|
+
},
|
|
54
|
+
schema: {},
|
|
55
|
+
handler: async (_request, response, _context) => {
|
|
56
|
+
response.setListExtensions();
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
export const reloadExtension = defineTool({
|
|
60
|
+
name: 'reload_extension',
|
|
61
|
+
description: 'Reloads an unpacked Chrome extension by its ID.',
|
|
62
|
+
annotations: {
|
|
63
|
+
category: ToolCategory.EXTENSIONS,
|
|
64
|
+
readOnlyHint: false,
|
|
65
|
+
conditions: [EXTENSIONS_CONDITION],
|
|
66
|
+
},
|
|
67
|
+
schema: {
|
|
68
|
+
id: zod.string().describe('ID of the extension to reload.'),
|
|
69
|
+
},
|
|
70
|
+
handler: async (request, response, context) => {
|
|
71
|
+
const { id } = request.params;
|
|
72
|
+
const extension = context.getExtension(id);
|
|
73
|
+
if (!extension) {
|
|
74
|
+
throw new Error(`Extension with ID ${id} not found.`);
|
|
75
|
+
}
|
|
76
|
+
await context.installExtension(extension.path);
|
|
77
|
+
response.appendResponseLine('Extension reloaded.');
|
|
78
|
+
},
|
|
79
|
+
});
|
package/build/src/tools/input.js
CHANGED
|
@@ -103,11 +103,16 @@ async function selectOption(handle, aXNode, value) {
|
|
|
103
103
|
throw new Error(`Could not find option with text "${value}"`);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
+
function hasOptionChildren(aXNode) {
|
|
107
|
+
return aXNode.children.some(child => child.role === 'option');
|
|
108
|
+
}
|
|
106
109
|
async function fillFormElement(uid, value, context) {
|
|
107
110
|
const handle = await context.getElementByUid(uid);
|
|
108
111
|
try {
|
|
109
112
|
const aXNode = context.getAXNodeByUid(uid);
|
|
110
|
-
|
|
113
|
+
// We assume that combobox needs to be handled as select if it has
|
|
114
|
+
// role='combobox' and option children.
|
|
115
|
+
if (aXNode && aXNode.role === 'combobox' && hasOptionChildren(aXNode)) {
|
|
111
116
|
await selectOption(handle, aXNode, value);
|
|
112
117
|
}
|
|
113
118
|
else {
|
package/build/src/tools/pages.js
CHANGED
|
@@ -110,6 +110,10 @@ export const navigatePage = defineTool({
|
|
|
110
110
|
.boolean()
|
|
111
111
|
.optional()
|
|
112
112
|
.describe('Whether to ignore cache on reload.'),
|
|
113
|
+
bypassCSP: zod
|
|
114
|
+
.boolean()
|
|
115
|
+
.optional()
|
|
116
|
+
.describe('Bypass Content-Security-Policy on the page. Useful for injecting scripts into third-party sites during development.'),
|
|
113
117
|
...timeoutSchema,
|
|
114
118
|
},
|
|
115
119
|
handler: async (request, response, context) => {
|
|
@@ -117,6 +121,9 @@ export const navigatePage = defineTool({
|
|
|
117
121
|
const options = {
|
|
118
122
|
timeout: request.params.timeout,
|
|
119
123
|
};
|
|
124
|
+
if (request.params.bypassCSP !== undefined) {
|
|
125
|
+
await context.setBypassCSP(request.params.bypassCSP);
|
|
126
|
+
}
|
|
120
127
|
if (!request.params.type && !request.params.url) {
|
|
121
128
|
throw new Error('Either URL or a type is required.');
|
|
122
129
|
}
|
|
@@ -185,7 +192,6 @@ export const resizePage = defineTool({
|
|
|
185
192
|
},
|
|
186
193
|
handler: async (request, response, context) => {
|
|
187
194
|
const page = context.getSelectedPage();
|
|
188
|
-
// @ts-expect-error internal API for now.
|
|
189
195
|
await page.resize({
|
|
190
196
|
contentWidth: request.params.width,
|
|
191
197
|
contentHeight: request.params.height,
|