chrome-devtools-mcp 0.15.1 → 0.17.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.
package/README.md CHANGED
@@ -15,7 +15,7 @@ Chrome DevTools for reliable automation, in-depth debugging, and performance ana
15
15
  DevTools](https://github.com/ChromeDevTools/devtools-frontend) to record
16
16
  traces and extract actionable performance insights.
17
17
  - **Advanced browser debugging**: Analyze network requests, take screenshots and
18
- check the browser console.
18
+ check browser console messages (with source-mapped stack traces).
19
19
  - **Reliable automation**. Uses
20
20
  [puppeteer](https://github.com/puppeteer/puppeteer) to automate actions in
21
21
  Chrome and automatically wait for action results.
@@ -27,6 +27,12 @@ allowing them to inspect, debug, and modify any data in the browser or DevTools.
27
27
  Avoid sharing sensitive or personal information that you don't want to share with
28
28
  MCP clients.
29
29
 
30
+ Performance tools may send trace URLs to the Google CrUX API to fetch real-user
31
+ experience data. This helps provide a holistic performance picture by
32
+ presenting field data alongside lab data. This data is collected by the [Chrome
33
+ User Experience Report (CrUX)](https://developer.chrome.com/docs/crux). To disable
34
+ this, run with the `--no-performance-crux` flag.
35
+
30
36
  ## **Usage statistics**
31
37
 
32
38
  Google collects usage statistics (such as tool invocation success rates, latency, and environment information) to improve the reliability and performance of Chrome DevTools MCP.
@@ -466,6 +472,11 @@ The Chrome DevTools MCP server supports the following configuration option:
466
472
  - **Type:** boolean
467
473
  - **Default:** `true`
468
474
 
475
+ - **`--performanceCrux`/ `--performance-crux`**
476
+ Set to false to disable sending URLs from performance traces to CrUX API to get field performance data.
477
+ - **Type:** boolean
478
+ - **Default:** `true`
479
+
469
480
  - **`--usageStatistics`/ `--usage-statistics`**
470
481
  Set to false to opt-out of usage statistics collection. Google collects usage data to improve the tool, handled under the Google Privacy Policy (https://policies.google.com/privacy). This is independent from Chrome browser metrics. Disabled if CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS or CI env variables are set.
471
482
  - **Type:** boolean
@@ -563,15 +574,12 @@ The following code snippet is an example configuration for gemini-cli:
563
574
  "mcpServers": {
564
575
  "chrome-devtools": {
565
576
  "command": "npx",
566
- "args": ["chrome-devtools-mcp@latest", "--autoConnect", "--channel=beta"]
577
+ "args": ["chrome-devtools-mcp@latest", "--autoConnect"]
567
578
  }
568
579
  }
569
580
  }
570
581
  ```
571
582
 
572
- Note: you have to specify `--channel=beta` until Chrome M144 has reached the
573
- stable channel.
574
-
575
583
  **Step 3:** Test your setup
576
584
 
577
585
  Make sure your browser is running. Open gemini-cli and run the following prompt:
@@ -169,16 +169,131 @@ const SKIP_ALL_PAUSES = {
169
169
  // Do nothing.
170
170
  },
171
171
  };
172
+ /**
173
+ * Constructed from Runtime.ExceptionDetails of an uncaught error.
174
+ *
175
+ * TODO: Also construct from a RemoteObject of subtype 'error'.
176
+ *
177
+ * Consists of the message, a fully resolved stack trace and a fully resolved 'cause' chain.
178
+ */
179
+ export class SymbolizedError {
180
+ message;
181
+ stackTrace;
182
+ cause;
183
+ constructor(message, stackTrace, cause) {
184
+ this.message = message;
185
+ this.stackTrace = stackTrace;
186
+ this.cause = cause;
187
+ }
188
+ static async fromDetails(opts) {
189
+ const message = SymbolizedError.#getMessage(opts.details);
190
+ if (!opts.includeStackAndCause || !opts.devTools) {
191
+ return new SymbolizedError(message, opts.resolvedStackTraceForTesting, opts.resolvedCauseForTesting);
192
+ }
193
+ let stackTrace;
194
+ if (opts.resolvedStackTraceForTesting) {
195
+ stackTrace = opts.resolvedStackTraceForTesting;
196
+ }
197
+ else if (opts.details.stackTrace) {
198
+ try {
199
+ stackTrace = await createStackTrace(opts.devTools, opts.details.stackTrace, opts.targetId);
200
+ }
201
+ catch {
202
+ // ignore
203
+ }
204
+ }
205
+ // TODO: Turn opts.details.exception into a JSHandle and retrieve the 'cause' property.
206
+ // If its an Error, recursively create a SymbolizedError.
207
+ let cause;
208
+ if (opts.resolvedCauseForTesting) {
209
+ cause = opts.resolvedCauseForTesting;
210
+ }
211
+ else if (opts.details.exception) {
212
+ try {
213
+ const causeRemoteObj = await SymbolizedError.#lookupCause(opts.devTools, opts.details.exception, opts.targetId);
214
+ if (causeRemoteObj) {
215
+ cause = await SymbolizedError.fromError({
216
+ devTools: opts.devTools,
217
+ error: causeRemoteObj,
218
+ targetId: opts.targetId,
219
+ });
220
+ }
221
+ }
222
+ catch {
223
+ // Ignore
224
+ }
225
+ }
226
+ return new SymbolizedError(message, stackTrace, cause);
227
+ }
228
+ static async fromError(opts) {
229
+ const details = await SymbolizedError.#getExceptionDetails(opts.devTools, opts.error, opts.targetId);
230
+ if (details) {
231
+ return SymbolizedError.fromDetails({
232
+ details,
233
+ devTools: opts.devTools,
234
+ targetId: opts.targetId,
235
+ includeStackAndCause: true,
236
+ });
237
+ }
238
+ return new SymbolizedError(SymbolizedError.#getMessageFromException(opts.error));
239
+ }
240
+ static #getMessage(details) {
241
+ // For Runtime.exceptionThrown with a present exception object, `details.text` will be "Uncaught" and
242
+ // we have to manually parse out the error text from the exception description.
243
+ // In the case of Runtime.getExceptionDetails, `details.text` has the Error.message.
244
+ if (details.text === 'Uncaught' && details.exception) {
245
+ return ('Uncaught ' +
246
+ SymbolizedError.#getMessageFromException(details.exception));
247
+ }
248
+ return details.text;
249
+ }
250
+ static #getMessageFromException(error) {
251
+ const messageWithRest = error.description?.split('\n at ', 2) ?? [];
252
+ return messageWithRest[0] ?? '';
253
+ }
254
+ static async #getExceptionDetails(devTools, error, targetId) {
255
+ if (!devTools || (error.type !== 'object' && error.subtype !== 'error')) {
256
+ return null;
257
+ }
258
+ const targetManager = devTools.universe.context.get(DevTools.TargetManager);
259
+ const target = targetId
260
+ ? targetManager.targetById(targetId) || devTools.target
261
+ : devTools.target;
262
+ const model = target.model(DevTools.RuntimeModel);
263
+ return ((await model.getExceptionDetails(error.objectId)) ?? null);
264
+ }
265
+ static async #lookupCause(devTools, error, targetId) {
266
+ if (!devTools || (error.type !== 'object' && error.subtype !== 'error')) {
267
+ return null;
268
+ }
269
+ const targetManager = devTools.universe.context.get(DevTools.TargetManager);
270
+ const target = targetId
271
+ ? targetManager.targetById(targetId) || devTools.target
272
+ : devTools.target;
273
+ const properties = await target.runtimeAgent().invoke_getProperties({
274
+ objectId: error.objectId,
275
+ });
276
+ if (properties.getError()) {
277
+ return null;
278
+ }
279
+ return properties.result.find(prop => prop.name === 'cause')?.value ?? null;
280
+ }
281
+ static createForTesting(message, stackTrace, cause) {
282
+ return new SymbolizedError(message, stackTrace, cause);
283
+ }
284
+ }
172
285
  export async function createStackTraceForConsoleMessage(devTools, consoleMessage) {
173
286
  const message = consoleMessage;
174
287
  const rawStackTrace = message._rawStackTrace();
175
- if (!rawStackTrace) {
176
- return undefined;
288
+ if (rawStackTrace) {
289
+ return createStackTrace(devTools, rawStackTrace, message._targetId());
177
290
  }
291
+ return undefined;
292
+ }
293
+ export async function createStackTrace(devTools, rawStackTrace, targetId) {
178
294
  const targetManager = devTools.universe.context.get(DevTools.TargetManager);
179
- const messageTargetId = message._targetId();
180
- const target = messageTargetId
181
- ? targetManager.targetById(messageTargetId) || devTools.target
295
+ const target = targetId
296
+ ? targetManager.targetById(targetId) || devTools.target
182
297
  : devTools.target;
183
298
  const model = target.model(DevTools.DebuggerModel);
184
299
  // DevTools doesn't wait for source maps to attach before building a stack trace, rather it'll send
@@ -80,15 +80,8 @@ export class McpContext {
80
80
  console: event => {
81
81
  collect(event);
82
82
  },
83
- pageerror: event => {
84
- if (event instanceof Error) {
85
- collect(event);
86
- }
87
- else {
88
- const error = new Error(`${event}`);
89
- error.stack = undefined;
90
- collect(error);
91
- }
83
+ uncaughtError: event => {
84
+ collect(event);
92
85
  },
93
86
  issue: event => {
94
87
  collect(event);
@@ -269,6 +262,9 @@ export class McpContext {
269
262
  isRunningPerformanceTrace() {
270
263
  return this.#isRunningTrace;
271
264
  }
265
+ isCruxEnabled() {
266
+ return this.#options.performanceCrux;
267
+ }
272
268
  getDialog() {
273
269
  return this.#dialog;
274
270
  }
@@ -7,6 +7,7 @@ import { ConsoleFormatter } from './formatters/ConsoleFormatter.js';
7
7
  import { IssueFormatter } from './formatters/IssueFormatter.js';
8
8
  import { NetworkFormatter } from './formatters/NetworkFormatter.js';
9
9
  import { SnapshotFormatter } from './formatters/SnapshotFormatter.js';
10
+ import { UncaughtError } from './PageCollector.js';
10
11
  import { DevTools } from './third_party/index.js';
11
12
  import { handleDialog } from './tools/pages.js';
12
13
  import { getInsightOutput, getTraceSummary } from './trace-processing/parse.js';
@@ -173,7 +174,7 @@ export class McpResponse {
173
174
  if (this.#attachedConsoleMessageId) {
174
175
  const message = context.getConsoleMessageById(this.#attachedConsoleMessageId);
175
176
  const consoleMessageStableId = this.#attachedConsoleMessageId;
176
- if ('args' in message) {
177
+ if ('args' in message || message instanceof UncaughtError) {
177
178
  const consoleMessage = message;
178
179
  const devTools = context.getDevToolsUniverse();
179
180
  detailedConsoleMessage = await ConsoleFormatter.from(consoleMessage, {
@@ -193,11 +194,6 @@ export class McpResponse {
193
194
  }
194
195
  detailedConsoleMessage = formatter;
195
196
  }
196
- else {
197
- detailedConsoleMessage = await ConsoleFormatter.from(message, {
198
- id: consoleMessageStableId,
199
- });
200
- }
201
197
  }
202
198
  let extensions;
203
199
  if (this.#listExtensions) {
@@ -220,7 +216,7 @@ export class McpResponse {
220
216
  }
221
217
  consoleMessages = (await Promise.all(messages.map(async (item) => {
222
218
  const consoleMessageStableId = context.getConsoleMessageStableId(item);
223
- if ('args' in item) {
219
+ if ('args' in item || item instanceof UncaughtError) {
224
220
  const consoleMessage = item;
225
221
  const devTools = context.getDevToolsUniverse();
226
222
  return await ConsoleFormatter.from(consoleMessage, {
@@ -238,9 +234,7 @@ export class McpResponse {
238
234
  }
239
235
  return formatter;
240
236
  }
241
- return await ConsoleFormatter.from(item, {
242
- id: consoleMessageStableId,
243
- });
237
+ return null;
244
238
  }))).filter(item => item !== null);
245
239
  }
246
240
  let networkRequests;
@@ -6,6 +6,14 @@
6
6
  import { FakeIssuesManager } from './DevtoolsUtils.js';
7
7
  import { logger } from './logger.js';
8
8
  import { DevTools } from './third_party/index.js';
9
+ export class UncaughtError {
10
+ details;
11
+ targetId;
12
+ constructor(details, targetId) {
13
+ this.details = details;
14
+ this.targetId = targetId;
15
+ }
16
+ }
9
17
  function createIdGenerator() {
10
18
  let i = 1;
11
19
  return () => {
@@ -161,7 +169,7 @@ export class ConsoleCollector extends PageCollector {
161
169
  addPage(page) {
162
170
  super.addPage(page);
163
171
  if (!this.#subscribedPages.has(page)) {
164
- const subscriber = new PageIssueSubscriber(page);
172
+ const subscriber = new PageEventSubscriber(page);
165
173
  this.#subscribedPages.set(page, subscriber);
166
174
  void subscriber.subscribe();
167
175
  }
@@ -172,17 +180,20 @@ export class ConsoleCollector extends PageCollector {
172
180
  this.#subscribedPages.delete(page);
173
181
  }
174
182
  }
175
- class PageIssueSubscriber {
183
+ class PageEventSubscriber {
176
184
  #issueManager = new FakeIssuesManager();
177
185
  #issueAggregator = new DevTools.IssueAggregator(this.#issueManager);
178
186
  #seenKeys = new Set();
179
187
  #seenIssues = new Set();
180
188
  #page;
181
189
  #session;
190
+ #targetId;
182
191
  constructor(page) {
183
192
  this.#page = page;
184
193
  // @ts-expect-error use existing CDP client (internal Puppeteer API).
185
194
  this.#session = this.#page._client();
195
+ // @ts-expect-error use internal Puppeteer API to get target ID
196
+ this.#targetId = this.#session.target()._targetId;
186
197
  }
187
198
  #resetIssueAggregator() {
188
199
  this.#issueManager = new FakeIssuesManager();
@@ -196,6 +207,7 @@ class PageIssueSubscriber {
196
207
  this.#resetIssueAggregator();
197
208
  this.#page.on('framenavigated', this.#onFrameNavigated);
198
209
  this.#session.on('Audits.issueAdded', this.#onIssueAdded);
210
+ this.#session.on('Runtime.exceptionThrown', this.#onExceptionThrown);
199
211
  try {
200
212
  await this.#session.send('Audits.enable');
201
213
  }
@@ -208,6 +220,7 @@ class PageIssueSubscriber {
208
220
  this.#seenIssues.clear();
209
221
  this.#page.off('framenavigated', this.#onFrameNavigated);
210
222
  this.#session.off('Audits.issueAdded', this.#onIssueAdded);
223
+ this.#session.off('Runtime.exceptionThrown', this.#onExceptionThrown);
211
224
  if (this.#issueAggregator) {
212
225
  this.#issueAggregator.removeEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedissue);
213
226
  }
@@ -222,6 +235,9 @@ class PageIssueSubscriber {
222
235
  this.#seenIssues.add(event.data);
223
236
  this.#page.emit('issue', event.data);
224
237
  };
238
+ #onExceptionThrown = (event) => {
239
+ this.#page.emit('uncaughtError', new UncaughtError(event.exceptionDetails, this.#targetId));
240
+ };
225
241
  // On navigation, we reset issue aggregation.
226
242
  #onFrameNavigated = (frame) => {
227
243
  // Only split the storage on main frame navigation
package/build/src/cli.js CHANGED
@@ -188,6 +188,11 @@ export const cliOptions = {
188
188
  hidden: true,
189
189
  describe: 'Set to false to exclude tools related to extensions.',
190
190
  },
191
+ performanceCrux: {
192
+ type: 'boolean',
193
+ default: true,
194
+ describe: 'Set to false to disable sending URLs from performance traces to CrUX API to get field performance data.',
195
+ },
191
196
  usageStatistics: {
192
197
  type: 'boolean',
193
198
  default: true,
@@ -277,6 +282,10 @@ export function parseArguments(version, argv = process.argv) {
277
282
  '$0 --no-usage-statistics',
278
283
  'Do not send usage statistics https://github.com/ChromeDevTools/chrome-devtools-mcp#usage-statistics.',
279
284
  ],
285
+ [
286
+ '$0 --no-performance-crux',
287
+ 'Disable CrUX (field data) integration in performance tools.',
288
+ ],
280
289
  ]);
281
290
  return yargsInstance
282
291
  .wrap(Math.min(120, yargsInstance.terminalWidth()))
@@ -3,85 +3,130 @@
3
3
  * Copyright 2026 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { createStackTraceForConsoleMessage, } from '../DevtoolsUtils.js';
6
+ var _a;
7
+ import { createStackTraceForConsoleMessage, SymbolizedError, } from '../DevtoolsUtils.js';
8
+ import { UncaughtError } from '../PageCollector.js';
9
+ import * as DevTools from '../third_party/index.js';
7
10
  export class ConsoleFormatter {
8
- #msg;
9
- #resolvedArgs = [];
10
- #resolvedStackTrace;
11
+ static #STACK_TRACE_MAX_LINES = 50;
11
12
  #id;
12
- constructor(msg, options) {
13
- this.#msg = msg;
14
- this.#id = options?.id;
15
- this.#resolvedStackTrace = options?.resolvedStackTraceForTesting;
13
+ #type;
14
+ #text;
15
+ #argCount;
16
+ #resolvedArgs;
17
+ #stack;
18
+ #cause;
19
+ #isIgnored;
20
+ constructor(params) {
21
+ this.#id = params.id;
22
+ this.#type = params.type;
23
+ this.#text = params.text;
24
+ this.#argCount = params.argCount ?? 0;
25
+ this.#resolvedArgs = params.resolvedArgs ?? [];
26
+ this.#stack = params.stack;
27
+ this.#cause = params.cause;
28
+ this.#isIgnored = params.isIgnored;
16
29
  }
17
30
  static async from(msg, options) {
18
- const formatter = new ConsoleFormatter(msg, options);
19
- if (options?.fetchDetailedData) {
20
- await formatter.#loadDetailedData(options?.devTools);
31
+ const ignoreListManager = options?.devTools?.universe.context.get(DevTools.DevTools.IgnoreListManager);
32
+ const isIgnored = options.isIgnoredForTesting ||
33
+ (frame => {
34
+ if (!ignoreListManager) {
35
+ return false;
36
+ }
37
+ if (frame.uiSourceCode) {
38
+ return ignoreListManager.isUserOrSourceMapIgnoreListedUISourceCode(frame.uiSourceCode);
39
+ }
40
+ if (frame.url) {
41
+ return ignoreListManager.isUserIgnoreListedURL(frame.url);
42
+ }
43
+ return false;
44
+ });
45
+ if (msg instanceof UncaughtError) {
46
+ const error = await SymbolizedError.fromDetails({
47
+ devTools: options?.devTools,
48
+ details: msg.details,
49
+ targetId: msg.targetId,
50
+ includeStackAndCause: options?.fetchDetailedData,
51
+ resolvedStackTraceForTesting: options?.resolvedStackTraceForTesting,
52
+ resolvedCauseForTesting: options?.resolvedCauseForTesting,
53
+ });
54
+ return new _a({
55
+ id: options.id,
56
+ type: 'error',
57
+ text: error.message,
58
+ stack: error.stackTrace,
59
+ cause: error.cause,
60
+ isIgnored,
61
+ });
21
62
  }
22
- return formatter;
23
- }
24
- async #loadDetailedData(devTools) {
25
- if (this.#msg instanceof Error) {
26
- return;
63
+ let resolvedArgs = [];
64
+ if (options.resolvedArgsForTesting) {
65
+ resolvedArgs = options.resolvedArgsForTesting;
27
66
  }
28
- this.#resolvedArgs = await Promise.all(this.#msg.args().map(async (arg, i) => {
29
- try {
30
- return await arg.jsonValue();
31
- }
32
- catch {
33
- return `<error: Argument ${i} is no longer available>`;
34
- }
35
- }));
36
- if (devTools) {
67
+ else if (options.fetchDetailedData) {
68
+ resolvedArgs = await Promise.all(msg.args().map(async (arg, i) => {
69
+ try {
70
+ const remoteObject = arg.remoteObject();
71
+ if (remoteObject.type === 'object' &&
72
+ remoteObject.subtype === 'error') {
73
+ return await SymbolizedError.fromError({
74
+ devTools: options.devTools,
75
+ error: remoteObject,
76
+ // @ts-expect-error Internal ConsoleMessage API
77
+ targetId: msg._targetId(),
78
+ });
79
+ }
80
+ return await arg.jsonValue();
81
+ }
82
+ catch {
83
+ return `<error: Argument ${i} is no longer available>`;
84
+ }
85
+ }));
86
+ }
87
+ let stack;
88
+ if (options.resolvedStackTraceForTesting) {
89
+ stack = options.resolvedStackTraceForTesting;
90
+ }
91
+ else if (options.fetchDetailedData && options.devTools) {
37
92
  try {
38
- this.#resolvedStackTrace = await createStackTraceForConsoleMessage(devTools, this.#msg);
93
+ stack = await createStackTraceForConsoleMessage(options.devTools, msg);
39
94
  }
40
95
  catch {
41
96
  // ignore
42
97
  }
43
98
  }
99
+ return new _a({
100
+ id: options.id,
101
+ type: msg.type(),
102
+ text: msg.text(),
103
+ argCount: resolvedArgs.length || msg.args().length,
104
+ resolvedArgs,
105
+ stack,
106
+ isIgnored,
107
+ });
44
108
  }
45
109
  // The short format for a console message.
46
110
  toString() {
47
- const type = this.#getType();
48
- const text = this.#getText();
49
- const argsCount = this.#msg instanceof Error
50
- ? 0
51
- : this.#resolvedArgs.length || this.#msg.args().length;
52
- const idPart = this.#id !== undefined ? `msgid=${this.#id} ` : '';
53
- return `${idPart}[${type}] ${text} (${argsCount} args)`;
111
+ return `msgid=${this.#id} [${this.#type}] ${this.#text} (${this.#argCount} args)`;
54
112
  }
55
113
  // The verbose format for a console message, including all details.
56
114
  toStringDetailed() {
57
115
  const result = [
58
- this.#id !== undefined ? `ID: ${this.#id}` : '',
59
- `Message: ${this.#getType()}> ${this.#getText()}`,
116
+ `ID: ${this.#id}`,
117
+ `Message: ${this.#type}> ${this.#text}`,
60
118
  this.#formatArgs(),
61
- this.#formatStackTrace(this.#resolvedStackTrace),
119
+ this.#formatStackTrace(this.#stack, this.#cause, {
120
+ includeHeading: true,
121
+ }),
62
122
  ].filter(line => !!line);
63
123
  return result.join('\n');
64
124
  }
65
- #getType() {
66
- if (this.#msg instanceof Error) {
67
- return 'error';
68
- }
69
- return this.#msg.type();
70
- }
71
- #getText() {
72
- if (this.#msg instanceof Error) {
73
- return this.#msg.message;
74
- }
75
- return this.#msg.text();
76
- }
77
125
  #getArgs() {
