chrome-devtools-frontend 1.0.1592129 → 1.0.1593518

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.
Files changed (125) hide show
  1. package/front_end/Images/src/tab-move.svg +1 -0
  2. package/front_end/application_tokens.css +4 -4
  3. package/front_end/core/host/UserMetrics.ts +2 -1
  4. package/front_end/core/root/ExperimentNames.ts +1 -0
  5. package/front_end/entrypoints/main/MainImpl.ts +8 -0
  6. package/front_end/generated/InspectorBackendCommands.ts +3 -1
  7. package/front_end/generated/SupportedCSSProperties.js +2 -2
  8. package/front_end/generated/protocol-mapping.d.ts +13 -0
  9. package/front_end/generated/protocol-proxy-api.d.ts +12 -0
  10. package/front_end/generated/protocol.ts +75 -0
  11. package/front_end/models/ai_assistance/agents/AiAgent.ts +24 -1
  12. package/front_end/models/ai_assistance/agents/BreakpointDebuggerAgent.ts +320 -26
  13. package/front_end/models/ai_assistance/agents/BreakpointDebuggerAgentOverlay.ts +87 -0
  14. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +8 -8
  15. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +79 -48
  16. package/front_end/models/ai_assistance/agents/StylingAgent.ts +16 -2
  17. package/front_end/models/computed_style/ComputedStyleModel.ts +40 -6
  18. package/front_end/models/javascript_metadata/NativeFunctions.js +6 -6
  19. package/front_end/panels/accessibility/AXBreadcrumbsPane.ts +5 -3
  20. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +39 -7
  21. package/front_end/panels/ai_assistance/README.md +61 -0
  22. package/front_end/panels/ai_assistance/components/ChatMessage.ts +141 -27
  23. package/front_end/panels/ai_assistance/components/ChatView.ts +5 -1
  24. package/front_end/panels/ai_assistance/components/MarkdownRendererWithCodeBlock.ts +55 -1
  25. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +19 -7
  26. package/front_end/panels/ai_assistance/components/chatMessage.css +30 -0
  27. package/front_end/panels/ai_assistance/components/walkthroughView.css +12 -7
  28. package/front_end/panels/animation/AnimationGroupPreviewUI.ts +1 -1
  29. package/front_end/panels/application/CookieItemsView.ts +194 -134
  30. package/front_end/panels/application/DeviceBoundSessionsTreeElement.ts +1 -1
  31. package/front_end/panels/application/ServiceWorkerUpdateCycleView.ts +1 -0
  32. package/front_end/panels/application/StorageItemsToolbar.ts +25 -2
  33. package/front_end/panels/application/components/BackForwardCacheView.ts +4 -2
  34. package/front_end/panels/application/cookieItemsView.css +28 -26
  35. package/front_end/panels/autofill/AutofillView.ts +1 -1
  36. package/front_end/panels/browser_debugger/DOMBreakpointsSidebarPane.ts +161 -154
  37. package/front_end/panels/browser_debugger/XHRBreakpointsSidebarPane.ts +1 -0
  38. package/front_end/panels/browser_debugger/domBreakpointsSidebarPane.css +58 -36
  39. package/front_end/panels/console/ConsoleViewMessage.ts +2 -1
  40. package/front_end/panels/console/consoleView.css +7 -2
  41. package/front_end/panels/elements/ComputedStyleWidget.ts +16 -26
  42. package/front_end/panels/elements/ElementsPanel.ts +26 -3
  43. package/front_end/panels/elements/ElementsTreeElement.ts +1 -0
  44. package/front_end/panels/elements/LayoutPane.ts +8 -7
  45. package/front_end/panels/elements/StandaloneStylesContainer.ts +254 -0
  46. package/front_end/panels/elements/StylePropertyTreeElement.ts +33 -0
  47. package/front_end/panels/elements/StylesAiCodeCompletionProvider.ts +148 -1
  48. package/front_end/panels/elements/components/ComputedStyleTrace.ts +4 -0
  49. package/front_end/panels/elements/components/ElementsBreadcrumbs.ts +1 -1
  50. package/front_end/panels/elements/components/StylePropertyEditor.ts +2 -1
  51. package/front_end/panels/elements/components/computedStyleProperty.css +1 -1
  52. package/front_end/panels/elements/elements-meta.ts +3 -5
  53. package/front_end/panels/elements/elements.ts +2 -0
  54. package/front_end/panels/elements/stylePropertiesTreeOutline.css +6 -0
  55. package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +1 -1
  56. package/front_end/panels/linear_memory_inspector/components/LinearMemoryValueInterpreter.ts +1 -1
  57. package/front_end/panels/linear_memory_inspector/components/ValueInterpreterDisplay.ts +1 -1
  58. package/front_end/panels/media/PlayerListView.ts +1 -1
  59. package/front_end/panels/network/NetworkLogViewColumns.ts +2 -1
  60. package/front_end/panels/network/components/RequestHeaderSection.ts +1 -1
  61. package/front_end/panels/protocol_monitor/JSONEditor.ts +1 -1
  62. package/front_end/panels/recorder/components/RecordingListView.ts +1 -1
  63. package/front_end/panels/recorder/components/StepEditor.ts +3 -3
  64. package/front_end/panels/settings/KeybindsSettingsTab.ts +2 -1
  65. package/front_end/panels/sources/components/HeadersView.ts +2 -2
  66. package/front_end/panels/timeline/components/BreadcrumbsUI.ts +1 -1
  67. package/front_end/panels/timeline/components/SidebarAnnotationsTab.ts +1 -1
  68. package/front_end/panels/timeline/components/metricCard.css +0 -4
  69. package/front_end/third_party/chromium/README.chromium +1 -1
  70. package/front_end/third_party/puppeteer/README.chromium +2 -2
  71. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Navigation.d.ts.map +1 -1
  72. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Navigation.js +1 -0
  73. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Navigation.js.map +1 -1
  74. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/BrowserContext.js +1 -1
  75. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/BrowserContext.js.map +1 -1
  76. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js +1 -1
  77. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Page.js.map +1 -1
  78. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  79. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  80. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js +1 -0
  81. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js.map +1 -1
  82. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
  83. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
  84. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
  85. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  86. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
  87. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
  88. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +6 -6
  89. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Navigation.d.ts.map +1 -1
  90. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Navigation.js +1 -0
  91. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Navigation.js.map +1 -1
  92. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/BrowserContext.js +1 -1
  93. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/BrowserContext.js.map +1 -1
  94. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js +1 -1
  95. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Page.js.map +1 -1
  96. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  97. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js +1 -0
  98. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js.map +1 -1
  99. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
  100. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
  101. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
  102. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
  103. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
  104. package/front_end/third_party/puppeteer/package/package.json +4 -4
  105. package/front_end/third_party/puppeteer/package/src/bidi/core/Navigation.ts +1 -0
  106. package/front_end/third_party/puppeteer/package/src/cdp/BrowserContext.ts +1 -1
  107. package/front_end/third_party/puppeteer/package/src/cdp/Page.ts +1 -1
  108. package/front_end/third_party/puppeteer/package/src/node/ChromeLauncher.ts +1 -0
  109. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  110. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  111. package/front_end/ui/components/suggestion_input/SuggestionInput.ts +7 -12
  112. package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +13 -2
  113. package/front_end/ui/components/tree_outline/TreeOutline.ts +6 -2
  114. package/front_end/ui/components/tree_outline/treeOutline.css +5 -0
  115. package/front_end/ui/legacy/ListControl.ts +5 -3
  116. package/front_end/ui/legacy/ListWidget.ts +1 -1
  117. package/front_end/ui/legacy/SoftContextMenu.ts +2 -1
  118. package/front_end/ui/legacy/Treeoutline.ts +1 -0
  119. package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +44 -24
  120. package/front_end/ui/legacy/components/data_grid/DataGrid.ts +4 -2
  121. package/front_end/ui/legacy/components/quick_open/FilteredListWidget.ts +1 -1
  122. package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +31 -0
  123. package/front_end/ui/visual_logging/Debugging.ts +4 -0
  124. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  125. package/package.json +1 -1
