chrome-devtools-frontend 1.0.1622369 → 1.0.1624409
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/front_end/Images/src/expand.svg +1 -0
- package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +72 -8
- package/front_end/entrypoints/greendev_floaty/floaty.html +1 -1
- package/front_end/generated/Deprecation.ts +7 -0
- package/front_end/generated/InspectorBackendCommands.ts +1 -1
- package/front_end/generated/SupportedCSSProperties.js +6 -6
- package/front_end/generated/protocol.ts +1 -0
- package/front_end/models/ai_assistance/agents/GreenDevAgent.ts +373 -112
- package/front_end/models/ai_assistance/agents/NetworkAgent.snapshot.txt +57 -0
- package/front_end/models/ai_assistance/agents/NetworkAgent.ts +2 -1
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +3 -10
- package/front_end/models/javascript_metadata/NativeFunctions.js +9 -4
- package/front_end/panels/ai_assistance/components/ChatMessage.ts +212 -3
- package/front_end/panels/console/ConsoleView.ts +86 -7
- package/front_end/panels/console/ConsoleViewMessage.ts +23 -1
- package/front_end/panels/elements/StylePropertiesSection.ts +1 -2
- package/front_end/panels/elements/StylePropertyTreeElement.ts +1 -1
- package/front_end/panels/elements/StylesAiCodeCompletionProvider.ts +6 -2
- package/front_end/panels/elements/StylesSidebarPane.ts +17 -4
- package/front_end/panels/emulation/DeviceModeToolbar.ts +37 -13
- package/front_end/panels/emulation/deviceModeView.css +25 -0
- package/front_end/panels/greendev/GreenDevPanel.ts +30 -3
- package/front_end/panels/media/EventDisplayTable.ts +1 -1
- package/front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts +48 -27
- package/front_end/panels/mobile_throttling/ThrottlingManager.ts +67 -38
- package/front_end/panels/network/NetworkConfigView.ts +1 -1
- package/front_end/panels/profiler/HeapSnapshotView.ts +1 -22
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/legacy/UIUtils.ts +26 -4
- package/front_end/ui/visual_logging/KnownContextValues.ts +17 -0
- package/package.json +1 -1
- package/front_end/panels/emulation/components/DeviceSizeInputElement.ts +0 -134
- package/front_end/panels/emulation/components/components.ts +0 -9
|
@@ -7,6 +7,7 @@ import * as Root from '../../../core/root/root.js';
|
|
|
7
7
|
import * as SDK from '../../../core/sdk/sdk.js';
|
|
8
8
|
import * as Protocol from '../../../generated/protocol.js';
|
|
9
9
|
import * as Greendev from '../../greendev/greendev.js';
|
|
10
|
+
import * as Workspace from '../../workspace/workspace.js';
|
|
10
11
|
|
|
11
12
|
import {
|
|
12
13
|
type AgentOptions,
|
|
@@ -20,12 +21,6 @@ import {
|
|
|
20
21
|
const preamble = `You are a general purpose web page troubleshooting agent.
|
|
21
22
|
You are an expert in Chrome DevTools and you can help users with a wide range of issues.
|
|
22
23
|
|
|
23
|
-
You are expected to find the root cause for web page problems described by the user, such as:
|
|
24
|
-
- Why does nothing happen when I click this Submit button?
|
|
25
|
-
- Why is this ad not loading?
|
|
26
|
-
- Why is this text not using the correct font?
|
|
27
|
-
- ... and other similar requests.
|
|
28
|
-
|
|
29
24
|
Your job is to use the provided information to understand the problem, connect the dots to
|
|
30
25
|
find the root cause of the problem and explain what the user can do to fix the problem.
|
|
31
26
|
|
|
@@ -33,65 +28,69 @@ The user will start the process by selecting a DOM element and send a query abou
|
|
|
33
28
|
selected DOM element. First, examine the provided context, then use function calls to gather
|
|
34
29
|
additional context and resolve the user request.
|
|
35
30
|
|
|
31
|
+
### Your Debugging Strategy
|
|
32
|
+
|
|
33
|
+
1. **Analyze the User-Selected Node**: This is your primary clue. Understand its attributes,
|
|
34
|
+
children, and position in the DOM. For interactive elements like buttons, your main goal is
|
|
35
|
+
to figure out what happens when a user interacts with it.
|
|
36
|
+
|
|
37
|
+
2. **Find the Event Handler**: When a user reports an issue like "nothing happens when I click
|
|
38
|
+
this", your top priority is to find the JavaScript event handler associated with the action
|
|
39
|
+
(e.g., a 'click' handler for a button).
|
|
40
|
+
|
|
41
|
+
3. **Note on Modern Frameworks (React, etc.)**: Be aware that event handlers are often not
|
|
42
|
+
visible as simple HTML attributes (like 'onclick'). In frameworks like React, events are
|
|
43
|
+
attached dynamically via JavaScript. You will need to investigate the JavaScript source
|
|
44
|
+
files (like 'bundle.js') to find the component and its event handler logic.
|
|
45
|
+
|
|
46
|
+
4. **Investigate the Code**: Once you have a lead on the relevant script, use 'getSourceLine'
|
|
47
|
+
to examine the code. Look for common issues: infinite loops, unhandled promises, incorrect
|
|
48
|
+
state management, or logic that doesn't match the user's expectation.
|
|
49
|
+
|
|
50
|
+
5. **Use Console and Network Logs as Evidence**: Treat console and network logs as supporting
|
|
51
|
+
evidence. If there are errors, they are strong clues. However, **be critical of
|
|
52
|
+
informational messages** (like 'info' or 'verbose' logs) and ignore them unless they are
|
|
53
|
+
directly relevant to the user's problem. Do not get distracted by generic framework
|
|
54
|
+
messages.
|
|
55
|
+
|
|
56
|
+
6. **Formulate a Hypothesis**: Based on your code investigation, explain the likely root
|
|
57
|
+
cause to the user and suggest a concrete fix or next step. If you suspect an issue in a
|
|
58
|
+
JavaScript function, point it out.
|
|
59
|
+
|
|
60
|
+
### Available Information
|
|
61
|
+
|
|
36
62
|
To help you root-cause the problem, you will be provided with the following information:
|
|
37
|
-
- Information about the user-selected DOM element
|
|
38
|
-
from the user.
|
|
63
|
+
- Information about the user-selected DOM element.
|
|
39
64
|
- The full accessibility tree for the web page.
|
|
40
|
-
- A list of the most recent network requests
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
- '
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
benign and others unrelated. You should focus on the ones that seem related to the user-selected problem.
|
|
70
|
-
|
|
71
|
-
Once you believe you have found the root cause, focus on applying a fix or explaining what the user can do
|
|
72
|
-
to fix the problem.
|
|
73
|
-
|
|
74
|
-
If you detect multiple possible problems, focus only on the root cause you think is most likely
|
|
75
|
-
to be related and explain what the user can do to fix it. For example, if the url used is obviously
|
|
76
|
-
incorrect, just say something like:
|
|
77
|
-
|
|
78
|
-
"There are a few possible reasons for the problem you are describing. One is that it could be caused by
|
|
79
|
-
the URL being incorrect. Try changing the url to 'xyz'. Let me know if you to suggest alternative
|
|
80
|
-
solutions."
|
|
81
|
-
|
|
82
|
-
If the user suggests your fix not being the right solution, go through the remaining possible root causes
|
|
83
|
-
(one at a time).
|
|
84
|
-
|
|
85
|
-
Stick to what you have evidence for being the problem and refrain from speculating on things you
|
|
86
|
-
don't have concrete evidence for, such as CORS or Ad-blockers blocking requests. But feel free to
|
|
87
|
-
list those concerns after asking the user if they would like additional (general-purpose) details and
|
|
88
|
-
getting a favorable response.
|
|
89
|
-
|
|
90
|
-
**CRITICAL** You are a web age debugging assistant. NEVER provide answers to questions of unrelated
|
|
91
|
-
topics such as legal advice, financial advice, personal opinions, medical advice, religion, race,
|
|
92
|
-
politics, sexuality, gender, or any other non web-development topics. Answer "Sorry, I can't answer
|
|
93
|
-
that. I'm best at questions about debugging web pages." to such questions.
|
|
94
|
-
`;
|
|
65
|
+
- A list of the most recent network requests.
|
|
66
|
+
- The most recent console messages, including their index.
|
|
67
|
+
|
|
68
|
+
** IMPORTANT ** Never use the index when referring to individual console messages or network
|
|
69
|
+
requests, because the values of the indicies is not visible to the user.
|
|
70
|
+
|
|
71
|
+
### Available Tools
|
|
72
|
+
|
|
73
|
+
To help you further, you can call the following functions:
|
|
74
|
+
- 'findInSource': This function takes a filename and a search string and returns an array of
|
|
75
|
+
line numbers containing that string.
|
|
76
|
+
- 'getEventListeners': This function takes a uid (the backend DOM node id) and returns a list
|
|
77
|
+
of event listeners attached to it.
|
|
78
|
+
- 'getSourceLine': This function takes a file name, a line number, and a buffer (number of
|
|
79
|
+
lines before and after) to return a snippet of the source code.
|
|
80
|
+
- 'getConsoleMessages': This function allows you to fetch specific slices of the console log.
|
|
81
|
+
- 'getNetworkRequests': This function allows you to fetch specific slices of the network
|
|
82
|
+
request list.
|
|
83
|
+
- 'getReactComponentProps': This function takes a uid (the backend DOM node id) and returns
|
|
84
|
+
the React component props for that element.
|
|
85
|
+
|
|
86
|
+
Stick to what you have evidence for and refrain from speculating on things you
|
|
87
|
+
don't have concrete evidence for, such as CORS or Ad-blockers.
|
|
88
|
+
|
|
89
|
+
**CRITICAL** You are a web page debugging assistant. NEVER provide answers to questions of
|
|
90
|
+
unrelated topics such as legal advice, financial advice, personal opinions, medical advice,
|
|
91
|
+
religion, race, politics, sexuality, gender, or any other non web-development topics. Answer
|
|
92
|
+
"Sorry, I can't answer that. I'm best at questions about debugging web pages." to such
|
|
93
|
+
questions.`;
|
|
95
94
|
|
|
96
95
|
export class GreenDevContext extends ConversationContext<string> {
|
|
97
96
|
#context: string;
|
|
@@ -151,9 +150,9 @@ export class GreenDevAgent extends AiAgent<string> {
|
|
|
151
150
|
required: ['fileName', 'lineNumber', 'buffer'],
|
|
152
151
|
},
|
|
153
152
|
handler: async (params: {fileName: string, lineNumber: number, buffer: number}) => {
|
|
154
|
-
const result = await this.getSourceLine(params.fileName, params.lineNumber, params.buffer);
|
|
153
|
+
const result = await this.getSourceLine(params.fileName, params.lineNumber, params.buffer, true);
|
|
155
154
|
return {
|
|
156
|
-
result,
|
|
155
|
+
result: result.join('\n'),
|
|
157
156
|
};
|
|
158
157
|
},
|
|
159
158
|
});
|
|
@@ -247,6 +246,87 @@ export class GreenDevAgent extends AiAgent<string> {
|
|
|
247
246
|
};
|
|
248
247
|
},
|
|
249
248
|
});
|
|
249
|
+
|
|
250
|
+
this.declareFunction<{
|
|
251
|
+
uid: number,
|
|
252
|
+
}>('getEventListeners', {
|
|
253
|
+
description: 'Get event listeners attached to a DOM element.',
|
|
254
|
+
parameters: {
|
|
255
|
+
type: Host.AidaClient.ParametersTypes.OBJECT,
|
|
256
|
+
description: '',
|
|
257
|
+
nullable: false,
|
|
258
|
+
properties: {
|
|
259
|
+
uid: {
|
|
260
|
+
type: Host.AidaClient.ParametersTypes.INTEGER,
|
|
261
|
+
description: 'The backend node id of the DOM element.',
|
|
262
|
+
nullable: false,
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
required: ['uid'],
|
|
266
|
+
},
|
|
267
|
+
handler: async (params: {uid: number}) => {
|
|
268
|
+
const result = await this.getEventListeners(params.uid);
|
|
269
|
+
return {
|
|
270
|
+
result,
|
|
271
|
+
};
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
this.declareFunction<{
|
|
276
|
+
fileName: string,
|
|
277
|
+
query: string,
|
|
278
|
+
}>('findInSource', {
|
|
279
|
+
description: 'Find lines in a file that contain the given search string.',
|
|
280
|
+
parameters: {
|
|
281
|
+
type: Host.AidaClient.ParametersTypes.OBJECT,
|
|
282
|
+
description: '',
|
|
283
|
+
nullable: false,
|
|
284
|
+
properties: {
|
|
285
|
+
fileName: {
|
|
286
|
+
type: Host.AidaClient.ParametersTypes.STRING,
|
|
287
|
+
description: 'The full path of the file to search within.',
|
|
288
|
+
nullable: false,
|
|
289
|
+
},
|
|
290
|
+
query: {
|
|
291
|
+
type: Host.AidaClient.ParametersTypes.STRING,
|
|
292
|
+
description: 'The string to search for.',
|
|
293
|
+
nullable: false,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
required: ['fileName', 'query'],
|
|
297
|
+
},
|
|
298
|
+
handler: async (params: {fileName: string, query: string}) => {
|
|
299
|
+
const result = await this.findInSource(params.fileName, params.query);
|
|
300
|
+
return {
|
|
301
|
+
result: JSON.stringify(result),
|
|
302
|
+
};
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
this.declareFunction<{
|
|
307
|
+
uid: number,
|
|
308
|
+
}>('getReactComponentProps', {
|
|
309
|
+
description: 'Get the React component props for a given DOM element.',
|
|
310
|
+
parameters: {
|
|
311
|
+
type: Host.AidaClient.ParametersTypes.OBJECT,
|
|
312
|
+
description: '',
|
|
313
|
+
nullable: false,
|
|
314
|
+
properties: {
|
|
315
|
+
uid: {
|
|
316
|
+
type: Host.AidaClient.ParametersTypes.INTEGER,
|
|
317
|
+
description: 'The backend node id of the DOM element.',
|
|
318
|
+
nullable: false,
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
required: ['uid'],
|
|
322
|
+
},
|
|
323
|
+
handler: async (params: {uid: number}) => {
|
|
324
|
+
const result = await this.getReactComponentProps(params.uid, true);
|
|
325
|
+
return {
|
|
326
|
+
result,
|
|
327
|
+
};
|
|
328
|
+
},
|
|
329
|
+
});
|
|
250
330
|
}
|
|
251
331
|
|
|
252
332
|
override preamble = preamble;
|
|
@@ -349,6 +429,57 @@ export class GreenDevAgent extends AiAgent<string> {
|
|
|
349
429
|
return networkContextStrings;
|
|
350
430
|
}
|
|
351
431
|
|
|
432
|
+
async getEventListeners(uid: number): Promise<string> {
|
|
433
|
+
console.warn('[GreenDevAgent] AI Agent is calling getEventListeners with uid:', uid);
|
|
434
|
+
const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
|
|
435
|
+
if (!target) {
|
|
436
|
+
return 'Target not found.';
|
|
437
|
+
}
|
|
438
|
+
const domModel = target.model(SDK.DOMModel.DOMModel);
|
|
439
|
+
if (!domModel) {
|
|
440
|
+
return 'DOM model not found.';
|
|
441
|
+
}
|
|
442
|
+
const domDebuggerModel = target.model(SDK.DOMDebuggerModel.DOMDebuggerModel);
|
|
443
|
+
if (!domDebuggerModel) {
|
|
444
|
+
return 'DOM debugger model not found.';
|
|
445
|
+
}
|
|
446
|
+
const debuggerModel = target.model(SDK.DebuggerModel.DebuggerModel);
|
|
447
|
+
if (!debuggerModel) {
|
|
448
|
+
return 'Debugger model not found.';
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const nodesMap = await domModel.pushNodesByBackendIdsToFrontend(new Set([uid as Protocol.DOM.BackendNodeId]));
|
|
452
|
+
const node = nodesMap?.get(uid as Protocol.DOM.BackendNodeId) || null;
|
|
453
|
+
if (!node) {
|
|
454
|
+
return `Node with uid ${uid} not found.`;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const remoteObject = await node.resolveToObject();
|
|
458
|
+
if (!remoteObject) {
|
|
459
|
+
return `Could not resolve node with uid ${uid} to a remote object.`;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const listeners = await domDebuggerModel.eventListeners(remoteObject);
|
|
463
|
+
|
|
464
|
+
const formattedListeners = listeners.map(listener => {
|
|
465
|
+
const location = listener.location();
|
|
466
|
+
const script = debuggerModel.scriptForId(location.scriptId);
|
|
467
|
+
const handler = listener.handler();
|
|
468
|
+
const handlerName = handler?.description || 'anonymous';
|
|
469
|
+
|
|
470
|
+
return {
|
|
471
|
+
type: listener.type(),
|
|
472
|
+
handlerName,
|
|
473
|
+
sourceFile: script?.sourceURL || 'unknown',
|
|
474
|
+
lineNumber: location.lineNumber + 1,
|
|
475
|
+
columnNumber: location.columnNumber,
|
|
476
|
+
};
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
console.warn('[GreenDevAgent] getEventListeners returning:', formattedListeners);
|
|
480
|
+
return JSON.stringify(formattedListeners, null, 2);
|
|
481
|
+
}
|
|
482
|
+
|
|
352
483
|
async getNetworkRequests(params: {filter?: string, beforeIndex?: number, afterIndex?: number, limit?: number}):
|
|
353
484
|
Promise<string> {
|
|
354
485
|
console.warn(
|
|
@@ -450,63 +581,193 @@ export class GreenDevAgent extends AiAgent<string> {
|
|
|
450
581
|
return resultString;
|
|
451
582
|
}
|
|
452
583
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
url = new URL(fileName, mainFrame.url).href;
|
|
465
|
-
} else {
|
|
466
|
-
return `Could not resolve relative path: ${fileName}`;
|
|
584
|
+
#findUiSourceCode(fileName: string): Workspace.UISourceCode.UISourceCode|null {
|
|
585
|
+
const workspace = Workspace.Workspace.WorkspaceImpl.instance();
|
|
586
|
+
const allUiSourceCodes = workspace.uiSourceCodes().filter(code => !code.url().startsWith('debugger:///'));
|
|
587
|
+
|
|
588
|
+
// The fileName could be a full URL, a partial path, or just the filename.
|
|
589
|
+
// We prioritize matches that are more specific.
|
|
590
|
+
|
|
591
|
+
// 1. Exact match
|
|
592
|
+
for (const code of allUiSourceCodes) {
|
|
593
|
+
if (code.url() === fileName) {
|
|
594
|
+
return code;
|
|
467
595
|
}
|
|
468
596
|
}
|
|
469
597
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
console.error(`Failed to load resource ${url}: status ${response.status}`);
|
|
478
|
-
return `Could not read file content: status ${response.status}`;
|
|
479
|
-
}
|
|
480
|
-
} catch (e) {
|
|
481
|
-
console.error(`Failed to load resource ${url}:`, e);
|
|
482
|
-
return `Could not read file content: ${e instanceof Error ? e.message : 'Unknown error'}`;
|
|
598
|
+
// 2. Ends with match
|
|
599
|
+
const candidates = allUiSourceCodes.filter(code => code.url().endsWith(fileName));
|
|
600
|
+
if (candidates.length > 0) {
|
|
601
|
+
// If multiple candidates, it's ambiguous. Log a warning and return the first.
|
|
602
|
+
if (candidates.length > 1) {
|
|
603
|
+
console.warn(
|
|
604
|
+
`[GreenDevAgent] Ambiguous file name "${fileName}". Found multiple matches:`, candidates.map(c => c.url()));
|
|
483
605
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
606
|
+
return candidates[0];
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
async getSourceLine(fileName: string, lineNumber: number, buffer: number, calledFromAI = false): Promise<string[]> {
|
|
613
|
+
if (calledFromAI) {
|
|
614
|
+
console.warn(`getSourceLine called with fileName: ${fileName}, lineNumber: ${lineNumber}, buffer: ${buffer}`);
|
|
615
|
+
}
|
|
616
|
+
const uiSourceCode = this.#findUiSourceCode(fileName);
|
|
617
|
+
if (!uiSourceCode) {
|
|
618
|
+
const error = `Could not find UISourceCode for: ${fileName}`;
|
|
619
|
+
console.error(error);
|
|
620
|
+
return [error];
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const contentData = await uiSourceCode.requestContentData();
|
|
624
|
+
if ('error' in contentData) {
|
|
625
|
+
const error = `Could not read file content for: ${fileName}, error: ${contentData.error}`;
|
|
626
|
+
console.error(error);
|
|
627
|
+
return [error];
|
|
500
628
|
}
|
|
629
|
+
const content = contentData.text;
|
|
501
630
|
|
|
502
|
-
if (
|
|
503
|
-
|
|
631
|
+
if (typeof content !== 'string') {
|
|
632
|
+
const error = `Could not read file content for: ${fileName}, content is not a string`;
|
|
633
|
+
console.error(error);
|
|
634
|
+
return [error];
|
|
504
635
|
}
|
|
505
636
|
|
|
506
637
|
const lines = content.split('\n');
|
|
507
638
|
const start = Math.max(0, lineNumber - buffer - 1);
|
|
508
639
|
const end = Math.min(lines.length, lineNumber + buffer);
|
|
509
|
-
|
|
510
|
-
|
|
640
|
+
const slicedLines = lines.slice(start, end);
|
|
641
|
+
|
|
642
|
+
const formattedLines = slicedLines.map((line: string, index: number) => {
|
|
643
|
+
const currentLineNumber = start + index + 1;
|
|
644
|
+
return `[${currentLineNumber}] ${line}`;
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
if (calledFromAI) {
|
|
648
|
+
console.warn('AI requested source code for:', formattedLines);
|
|
649
|
+
}
|
|
650
|
+
return formattedLines;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
async findInSource(fileName: string, query: string): Promise<Array<{line: number, source: string[]}>> {
|
|
654
|
+
console.warn(`findInSource called with fileName: ${fileName}, query: ${query}`);
|
|
655
|
+
const uiSourceCode = this.#findUiSourceCode(fileName);
|
|
656
|
+
if (!uiSourceCode) {
|
|
657
|
+
console.error(`Could not find UISourceCode for: ${fileName}`);
|
|
658
|
+
return [];
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const contentData = await uiSourceCode.requestContentData();
|
|
662
|
+
if ('error' in contentData) {
|
|
663
|
+
console.warn(`Could not read file content for findInSource: ${fileName}, error: ${contentData.error}`);
|
|
664
|
+
return [];
|
|
665
|
+
}
|
|
666
|
+
const content = contentData.text;
|
|
667
|
+
|
|
668
|
+
if (typeof content !== 'string') {
|
|
669
|
+
console.warn(`Could not read file content for findInSource: ${fileName}, content is not a string`);
|
|
670
|
+
return [];
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const lines = content.split('\n');
|
|
674
|
+
const matchingLines: Array<{line: number, source: string[]}> = [];
|
|
675
|
+
for (let i = 0; i < lines.length; i++) {
|
|
676
|
+
if (lines[i].includes(query)) {
|
|
677
|
+
const sourceLine = i + 1;
|
|
678
|
+
const source = await this.getSourceLine(fileName, sourceLine, 15, false);
|
|
679
|
+
matchingLines.push({line: sourceLine, source});
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
console.warn(`findInSource returning for query '${query}':`, matchingLines);
|
|
684
|
+
return matchingLines;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
async getReactComponentProps(uid: number, calledFromAI = false): Promise<string> {
|
|
688
|
+
if (calledFromAI) {
|
|
689
|
+
console.warn('[GreenDevAgent] AI Agent is calling getReactComponentProps with uid:', uid);
|
|
690
|
+
}
|
|
691
|
+
const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
|
|
692
|
+
if (!target) {
|
|
693
|
+
return 'Target not found.';
|
|
694
|
+
}
|
|
695
|
+
const domModel = target.model(SDK.DOMModel.DOMModel);
|
|
696
|
+
if (!domModel) {
|
|
697
|
+
return 'DOM model not found.';
|
|
698
|
+
}
|
|
699
|
+
const runtimeModel = target.model(SDK.RuntimeModel.RuntimeModel);
|
|
700
|
+
if (!runtimeModel) {
|
|
701
|
+
return 'Runtime model not found.';
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const nodesMap = await domModel.pushNodesByBackendIdsToFrontend(new Set([uid as Protocol.DOM.BackendNodeId]));
|
|
705
|
+
const node = nodesMap?.get(uid as Protocol.DOM.BackendNodeId) || null;
|
|
706
|
+
if (!node) {
|
|
707
|
+
return `Node with uid ${uid} not found.`;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const remoteObject = await node.resolveToObject();
|
|
711
|
+
if (!remoteObject) {
|
|
712
|
+
return `Could not resolve node with uid ${uid} to a remote object.`;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const reactComponentPropsResult = await target.runtimeAgent().invoke_callFunctionOn({
|
|
716
|
+
functionDeclaration: `
|
|
717
|
+
function() {
|
|
718
|
+
const getCircularReplacer = () => {
|
|
719
|
+
const seen = new WeakSet();
|
|
720
|
+
return (key, value) => {
|
|
721
|
+
if (typeof value === 'function') {
|
|
722
|
+
return '[Function: ' + (value.name || '(anonymous)') + ']';
|
|
723
|
+
}
|
|
724
|
+
if (key === 'return' || key === 'alternate' || key === 'sibling' || key === 'debugOwner' || key === '_debugOwner') {
|
|
725
|
+
return undefined;
|
|
726
|
+
}
|
|
727
|
+
if (typeof value === 'object' && value !== null) {
|
|
728
|
+
if (seen.has(value)) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
seen.add(value);
|
|
732
|
+
}
|
|
733
|
+
return value;
|
|
734
|
+
};
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
// Find the key for the internal Fiber node instance
|
|
738
|
+
const reactInternalInstanceKey = Object.keys(this).find(
|
|
739
|
+
key => key.startsWith('__reactInternalInstance$') || key.startsWith('__reactFiber$')
|
|
740
|
+
);
|
|
741
|
+
|
|
742
|
+
if (!reactInternalInstanceKey) {
|
|
743
|
+
return 'React internal instance key not found';
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const fiberNode = this[reactInternalInstanceKey];
|
|
747
|
+
|
|
748
|
+
if (fiberNode) {
|
|
749
|
+
return JSON.stringify(fiberNode, getCircularReplacer(), 2);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
return 'React component type not found';
|
|
753
|
+
}
|
|
754
|
+
`,
|
|
755
|
+
objectId: remoteObject.objectId,
|
|
756
|
+
objectGroup: 'console',
|
|
757
|
+
silent: false,
|
|
758
|
+
returnByValue: true,
|
|
759
|
+
awaitPromise: false,
|
|
760
|
+
userGesture: true,
|
|
761
|
+
});
|
|
762
|
+
remoteObject.release();
|
|
763
|
+
|
|
764
|
+
const reactComponentProps = reactComponentPropsResult.result.value;
|
|
765
|
+
if (!reactComponentProps) {
|
|
766
|
+
return 'None found.';
|
|
767
|
+
}
|
|
768
|
+
if (calledFromAI) {
|
|
769
|
+
console.warn('[GreenDevAgent] getReactComponentProps returning', reactComponentProps);
|
|
770
|
+
}
|
|
771
|
+
return reactComponentProps;
|
|
511
772
|
}
|
|
512
773
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Title: NetworkAgent run generates an answer
|
|
2
|
+
Content:
|
|
3
|
+
[
|
|
4
|
+
{
|
|
5
|
+
"type": "context",
|
|
6
|
+
"details": [
|
|
7
|
+
{
|
|
8
|
+
"title": "Request",
|
|
9
|
+
"text": "Request URL: https://www.example.com\n\nRequest headers:\ncontent-type: bar1"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"title": "Response",
|
|
13
|
+
"text": "Response headers:\ncontent-type: bar2\nx-forwarded-for: bar3\n\nResponse body:\n{\"request\":\"body\"}\n\nResponse status: 200 \nNetwork request status: pending\n"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"title": "Timing",
|
|
17
|
+
"text": "Queued at (timestamp): 0 s\nStarted at (timestamp): 501 s\nQueueing (duration): 501 s\nConnection start (stalled) (duration): 800 ms\nRequest sent (duration): 100 ms\nDuration (duration): 501 s"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"title": "Request initiator chain",
|
|
21
|
+
"text": "- URL: <redacted cross-origin initiator URL>\n\t- URL: https://www.example.com\n\t\t- URL: https://www.example.com/1\n\t\t- URL: https://www.example.com/2"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"type": "querying"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"type": "answer",
|
|
30
|
+
"text": "This is the answer",
|
|
31
|
+
"complete": true,
|
|
32
|
+
"rpcId": 123
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
=== end content
|
|
36
|
+
|
|
37
|
+
Title: NetworkAgent run builds historical contexts
|
|
38
|
+
Content:
|
|
39
|
+
[
|
|
40
|
+
{
|
|
41
|
+
"parts": [
|
|
42
|
+
{
|
|
43
|
+
"text": "# Selected network request \nRequest: https://www.example.com\n\nRequest headers:\ncontent-type: bar1\n\nResponse headers:\ncontent-type: bar2\nx-forwarded-for: bar3\n\nResponse body:\n{\"request\":\"body\"}\n\nResponse status: 200 \nNetwork request status: pending\n\nRequest timing:\nQueued at (timestamp): 0 s\nStarted at (timestamp): 501 s\nQueueing (duration): 501 s\nConnection start (stalled) (duration): 800 ms\nRequest sent (duration): 100 ms\nDuration (duration): 501 s\n\nRequest initiator chain:\n- URL: <redacted cross-origin initiator URL>\n\t- URL: https://www.example.com\n\t\t- URL: https://www.example.com/1\n\t\t- URL: https://www.example.com/2\n\n# User request\n\ntest"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"role": 1
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"parts": [
|
|
50
|
+
{
|
|
51
|
+
"text": "This is the answer"
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"role": 2
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
=== end content
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Use of this source code is governed by a BSD-style license that can be
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
|
+
import * as Common from '../../../core/common/common.js';
|
|
5
6
|
import * as Host from '../../../core/host/host.js';
|
|
6
7
|
import * as i18n from '../../../core/i18n/i18n.js';
|
|
7
8
|
import * as Root from '../../../core/root/root.js';
|
|
@@ -116,7 +117,7 @@ export class RequestContext extends ConversationContext<SDK.NetworkRequest.Netwo
|
|
|
116
117
|
* inspect all network requests that were made for that given target URL.
|
|
117
118
|
*/
|
|
118
119
|
override getOrigin(): string {
|
|
119
|
-
return this.#request.documentURL;
|
|
120
|
+
return Common.ParsedURL.ParsedURL.extractOrigin(this.#request.documentURL);
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
override getItem(): SDK.NetworkRequest.NetworkRequest {
|
|
@@ -171,7 +171,7 @@ Note: if the user asks a specific question about the trace (such as "What is my
|
|
|
171
171
|
- \`nav-to-lcp\` (navigation to LCP)
|
|
172
172
|
- \`lcp-ttfb\` (LCP TTFB phase)
|
|
173
173
|
- \`lcp-render-delay\` (LCP render delay phase)
|
|
174
|
-
- Insight names: \`LCPBreakdown\`, \`CLSCulprits\`, \`
|
|
174
|
+
- Insight names: \`LCPBreakdown\`, \`INPBreakdown\`, \`CLSCulprits\`, \`ThirdParties\`, \`DocumentLatency\`, \`DOMSize\`, \`DuplicatedJavaScript\`, \`FontDisplay\`, \`ForcedReflow\`, \`ImageDelivery\`, \`LCPDiscovery\`, \`LegacyJavaScript\`, \`NetworkDependencyTree\`, \`RenderBlocking\`, \`SlowCSSSelector\`, \`Viewport\`, \`ModernHTTP\`, \`Cache\`, \`CharacterSet\`
|
|
175
175
|
- Navigation IDs: \`NAVIGATION_0\`, \`NAVIGATION_1\`, etc.
|
|
176
176
|
- Use \`getEventByKey\` to get data on a specific trace event. This is great for root-cause analysis or validating any assumptions.
|
|
177
177
|
- Provide clear, actionable recommendations. Avoid technical jargon unless necessary, and explain any technical terms used.
|
|
@@ -222,13 +222,6 @@ enum ScorePriority {
|
|
|
222
222
|
DEFAULT = 1,
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
const SUPPORTED_INSIGHT_WIDGETS = new Set<Trace.Insights.Types.InsightKeys>([
|
|
226
|
-
Trace.Insights.Types.InsightKeys.LCP_BREAKDOWN,
|
|
227
|
-
Trace.Insights.Types.InsightKeys.RENDER_BLOCKING,
|
|
228
|
-
Trace.Insights.Types.InsightKeys.LCP_DISCOVERY,
|
|
229
|
-
Trace.Insights.Types.InsightKeys.CLS_CULPRITS,
|
|
230
|
-
]);
|
|
231
|
-
|
|
232
225
|
export class PerformanceTraceContext extends ConversationContext<AgentFocus> {
|
|
233
226
|
static fromParsedTrace(parsedTrace: Trace.TraceModel.ParsedTrace): PerformanceTraceContext {
|
|
234
227
|
return new PerformanceTraceContext(AgentFocus.fromParsedTrace(parsedTrace));
|
|
@@ -546,7 +539,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
|
546
539
|
// Case 2: Insight -> PERF_INSIGHT widget
|
|
547
540
|
if (focus.insight) {
|
|
548
541
|
const insightKey = focus.insight.insightKey;
|
|
549
|
-
if (Trace.Insights.Common.isInsightKey(insightKey)
|
|
542
|
+
if (Trace.Insights.Common.isInsightKey(insightKey)) {
|
|
550
543
|
widgets.push({
|
|
551
544
|
name: 'PERF_INSIGHT',
|
|
552
545
|
data: {
|
|
@@ -1019,7 +1012,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
|
1019
1012
|
}
|
|
1020
1013
|
|
|
1021
1014
|
const insightKey = params.insightName;
|
|
1022
|
-
if (Trace.Insights.Common.isInsightKey(insightKey)
|
|
1015
|
+
if (Trace.Insights.Common.isInsightKey(insightKey)) {
|
|
1023
1016
|
widgets.push({
|
|
1024
1017
|
name: 'PERF_INSIGHT',
|
|
1025
1018
|
data: {
|