78
- if (this.#msg instanceof Error) {
79
- return [];
80
- }
81
126
  if (this.#resolvedArgs.length > 0) {
82
127
  const args = [...this.#resolvedArgs];
83
128
  // If there is no text, the first argument serves as text (see formatMessage).
84
- if (!this.#msg.text()) {
129
+ if (!this.#text) {
85
130
  args.shift();
86
131
  }
87
132
  return args;
@@ -89,6 +134,16 @@ export class ConsoleFormatter {
89
134
  return [];
90
135
  }
91
136
  #formatArg(arg) {
137
+ if (arg instanceof SymbolizedError) {
138
+ return [
139
+ arg.message,
140
+ this.#formatStackTrace(arg.stackTrace, arg.cause, {
141
+ includeHeading: false,
142
+ }),
143
+ ]
144
+ .filter(line => !!line)
145
+ .join('\n');
146
+ }
92
147
  return typeof arg === 'object' ? JSON.stringify(arg) : String(arg);
93
148
  }
94
149
  #formatArgs() {
@@ -102,52 +157,84 @@ export class ConsoleFormatter {
102
157
  }
103
158
  return result.join('\n');
104
159
  }
105
- #formatStackTrace(stackTrace) {
160
+ #formatStackTrace(stackTrace, cause, opts) {
106
161
  if (!stackTrace) {
107
162
  return '';
108
163
  }