@@ -5,7 +5,6 @@
5
5
  import * as Common from '../../../core/common/common.js';
6
6
  import * as Host from '../../../core/host/host.js';
7
7
  import * as i18n from '../../../core/i18n/i18n.js';
8
- import * as Platform from '../../../core/platform/platform.js';
9
8
  import * as Root from '../../../core/root/root.js';
10
9
  import * as SDK from '../../../core/sdk/sdk.js';
11
10
  import * as Logs from '../../logs/logs.js';
@@ -35,13 +34,12 @@ You are a Web Development Assistant integrated into Chrome DevTools. Your tone i
35
34
  You aim to help developers of all levels, prioritizing teaching web concepts as the primary entry point for any solution.
36
35
 
37
36
  # Considerations
38
- * Determine what the question the domain of the question is - styling, network, sources, performance or other part of DevTools.
37
+ * Determine what is the domain of the question - styling, network, sources, performance or other part of DevTools.
39
38
  * Proactively try to gather additional data. If a select specific data can be selected, select one.
40
39
  * Always try select single specific context before answering the question.
41
40
  * Avoid making assumptions without sufficient evidence, and always seek further clarification if needed.
42
41
  * When presenting solutions, clearly distinguish between the primary cause and contributing factors.
43
42
  * Please answer only if you are sure about the answer. Otherwise, explain why you're not able to answer.
44
- * When answering, always consider MULTIPLE possible solutions.
45
43
  * If you are unable to gather more information provide a comprehensive guide to how to fix the issue using Chrome DevTools and explain how and why.
46
44
  * You can suggest any panel or flow in Chrome DevTools that may help the user out
47
45
 
@@ -50,9 +48,13 @@ You aim to help developers of all levels, prioritizing teaching web concepts as
50
48
  * Always specify the language for code blocks (e.g., \`\`\`css, \`\`\`javascript).
51
49
  * Keep text responses concise and scannable.
52
50
 
51
+ * **CRITICAL** If a tool returns an empty list, immediately pivot to the next logical tool (e.g., from sources to network).
52
+ * **CRITICAL** Always exhaust all possible way to find and select context from different domains.
53
53
  * **CRITICAL** NEVER write full Python programs - you should only write individual statements that invoke a single function from the provided library.
54
54
  * **CRITICAL** NEVER output text before a function call. Always do a function call first.
55
55
  * **CRITICAL** You are a debugging assistant in DevTools. NEVER provide answers to questions of unrelated topics such as legal advice, financial advice, personal opinions, medical advice, religion, race, politics, sexuality, gender, or any other non web-development topics. Answer "Sorry, I can't answer that. I'm best at questions about debugging web pages." to such questions.
56
+ * **CRITICAL** When referring to DevTools resource output a markdown link to the object using the format \`[<text>](#<type>-<ID>)\`.
57
+ * The only available types are \`#req\` for network request and \`#file\` for source files. Only use ID inside the link, never ask about user selecting by ID.
56
58
  `;
57
59
 
58
60
  /**
@@ -117,9 +119,11 @@ export class ContextSelectionAgent extends AiAgent<never> {
117
119
  }
118
120
 
119
121
  requests.push({
122
+ id: request.requestId(),
120
123
  url: request.url(),
121
124
  statusCode: request.statusCode,
122
125
  duration: i18n.TimeUtilities.secondsToString(request.duration),
126
+ transferSize: i18n.ByteUtilities.formatBytesToKb(request.transferSize),
123
127
  });
124
128
  }
125
129
 
@@ -135,18 +139,18 @@ export class ContextSelectionAgent extends AiAgent<never> {
135
139
  },
136
140
  });
137
141
 
138
- this.declareFunction<{url: string}>('selectNetworkRequest', {
142
+ this.declareFunction<{id: string}>('selectNetworkRequest', {
139
143
  description:
140
144
  `Selects a specific network request to further provide information about. Use this when asked about network requests issues.`,
141
145
  parameters: {
142
146
  type: Host.AidaClient.ParametersTypes.OBJECT,
143
147
  description: '',
144
148
  nullable: true,
145
- required: ['url'],
149
+ required: ['id'],
146
150
  properties: {
147
- url: {
151
+ id: {
148
152
  type: Host.AidaClient.ParametersTypes.STRING,
149
- description: 'The url of the requests',
153
+ description: 'The id of the network request',
150
154
  nullable: false,
151
155
  },
152
156
  },
@@ -154,15 +158,12 @@ export class ContextSelectionAgent extends AiAgent<never> {
154
158
  displayInfoFromArgs: args => {
155
159
  return {
156
160
  title: lockedString('Getting network request…'),
157
- action: `selectNetworkRequest(${args.url})`,
161
+ action: `selectNetworkRequest(${args.id})`,
158
162
  };
159
163
  },
160
- handler: async ({url}) => {
161
- // TODO: Switch to using IDs to make is easier to link to as well.
164
+ handler: async ({id}) => {
162
165
  const request = Logs.NetworkLog.NetworkLog.instance().requests().find(req => {
163
- return req.url() === Platform.DevToolsPath.urlString`${url}` ||
164
- req.url() === Platform.DevToolsPath.urlString`${url.slice(0, -1)}` ||
165
- req.url() === Platform.DevToolsPath.urlString`${url}/`;
166
+ return req.requestId() === id;
166
167
  });
167
168
 
168
169
  if (request) {
@@ -195,32 +196,32 @@ export class ContextSelectionAgent extends AiAgent<never> {
195
196
  };
196
197
  },
197
198
  handler: async () => {
198
- // We get multiple of the same file name
199
- // so we create a set and the pick based
200
- // on heuristics in the select function
201
- const files = new Set();
202
- for (const file of this.#getUISourceCodes()) {
203
- files.add(file.fullDisplayName());
199
+ const files: Array<{file: string, id: number | undefined}> = [];
200
+ for (const file of ContextSelectionAgent.getUISourceCodes()) {
201
+ files.push({
202
+ file: file.fullDisplayName(),
203
+ id: ContextSelectionAgent.uiSourceCodeId.get(file),
204
+ });
204
205
  }
205
206
 
206
207
  return {
207
- result: [...files],
208
+ result: files,
208
209
  };
209
210
  },
210
211
  });
211
212
 
212
- this.declareFunction<{name: string}>('selectSourceFile', {
213
+ this.declareFunction<{id: number}>('selectSourceFile', {
213
214
  description:
214
- `Selects a source file. Use this when asked about files on the page. Use listSourceFiles if you don't know the full path name.`,
215
+ `Selects a source file. Use this when asked about files on the page. Use listSourceFiles to find the file ID.`,
215
216
  parameters: {
216
217
  type: Host.AidaClient.ParametersTypes.OBJECT,
217
218
  description: '',
218
219
  nullable: true,
219
- required: ['name'],
220
+ required: ['id'],
220
221
  properties: {
221
- name: {
222
- type: Host.AidaClient.ParametersTypes.STRING,
223
- description: 'The full path name of the file you want to select.',
222
+ id: {
223
+ type: Host.AidaClient.ParametersTypes.INTEGER,
224
+ description: 'The id (URL) of the file you want to select.',
224
225
  nullable: false,
225
226
  },
226
227
  },
@@ -228,22 +229,19 @@ export class ContextSelectionAgent extends AiAgent<never> {
228
229
  displayInfoFromArgs: args => {
229
230
  return {
230
231
  title: lockedString('Getting source file…'),
231
- action: `selectSourceFile(${args.name})`,
232
+ action: `selectSourceFile(${args.id})`,
232
233
  };
233
234
  },
234
235
  handler: async params => {
235
- // In some cases we get multiple files
236
- // use the heuristics bellow to pick the better one
237
- const files = this.#getUISourceCodes().filter(file => file.fullDisplayName() === params.name);
236
+ const file = ContextSelectionAgent.getUISourceCodes().find(
237
+ file => ContextSelectionAgent.uiSourceCodeId.get(file) === params.id);
238
238
 
239
- if (files.length === 0) {
239
+ if (!file) {
240
240
  return {
241
241
  error: 'Unable to find file.',
242
242
  };
243
243
  }
244
244
 
245
- // This help us pick the file that is resolved source map.
246
- const file = files.find(f => f.contentType().isFromSourceMap()) ?? files[0];
247
245
  return {
248
246
  context: new FileContext(file),
249
247
  description: 'User selected a source file',
@@ -293,14 +291,23 @@ export class ContextSelectionAgent extends AiAgent<never> {
293
291
  },
294
292
  displayInfoFromArgs: () => {
295
293
  return {
296
- title: lockedString('Please select an element on the page'),
297
- action: 'selectElement()',
294
+ title: lockedString('Select an element on the page or in the Elements panel'),
298
295
  };
299
296
  },
300
- handler: async () => {
297
+ handler: async (_params, options) => {
301
298
  if (!this.#onInspectElement) {
302
- return {error: 'The inspect element action is not available.'};
299
+ return {
300
+ error: 'The inspect element action is not available.',
301
+ };
303
302
  }
303
+
304
+ if (!options?.approved) {
305
+ return {
306
+ requiresApproval: true,
307
+ description: null,
308
+ };
309
+ }
310
+
304
311
  const node = await this.#onInspectElement();
305
312
  if (node) {
306
313
  return {
@@ -315,27 +322,51 @@ export class ContextSelectionAgent extends AiAgent<never> {
315
322
  });
316
323
  }
317
324
 
318
- #getUISourceCodes = (): Workspace.UISourceCode.UISourceCode[] => {
325
+ async * handleContextDetails(): AsyncGenerator<ContextResponse, void, void> {
326
+ }
327
+
328
+ override async enhanceQuery(query: string): Promise<string> {
329
+ return query;
330
+ }
331
+
332
+ static lastSourceId = 0;
333
+ static uiSourceCodeId = new WeakMap<Workspace.UISourceCode.UISourceCode, number>();
334
+ /**
335
+ * This is a heuristic algorithm that gets all the source files coming from the
336
+ * network and assigns unique ids to be linked from the LLM Markdown response.
337
+ * Steps we do:
338
+ * 1. Get all project that are coming from the Network. This scopes down
339
+ * sources exposed to the LLM
340
+ * 2. Remove all ignore listed source code. We further reduce thing that the
341
+ * user most likely does not have interest in, from global setting.
342
+ * 3.1. Source files don't have an uniqueId so we use the URL to differentiate
343
+ * them.
344
+ * 3.2. In cases where we encounter a duplicated URLs we prefer the latest one
345
+ * coming from SourceMaps (usually only one) as that has simple code and
346
+ * usually is what the user authored.
347
+ */
348
+ static getUISourceCodes(): Workspace.UISourceCode.UISourceCode[] {
319
349
  const workspace = Workspace.Workspace.WorkspaceImpl.instance();
320
350
  const projects =
321
351
  workspace.projects().filter(project => project.type() === Workspace.Workspace.projectTypes.Network);
322
- const uiSourceCodes = [];
352
+ const uiSourceCodes = new Map<string, Workspace.UISourceCode.UISourceCode>();
353
+
323
354
  for (const project of projects) {
324
355
  for (const uiSourceCode of project.uiSourceCodes()) {
325
356
  if (uiSourceCode.isIgnoreListed()) {
326
357
  continue;
327
358
  }
328
- uiSourceCodes.push(uiSourceCode);
359
+ const url = uiSourceCode.url();
360
+ // This helps us pick the file that is a resolved source map.
361
+ if (!uiSourceCodes.get(url) || uiSourceCode.contentType().isFromSourceMap()) {
362
+ uiSourceCodes.set(url, uiSourceCode);
363
+ if (!ContextSelectionAgent.uiSourceCodeId.has(uiSourceCode)) {
364
+ ContextSelectionAgent.uiSourceCodeId.set(uiSourceCode, ++ContextSelectionAgent.lastSourceId);
365
+ }
366
+ }
329
367
  }
330
368
  }
331
369
 
332
- return uiSourceCodes;
333
- };
334
-
335
- async * handleContextDetails(): AsyncGenerator<ContextResponse, void, void> {
336
- }
337
-
338
- override async enhanceQuery(query: string): Promise<string> {
339
- return query;
370
+ return [...uiSourceCodes.values()];
340
371
  }
341
372
  }