164
+ const lines = this.#formatStackTraceInner(stackTrace, cause);
165
+ const includedLines = lines.slice(0, _a.#STACK_TRACE_MAX_LINES);
166
+ const reminderCount = lines.length - includedLines.length;
167
+ return [
168
+ opts.includeHeading ? '### Stack trace' : '',
169
+ ...includedLines,
170
+ reminderCount > 0 ? `... and ${reminderCount} more frames` : '',
171
+ 'Note: line and column numbers use 1-based indexing',
172
+ ]
173
+ .filter(line => !!line)
174
+ .join('\n');
175
+ }
176
+ #formatStackTraceInner(stackTrace, cause) {
177
+ if (!stackTrace) {
178
+ return [];
179
+ }
109
180
  return [
110
- '### Stack trace',
111
- this.#formatFragment(stackTrace.syncFragment),
112
- ...stackTrace.asyncFragments.map(this.#formatAsyncFragment.bind(this)),
113
- ].join('\n');
181
+ ...this.#formatFragment(stackTrace.syncFragment),
182
+ ...stackTrace.asyncFragments
183
+ .map(this.#formatAsyncFragment.bind(this))
184
+ .flat(),
185
+ ...this.#formatCause(cause),
186
+ ];
114
187
  }
115
188
  #formatFragment(fragment) {