@@ -18,6 +18,7 @@ import {FREESTYLER_WORLD_NAME} from '../injected.js';
18
18
  import {
19
19
  type AgentOptions as BaseAgentOptions,
20
20
  AiAgent,
21
+ type ComputedStyleAiWidget,
21
22
  type ContextResponse,
22
23
  ConversationContext,
23
24
  type ConversationSuggestions,
@@ -325,7 +326,7 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
325
326
  };
326
327
  },
327
328
  handler: async params => {
328
- return await this.getStyles(params.elements, params.styleProperties);
329
+ return await this.#getStyles(params.elements, params.styleProperties);
329
330
  },
330
331
  });
331
332
 
@@ -606,7 +607,9 @@ const data = {
606
607
  return this.context?.getItem() ?? null;
607
608
  }
608
609
 
609
- async getStyles(elements: number[], properties: string[]): Promise<FunctionCallHandlerResult<unknown>> {
610
+ async #getStyles(elements: number[], properties: string[]): Promise<FunctionCallHandlerResult<unknown>> {
611
+ const widgets: ComputedStyleAiWidget[] = [];
612
+
610
613
  const result:
611
614
  Record<string, {computed: Record<string, string|undefined>, authored: Record<string, string|undefined>}> = {};
612
615
  for (const uid of elements) {
@@ -630,6 +633,15 @@ const data = {
630
633
  if (!matchedStyles) {
631
634
  return {error: 'Error: Could not get authored styles.'};
632
635
  }
636
+ widgets.push({
637
+ name: 'COMPUTED_STYLES',
638
+ data: {
639
+ computedStyles: styles,
640
+ backendNodeId: node.backendNodeId(),
641
+ matchedCascade: matchedStyles,
642
+ properties,
643
+ }
644
+ });
633
645
  for (const prop of properties) {
634
646
  result[uid].computed[prop] = styles.get(prop);
635
647
  }
@@ -647,6 +659,7 @@ const data = {
647
659
  }
648
660
  return {
649
661
  result: JSON.stringify(result, null, 2),
662
+ widgets,
650
663
  };
651
664
  }
652
665
 
@@ -701,6 +714,7 @@ const data = {
701
714
 
702
715
  return {
703
716
  requiresApproval: true,
717
+ description: lockedString('This code may modify page content. Continue?'),
704
718
  };
705
719
  }
706
720
  if (result.canceled) {
@@ -12,17 +12,18 @@ import * as SDK from '../../core/sdk/sdk.js';
12
12
  * Model trackComputedStyleUpdatesForNode method.
13
13
  */
14
14
  export class ComputedStyleModel extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
15
- #node: SDK.DOMModel.DOMNode|null;
16
- #cssModel: SDK.CSSModel.CSSModel|null;
17
- private eventListeners: Common.EventTarget.EventDescriptor[];
15
+ #node: SDK.DOMModel.DOMNode|null = null;
16
+ #cssModel: SDK.CSSModel.CSSModel|null = null;
17
+ private eventListeners: Common.EventTarget.EventDescriptor[] = [];
18
18
  private frameResizedTimer?: number;
19
19
  private computedStylePromise?: Promise<ComputedStyle|null>;
20
20
 
21
21
  constructor(node?: SDK.DOMModel.DOMNode|null) {
22
22
  super();
23
- this.#cssModel = null;
24
- this.eventListeners = [];
25
- this.#node = node ?? null;
23
+ if (node) {
24
+ // Call the explicit setter to trigger the setup and event binding.
25
+ this.node = node;
26
+ }
26
27
  }
27
28
 
28
29
  get node(): SDK.DOMModel.DOMNode|null {
@@ -39,6 +40,21 @@ export class ComputedStyleModel extends Common.ObjectWrapper.ObjectWrapper<Event
39
40
  return this.#cssModel?.isEnabled() ? this.#cssModel : null;
40
41
  }
41
42
 
43
+ /**
44
+ * Clears all event listeners to ensure the instance can be GC'd without leaking memory.
45
+ */
46
+ dispose(): void {
47
+ Common.EventTarget.removeEventListeners(this.eventListeners);
48
+ this.eventListeners = [];
49
+ this.node = null;
50
+ this.#cssModel = null;
51
+ this.computedStylePromise = undefined;
52
+ if (this.frameResizedTimer) {
53
+ clearTimeout(this.frameResizedTimer);
54
+ this.frameResizedTimer = undefined;
55
+ }
56
+ }
57
+
42
58
  private updateModel(cssModel: SDK.CSSModel.CSSModel|null): void {
43
59
  if (this.#cssModel === cssModel) {
44
60
  return;
@@ -168,6 +184,24 @@ export class ComputedStyleModel extends Common.ObjectWrapper.ObjectWrapper<Event
168
184
  }
169
185
  return matchedStyles.node() === this.node ? matchedStyles : null;
170
186
  }
187
+
188
+ computePropertyTraces(matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles):
189
+ Map<string, SDK.CSSProperty.CSSProperty[]> {
190
+ const result = new Map<string, SDK.CSSProperty.CSSProperty[]>();
191
+ for (const style of matchedStyles.nodeStyles()) {
192
+ const allProperties = style.allProperties();
193
+ for (const property of allProperties) {
194
+ if (!property.activeInStyle() || !matchedStyles.propertyState(property)) {
195
+ continue;
196
+ }
197
+
198
+ const matches = result.get(property.name) ?? [];
199
+ matches.push(property);
200
+ result.set(property.name, matches);
201
+ }
202
+ }
203
+ return result;
204
+ }
171
205
  }
172
206
 
173
207
  export const enum Events {
@@ -7322,16 +7322,16 @@ export const NativeFunctions = [
7322
7322
  signatures: [["type","eventInitDict"]]
7323
7323
  },
7324
7324
  {
7325
- name: "registerTool",
7326
- signatures: [["params"]]
7325
+ name: "provideContext",
7326
+ signatures: [["?options"]]
7327
7327
  },
7328
7328
  {
7329
- name: "unregisterTool",
7330
- signatures: [["tool_name"]]
7329
+ name: "registerTool",
7330
+ signatures: [["tool"]]
7331
7331
  },
7332
7332
  {
7333
- name: "provideContext",
7334
- signatures: [["params"]]
7333
+ name: "unregisterTool",
7334
+ signatures: [["name"]]
7335
7335
  },
7336
7336
  {
7337
7337
  name: "SnapEvent",
@@ -452,9 +452,11 @@ export class AXBreadcrumb {
452
452
 
453
453
  this.#element = document.createElement('div');
454
454
  this.#element.classList.add('ax-breadcrumb');
455
- this.#element.setAttribute(
456
- 'jslog',
457
- `${VisualLogging.treeItem().track({click: true, keydown: 'ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Enter'})}`);
455
+ this.#element.setAttribute('jslog', `${VisualLogging.treeItem().track({
456
+ click: true,
457
+ resize: true,
458
+ keydown: 'ArrowUp|ArrowDown|ArrowLeft|ArrowRight|Enter'
459
+ })}`);
458
460
  elementsToAXBreadcrumb.set(this.#element, this);
459
461
 
460
462
  this.#nodeElement = document.createElement('div');
@@ -13,6 +13,7 @@ import * as SDK from '../../core/sdk/sdk.js';
13
13
  import * as AiAssistanceModel from '../../models/ai_assistance/ai_assistance.js';
14
14
  import * as Annotations from '../../models/annotations/annotations.js';
15
15
  import * as Badges from '../../models/badges/badges.js';
16
+ import * as Greendev from '../../models/greendev/greendev.js';
16
17
  import * as TextUtils from '../../models/text_utils/text_utils.js';
17
18
  import type * as Trace from '../../models/trace/trace.js';
18
19
  import * as Workspace from '../../models/workspace/workspace.js';
@@ -459,9 +460,23 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
459
460
  }
460
461
  }
461
462
 
462
- const shouldShowWalkthrough = input.state === ViewState.CHAT_VIEW && input.walkthrough.isExpanded;
463
+ if (Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled ||
464
+ Greendev.Prototypes.instance().isEnabled('breakpointDebuggerAgent')) {
465
+
466
+ const shouldShowWalkthrough = input.state === ViewState.CHAT_VIEW && input.walkthrough.isExpanded;
467
+ /**
468
+ * We want to mark the walkthrough as loading only if it's showing the last
469
+ * message. Otherwise, a previous walkthrough will show as loading if we
470
+ * rely only on the isLoading flag.
471
+ */
472
+ let walkthroughIsForLastMessage = false;
473
+ if(input.state === ViewState.CHAT_VIEW) {
474
+ const lastMessage = input.props.messages.at(-1);
475
+ if(lastMessage && input.props.walkthrough.activeMessage === lastMessage) {
476
+ walkthroughIsForLastMessage = true;
477
+ }
478
+ }
463
479
 
464
- if (Root.Runtime.hostConfig.devToolsAiAssistanceV2?.enabled) {
465
480
  Lit.render(html`
466
481
  ${toolbarView(input)}
467
482
  <div class="ai-assistance-view-container">
@@ -479,7 +494,7 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
479
494
  ${shouldShowWalkthrough ? html`
480
495
  <devtools-widget .widgetConfig=${UI.Widget.widgetConfig(WalkthroughView, {
481
496
  message: input.props.walkthrough.activeMessage,
482
- isLoading: input.props.isLoading,
497
+ isLoading: input.props.isLoading && walkthroughIsForLastMessage,
483
498
  markdownRenderer: input.props.markdownRenderer,
484
499
  onToggle: input.props.walkthrough.onToggle,
485
500
  })}></devtools-widget>` : Lit.nothing}
@@ -915,7 +930,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
915
930
  this.requestUpdate();
916
931
  }