116
- return fragment.frames.map(this.#formatFrame.bind(this)).join('\n');
189
+ const frames = fragment.frames.filter(frame => !this.#isIgnored(frame));
190
+ return frames.map(this.#formatFrame.bind(this));
117
191
  }
118
192
  #formatAsyncFragment(fragment) {
193
+ const formattedFrames = this.#formatFragment(fragment);
194
+ if (formattedFrames.length === 0) {
195
+ return [];
196
+ }
119
197
  const separatorLineLength = 40;
120
198
  const prefix = `--- ${fragment.description || 'async'} `;
121
199
  const separator = prefix + '-'.repeat(separatorLineLength - prefix.length);
122
- return separator + '\n' + this.#formatFragment(fragment);
200
+ return [separator, ...formattedFrames];
123
201
  }
124
202
  #formatFrame(frame) {
125
203
  let result = `at ${frame.name ?? '<anonymous>'}`;
126
204
  if (frame.uiSourceCode) {
127
- result += ` (${frame.uiSourceCode.displayName()}:${frame.line}:${frame.column})`;
205
+ const location = frame.uiSourceCode.uiLocation(frame.line, frame.column);
206
+ result += ` (${location.linkText(/* skipTrim */ false, /* showColumnNumber */ true)})`;
128
207
  }
129
208
  else if (frame.url) {
130
209
  result += ` (${frame.url}:${frame.line}:${frame.column})`;
131
210
  }
132
211
  return result;
133
212
  }
213
+ #formatCause(cause) {
214
+ if (!cause) {
215
+ return [];
216
+ }
217
+ return [
218
+ `Caused by: ${cause.message}`,
219
+ ...this.#formatStackTraceInner(cause.stackTrace, cause.cause),
220
+ ];
221
+ }
134
222
  toJSON() {
135
223
  return {
136
- type: this.#getType(),
137
- text: this.#getText(),
138
- argsCount: this.#msg instanceof Error
139
- ? 0
140
- : this.#resolvedArgs.length || this.#msg.args().length,
224
+ type: this.#type,
225
+ text: this.#text,
226
+ argsCount: this.#argCount,
141
227
  id: this.#id,
142
228
  };
143
229
  }
144
230
  toJSONDetailed() {
145
231
  return {
146
232
  id: this.#id,
147
- type: this.#getType(),
148
- text: this.#getText(),
233
+ type: this.#type,
234
+ text: this.#text,
149
235
  args: this.#getArgs().map(arg => typeof arg === 'object' ? arg : String(arg)),
150
- stackTrace: this.#resolvedStackTrace,
236
+ stackTrace: this.#stack,
151
237
  };
152
238
  }
153
239
  }
240
+ _a = ConsoleFormatter;