917
932
 
918
- async handleBreakpointConversation(uiLocation: Workspace.UISourceCode.UILocation): Promise<void> {
933
+ async handleBreakpointConversation(uiLocation: Workspace.UISourceCode.UILocation, errorMsg?: string): Promise<void> {
919
934
  const context = new AiAssistanceModel.BreakpointDebuggerAgent.BreakpointContext(uiLocation);
920
935
  this.#selectedBreakpoint = context;
921
936
  const conversation = new AiAssistanceModel.AiConversation.AiConversation(
@@ -934,6 +949,9 @@ export class AiAssistancePanel extends UI.Panel.Panel {
934
949
  this.#conversation?.setContext(context);
935
950
  this.requestUpdate();
936
951
  await UI.ViewManager.ViewManager.instance().showView(AiAssistancePanel.panelName);
952
+ const prompt = errorMsg ? `debug the error "${errorMsg}" using breakpoint debugging agent` :
953
+ 'debug the error using breakpoint debugging agent';
954
+ await this.#startConversation(prompt);
937
955
  }
938
956
 
939
957
  override wasShown(): void {
@@ -1434,11 +1452,19 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1434
1452
  this.#updateConversationState();
1435
1453
  }
1436
1454
 
1455
+ #clearWalkthrough(): void {
1456
+ this.#walkthrough.isExpanded = false;
1457
+ this.#walkthrough.activeMessage = null;
1458
+ }
1459
+
1437
1460
  #onDeleteClicked(): void {
1438
1461
  if (!this.#conversation) {
1439
1462
  return;
1440
1463
  }
1441
1464
 
1465
+ // Ensure we clear the walkthrough so it doesn't hold onto a chat that is about to be deleted.
1466
+ this.#clearWalkthrough();
1467
+
1442
1468
  void AiAssistanceModel.AiHistoryStorage.AiHistoryStorage.instance().deleteHistoryEntry(this.#conversation.id);
1443
1469
  this.#updateConversationState();
1444
1470
  UI.ARIAUtils.LiveAnnouncer.alert(i18nString(UIStrings.chatDeleted));
@@ -1645,7 +1671,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1645
1671
  let announcedAnswerLoading = false;
1646
1672
  let announcedAnswerReady = false;
1647
1673
  for await (const data of items) {
1648
- step.sideEffect = undefined;
1674
+ step.requestApproval = undefined;
1649
1675
  switch (data.type) {
1650
1676
  case AiAssistanceModel.AiAgent.ResponseType.USER_QUERY: {
1651
1677
  this.#messages.push({
@@ -1658,6 +1684,10 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1658
1684
  parts: [],
1659
1685
  };
1660
1686
  this.#messages.push(systemMessage);
1687
+ if (Greendev.Prototypes.instance().isEnabled('breakpointDebuggerAgent') &&
1688
+ this.#conversation?.type === AiAssistanceModel.AiHistoryStorage.ConversationType.BREAKPOINT) {
1689
+ this.#openWalkthrough(systemMessage);
1690
+ }
1661
1691
  break;
1662
1692
  }
1663
1693
  case AiAssistanceModel.AiAgent.ResponseType.QUERYING: {
@@ -1702,10 +1732,11 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1702
1732
  case AiAssistanceModel.AiAgent.ResponseType.SIDE_EFFECT: {
1703
1733
  step.isLoading = false;
1704
1734
  step.code ??= data.code;
1705
- step.sideEffect = {
1735
+ step.requestApproval = {
1736
+ description: data.description,
1706
1737
  onAnswer: (result: boolean) => {
1707
1738
  data.confirm(result);
1708
- step.sideEffect = undefined;
1739
+ step.requestApproval = undefined;
1709
1740
  this.requestUpdate();
1710
1741
  },
1711
1742
  };
@@ -1717,6 +1748,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1717
1748
  step.code ??= data.code;
1718
1749
  step.output ??= data.output;
1719
1750
  step.canceled = data.canceled;
1751
+ step.widgets ??= data.widgets;
1720
1752
  commitStep();
1721
1753
  break;
1722
1754
  }
@@ -83,3 +83,64 @@ The spinner displayed next to a step in the UI is intentionally tied to the **co
83
83
  * **Providing Continuous Feedback**: Using the conversation's `isLoading` ensures a persistent visual indicator that the AI is actively working on the query, even if individual steps are quickly parsed. This prevents the UI from appearing unresponsive.
84
84
  * **Avoiding a "Stuck" Feeling**: If the spinner were tied to `step.isLoading`, it would flicker on and off rapidly, potentially making the user feel that the AI has stopped processing or is stuck.
85
85
  * **Clear Progress Visualization**: The spinner is dynamically moved to the *last* active step in the list as long as the overall conversation is loading. Once a step is completed, it receives a checkmark, and the spinner moves to the next active step.
86
+
87
+ ## AI-Defined UI Widgets
88
+
89
+ To provide a richer user experience, AI functions can now return data that represents UI widgets. These widgets are then rendered directly within the AI assistance panel, allowing for more interactive and contextual responses.
90
+
91
+ ### Data Structure for Widgets
92
+
93
+ This is achieved through a new type of `ModelMessagePart`: the `WidgetPart`.
94
+
95
+ - **`WidgetPart`**: Represents a UI widget to be rendered. It contains a `widget` object with the following properties:
96
+ - `name`: A string identifier for the widget to be rendered (e.g., `'freestyler'`). This name is used by the frontend to select the correct widget component.
97
+ - `data`: An object containing the data required by the widget. The structure of this data is specific to each widget.
98
+
99
+ A `ModelChatMessage` can contain one or more `WidgetPart`s, usually as part of the final answer.
100
+
101
+ **Example `ModelChatMessage` with a `WidgetPart`:**
102
+
103
+ ```json
104
+ {
105
+ "entity": "MODEL",
106
+ "parts": [
107
+ {
108
+ "type": "answer",
109
+ "text": "Here is a widget to help you with CSS."
110
+ },
111
+ {
112
+ "type": "widget",
113
+ "widget": {
114
+ "name": "freestyler",
115
+ "data": {
116
+ "css": "color: red;"
117
+ }
118
+ }
119
+ }
120
+ ]
121
+ }
122
+ ```
123
+
124
+ ### From AI Function to UI
125
+
126
+ The process of rendering an AI-defined UI widget begins within the AI function handler itself. AI functions, typically defined in `front_end/models/ai_assistance/agents/AiAgent.js`, can return an `AiWidget` type as part of their response.
127
+
128
+ 1. **AI Function Output**: An AI function constructs an `AiWidget` object, specifying its `name` (e.g., `'freestyler'` or `'COMPUTED_STYLES'`) and a `data` payload that contains all the necessary information for the widget to render.
129
+
130
+ 2. **`ModelChatMessage` Integration**: This `AiWidget` is then encapsulated within a `WidgetPart`, which is added to the `parts` array of a `ModelChatMessage`. This `ModelChatMessage` is what the `ChatMessage` UI component receives.
131
+
132
+ 3. **`ChatMessage` Processing**: In `front_end/panels/ai_assistance/components/ChatMessage.ts`, the `ChatMessage` component is responsible for iterating through the `parts` of a `ModelChatMessage`. When it encounters a `WidgetPart`, it delegates the rendering to the `renderStepWidgets` function.
133
+
134
+ 4. **`renderStepWidgets` Mapping**: The `renderStepWidgets` function acts as a registry and renderer for different widget types. It reads the `widget.name` from the `WidgetPart` and, based on this name, calls a specific `make...Widget` function (e.g., `makeComputedStyleWidget` for `'COMPUTED_STYLES'` widgets).
135
+
136
+ 5. **Widget Instantiation and Rendering**: Each `make...Widget` function is responsible for taking the `widget.data` and converting it into a `UI.Widget.widgetConfig` object. This configuration is then used with the `<devtools-widget>` Lit component, which dynamically instantiates the corresponding `UI.Widget` subclass and renders it into the DOM.
137
+
138
+ This modular approach ensures that new UI widgets can be introduced and managed by AI functions without requiring significant changes to the core messaging or rendering infrastructure.
139
+
140
+ The `ChatMessage` component is responsible for handling `WidgetPart`s. When it encounters a `WidgetPart`, it will:
141
+
142
+ 1. Look up the widget name (`widget.name`) in a registry of available widget components.
143
+ 2. Instantiate the corresponding widget component.
144
+ 3. Pass the `widget.data` to the component as properties.
145
+
146
+ This allows for a flexible system where new widgets can be added to the frontend and then invoked by the AI without requiring changes to the core message handling logic.