chrome-devtools-frontend 1.0.1598808 → 1.0.1601661

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 (161) hide show
  1. package/.agents/README.md +13 -0
  2. package/{agents/prompts/creating-a-model.md → .agents/skills/creating-a-model/SKILL.md} +7 -1
  3. package/{agents/prompts/devtools-imports.md → .agents/skills/devtools-imports/SKILL.md} +10 -5
  4. package/{agents/prompts/merging-devtools-module.md → .agents/skills/merging-devtools-module/SKILL.md} +5 -0
  5. package/{agents/prompts/ui-widgets.md → .agents/skills/ui-widgets/SKILL.md} +5 -0
  6. package/{agents/prompts/verification.md → .agents/skills/verification/SKILL.md} +5 -0
  7. package/front_end/core/common/Settings.ts +24 -1
  8. package/front_end/core/dom_extension/DOMExtension.ts +1 -0
  9. package/front_end/core/host/AidaClient.ts +5 -2
  10. package/front_end/core/host/AidaGcaTranslation.ts +377 -0
  11. package/front_end/core/host/GcaTypes.ts +520 -0
  12. package/front_end/core/host/UserMetrics.ts +0 -3
  13. package/front_end/core/host/host.ts +4 -0
  14. package/front_end/core/root/ExperimentNames.ts +0 -3
  15. package/front_end/core/sdk/CPUThrottlingManager.ts +12 -9
  16. package/front_end/core/sdk/ResourceTreeModel.ts +1 -1
  17. package/front_end/core/sdk/SourceMap.ts +4 -2
  18. package/front_end/entrypoints/main/MainImpl.ts +1 -12
  19. package/front_end/generated/Deprecation.ts +16 -0
  20. package/front_end/generated/InspectorBackendCommands.ts +4 -6
  21. package/front_end/generated/protocol.ts +46 -77
  22. package/front_end/models/ai_assistance/AiConversation.ts +49 -22
  23. package/front_end/models/ai_assistance/AiHistoryStorage.snapshot.txt +1 -1
  24. package/front_end/models/ai_assistance/AiHistoryStorage.ts +1 -0
  25. package/front_end/models/ai_assistance/ConversationHandler.ts +16 -11
  26. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +76 -36
  27. package/front_end/models/ai_assistance/agents/AiAgent.ts +26 -2
  28. package/front_end/models/ai_assistance/agents/BreakpointDebuggerAgent.ts +0 -1
  29. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +11 -0
  30. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +39 -0
  31. package/front_end/models/ai_assistance/agents/ConversationSummaryAgent.ts +53 -12
  32. package/front_end/models/ai_assistance/agents/FileAgent.ts +0 -15
  33. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +0 -5
  34. package/front_end/models/ai_assistance/agents/PerformanceAgent.snapshot.txt +57 -0
  35. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +87 -15
  36. package/front_end/models/ai_assistance/agents/PerformanceAnnotationsAgent.ts +0 -10
  37. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +0 -6
  38. package/front_end/models/ai_assistance/agents/StylingAgent.ts +1 -5
  39. package/front_end/models/ai_assistance/ai_assistance.ts +2 -0
  40. package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.snapshot.txt +84 -0
  41. package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.ts +172 -0
  42. package/front_end/models/breakpoints/BreakpointManager.ts +20 -12
  43. package/front_end/models/emulation/DeviceModeModel.ts +5 -1
  44. package/front_end/models/emulation/EmulatedDevices.ts +3 -2
  45. package/front_end/models/javascript_metadata/NativeFunctions.js +60 -0
  46. package/front_end/models/lighthouse/LighthouseReporterTypes.ts +104 -10
  47. package/front_end/models/lighthouse/RunTypes.ts +42 -0
  48. package/front_end/models/lighthouse/lighthouse.ts +2 -0
  49. package/front_end/models/live-metrics/web-vitals-injected/web-vitals-injected.ts +59 -35
  50. package/front_end/models/stack_trace/StackTrace.ts +5 -0
  51. package/front_end/models/stack_trace/StackTraceImpl.ts +7 -1
  52. package/front_end/models/stack_trace/StackTraceModel.ts +2 -1
  53. package/front_end/models/stack_trace/stack_trace.ts +4 -0
  54. package/front_end/models/trace/ModelImpl.ts +4 -0
  55. package/front_end/models/trace/helpers/SamplesIntegrator.ts +1 -8
  56. package/front_end/models/trace/types/Configuration.ts +0 -5
  57. package/front_end/panels/accessibility/AccessibilityNodeView.ts +7 -3
  58. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +163 -74
  59. package/front_end/panels/ai_assistance/ExportConversation.ts +25 -0
  60. package/front_end/panels/ai_assistance/ai_assistance.ts +2 -0
  61. package/front_end/panels/ai_assistance/components/ChatInput.ts +2 -0
  62. package/front_end/panels/ai_assistance/components/ChatMessage.ts +120 -32
  63. package/front_end/panels/ai_assistance/components/ChatView.ts +44 -0
  64. package/front_end/panels/ai_assistance/components/ExportForAgentsDialog.ts +240 -0
  65. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +4 -12
  66. package/front_end/panels/ai_assistance/components/StylingAgentMarkdownRenderer.ts +5 -27
  67. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +115 -17
  68. package/front_end/panels/ai_assistance/components/chatMessage.css +65 -1
  69. package/front_end/panels/ai_assistance/components/chatView.css +8 -0
  70. package/front_end/panels/ai_assistance/components/exportForAgentsDialog.css +82 -0
  71. package/front_end/panels/ai_assistance/components/walkthroughView.css +9 -1
  72. package/front_end/panels/animation/AnimationTimeline.ts +5 -5
  73. package/front_end/panels/application/AppManifestView.ts +13 -7
  74. package/front_end/panels/application/FrameDetailsView.ts +4 -4
  75. package/front_end/panels/application/preloading/PreloadingView.ts +2 -4
  76. package/front_end/panels/application/preloading/components/PreloadingDetailsReportView.ts +6 -3
  77. package/front_end/panels/application/preloading/components/PreloadingString.ts +15 -2
  78. package/front_end/panels/application/preloading/helper/PreloadingForward.ts +31 -2
  79. package/front_end/panels/browser_debugger/DOMBreakpointsSidebarPane.ts +3 -2
  80. package/front_end/panels/common/DOMLinkifier.ts +10 -8
  81. package/front_end/panels/console/ConsoleView.ts +24 -2
  82. package/front_end/panels/console/ConsoleViewMessage.ts +22 -23
  83. package/front_end/panels/console/console.ts +0 -2
  84. package/front_end/panels/css_overview/CSSOverviewCompletedView.ts +5 -5
  85. package/front_end/panels/elements/ElementsTreeElement.ts +2 -3
  86. package/front_end/panels/elements/PropertiesWidget.ts +5 -3
  87. package/front_end/panels/elements/StandaloneStylesContainer.ts +21 -9
  88. package/front_end/panels/elements/StylePropertiesSection.ts +98 -50
  89. package/front_end/panels/elements/StylePropertyTreeElement.ts +38 -4
  90. package/front_end/panels/elements/StylesAiCodeCompletionProvider.ts +43 -35
  91. package/front_end/panels/elements/StylesSidebarPane.ts +154 -24
  92. package/front_end/panels/elements/components/ComputedStyleTrace.ts +3 -2
  93. package/front_end/panels/event_listeners/EventListenersView.ts +16 -4
  94. package/front_end/panels/explain/components/ConsoleInsight.ts +6 -9
  95. package/front_end/panels/explain/explain-meta.ts +5 -0
  96. package/front_end/panels/issues/AffectedResourcesView.ts +5 -9
  97. package/front_end/panels/issues/AffectedSelectivePermissionsInterventionView.ts +1 -1
  98. package/front_end/panels/lighthouse/LighthouseController.ts +154 -164
  99. package/front_end/panels/lighthouse/LighthousePanel.ts +5 -9
  100. package/front_end/panels/lighthouse/LighthouseProtocolService.ts +1 -0
  101. package/front_end/panels/lighthouse/LighthouseReportRenderer.ts +9 -6
  102. package/front_end/panels/lighthouse/LighthouseStartView.ts +7 -6
  103. package/front_end/panels/lighthouse/LighthouseStatusView.ts +32 -8
  104. package/front_end/panels/linear_memory_inspector/components/LinearMemoryInspector.ts +7 -3
  105. package/front_end/panels/network/NetworkDataGridNode.ts +34 -0
  106. package/front_end/panels/network/NetworkLogViewColumns.ts +10 -0
  107. package/front_end/panels/network/RequestPayloadView.ts +4 -1
  108. package/front_end/panels/network/RequestTimingView.ts +8 -2
  109. package/front_end/panels/performance_monitor/performanceMonitor.css +6 -4
  110. package/front_end/panels/recorder/RecorderController.ts +52 -14
  111. package/front_end/panels/recorder/components/RecordingView.ts +2 -1
  112. package/front_end/panels/recorder/models/RecordingStorage.ts +15 -20
  113. package/front_end/panels/sensors/SensorsView.ts +333 -157
  114. package/front_end/panels/settings/emulation/DevicesSettingsTab.ts +18 -4
  115. package/front_end/panels/sources/DebuggerPausedMessage.ts +6 -6
  116. package/front_end/panels/sources/FilteredUISourceCodeListProvider.ts +2 -3
  117. package/front_end/panels/sources/NavigatorView.ts +11 -7
  118. package/front_end/panels/sources/ScopeChainSidebarPane.ts +1 -1
  119. package/front_end/panels/sources/SourcesPanel.ts +12 -34
  120. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +5 -1
  121. package/front_end/panels/sources/sources-meta.ts +6 -0
  122. package/front_end/panels/timeline/StatusDialog.ts +159 -83
  123. package/front_end/panels/timeline/ThirdPartyTreeView.ts +6 -2
  124. package/front_end/panels/timeline/TimelineController.ts +0 -4
  125. package/front_end/panels/timeline/TimelineDetailsView.ts +35 -3
  126. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +6 -1
  127. package/front_end/panels/timeline/TimelineFlameChartNetworkDataProvider.ts +9 -4
  128. package/front_end/panels/timeline/TimelinePanel.ts +36 -11
  129. package/front_end/panels/timeline/TimelineTreeView.ts +14 -3
  130. package/front_end/panels/timeline/TimelineUIUtils.ts +5 -57
  131. package/front_end/panels/timeline/components/InteractionBreakdown.ts +2 -1
  132. package/front_end/panels/timeline/components/LiveMetricsView.ts +2 -2
  133. package/front_end/panels/timeline/components/NetworkRequestTooltip.ts +3 -7
  134. package/front_end/panels/timeline/components/Sidebar.ts +11 -19
  135. package/front_end/panels/timeline/components/SidebarInsightsTab.ts +2 -1
  136. package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +60 -27
  137. package/front_end/panels/timeline/components/TimelineRangeSummaryView.ts +66 -0
  138. package/front_end/panels/timeline/components/TimelineSummary.ts +7 -19
  139. package/front_end/panels/timeline/components/components.ts +2 -0
  140. package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +55 -28
  141. package/front_end/panels/timeline/components/insights/NodeLink.ts +3 -3
  142. package/front_end/panels/timeline/components/insights/baseInsightComponent.css +7 -0
  143. package/front_end/panels/timeline/components/insights/insights.ts +0 -2
  144. package/front_end/panels/timeline/components/timelineRangeSummaryView.css +20 -0
  145. package/front_end/panels/timeline/overlays/OverlaysImpl.ts +7 -7
  146. package/front_end/panels/timeline/timeline-meta.ts +11 -0
  147. package/front_end/panels/timeline/timelineDetailsView.css +0 -9
  148. package/front_end/third_party/chromium/README.chromium +1 -1
  149. package/front_end/third_party/web-vitals/package/dist/modules/attribution/onINP.js +11 -2
  150. package/front_end/third_party/web-vitals/package/src/attribution/onINP.ts +11 -2
  151. package/front_end/third_party/web-vitals/patches/0001-Add-onEachInteraction-to-onINP-options.patch +65 -4
  152. package/front_end/ui/kit/link/link.css +2 -2
  153. package/front_end/ui/legacy/Widget.ts +5 -3
  154. package/front_end/ui/legacy/components/object_ui/CustomPreviewComponent.ts +7 -2
  155. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +57 -35
  156. package/front_end/ui/legacy/components/source_frame/PreviewFactory.ts +1 -0
  157. package/front_end/ui/visual_logging/KnownContextValues.ts +6 -1
  158. package/package.json +1 -1
  159. package/agents/prompts/README.md +0 -18
  160. package/front_end/panels/timeline/components/insights/InsightRenderer.ts +0 -93
  161. /package/front_end/{panels/console → models/stack_trace}/ErrorStackParser.ts +0 -0
@@ -11,7 +11,9 @@ import * as SDK from '../../../core/sdk/sdk.js';
11
11
  import type * as Protocol from '../../../generated/protocol.js';
12
12
  import * as Tracing from '../../../services/tracing/tracing.js';
13
13
  import * as Annotations from '../../annotations/annotations.js';
14
+ import * as Logs from '../../logs/logs.js';
14
15
  import * as SourceMapScopes from '../../source_map_scopes/source_map_scopes.js';
16
+ import * as TextUtils from '../../text_utils/text_utils.js';
15
17
  import * as Trace from '../../trace/trace.js';
16
18
  import {
17
19
  PerformanceInsightFormatter,
@@ -35,10 +37,6 @@ import {
35
37
  } from './AiAgent.js';
36
38
 
37
39
  const UIStringsNotTranslated = {
38
- /**
39
- *@description Shown when the agent is investigating a trace
40
- */
41
- analyzingTrace: 'Analyzing trace',
42
40
  /**
43
41
  * @description Shown when the agent is investigating network activity
44
42
  */
@@ -318,7 +316,6 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
318
316
  #formatter: PerformanceTraceFormatter|null = null;
319
317
  #lastEventForEnhancedQuery: Trace.Types.Events.Event|undefined;
320
318
  #lastInsightForEnhancedQuery: Trace.Insights.Types.InsightModel|undefined;
321
- #hasShownAnalyzeTraceContext = false;
322
319
 
323
320
  /**
324
321
  * Cache of all function calls made by the agent. This allows us to include (as a
@@ -351,6 +348,32 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
351
348
  };
352
349
  #traceFacts: Host.AidaClient.RequestFact[] = [];
353
350
 
351
+ /**
352
+ * These facts do not contain page data, they are static instructions to the
353
+ * LLM, so we don't need to add them to the disclosure.
354
+ */
355
+ #factsToNeverDisclose = new Set<Host.AidaClient.RequestFact>([
356
+ this.#callFrameDataDescriptionFact,
357
+ this.#networkDataDescriptionFact,
358
+ this.#freshTraceExtraPreambleFact,
359
+ this.#notExternalExtraPreambleFact,
360
+ ]);
361
+
362
+ /**
363
+ * When we enhance the query with additional information, we need to know it
364
+ * so we can show it in the disclosure UI. This is cleared and then populated
365
+ * on each prompt.
366
+ */
367
+ #additionalSelectionsForQuery: string[] = [];
368
+
369
+ /**
370
+ * The CWV widget is shown when we analyze the trace summary, but we don't
371
+ * want to show it on every single "Analyzing data..." pill, as we show one
372
+ * after every prompt. So we make sure for a given Insight Set (which is based on navigation)
373
+ * we only show it once.
374
+ */
375
+ #hasShownWidgetForInsightSet = new WeakSet<Trace.Insights.Types.InsightSet>();
376
+
354
377
  get preamble(): string {
355
378
  return buildPreamble();
356
379
  }
@@ -379,13 +402,19 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
379
402
  return;
380
403
  }
381
404
 
382
- if (this.#hasShownAnalyzeTraceContext) {
383
- return;
405
+ const contextDisclosure: string[] = [];
406
+
407
+ for (const fact of this.currentFacts()) {
408
+ if (this.#factsToNeverDisclose.has(fact)) {
409
+ continue;
410
+ }
411
+ contextDisclosure.push(fact.text);
384
412
  }
413
+ contextDisclosure.push(...this.#additionalSelectionsForQuery);
385
414
 
386
415
  const widgets: AiWidget[] = [];
387
416
  const primaryInsightSet = context.getItem().primaryInsightSet;
388
- if (primaryInsightSet) {
417
+ if (primaryInsightSet && !this.#hasShownWidgetForInsightSet.has(primaryInsightSet)) {
389
418
  widgets.push({
390
419
  name: 'CORE_VITALS',
391
420
  data: {
@@ -393,21 +422,19 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
393
422
  insightSetKey: primaryInsightSet.id,
394
423
  },
395
424
  });
425
+ this.#hasShownWidgetForInsightSet.add(primaryInsightSet);
396
426
  }
397
427
 
398
428
  yield {
399
429
  type: ResponseType.CONTEXT,
400
- title: lockedString(UIStringsNotTranslated.analyzingTrace),
401
430
  details: [
402
431
  {
403
- title: 'Trace',
404
- text: this.#formatter?.formatTraceSummary() ?? '',
432
+ title: 'Trace details',
433
+ text: contextDisclosure.join('\n'),
405
434
  },
406
435
  ],
407
436
  widgets,
408
437
  };
409
-
410
- this.#hasShownAnalyzeTraceContext = true;
411
438
  }
412
439
 
413
440
  #callTreeContextSet = new WeakSet();
@@ -536,6 +563,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
536
563
  }
537
564
  }
538
565
 
566
+ this.#additionalSelectionsForQuery = selected;
539
567
  if (!selected.length) {
540
568
  return query;
541
569
  }
@@ -752,7 +780,8 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
752
780
  const details = new PerformanceInsightFormatter(focus, insight).formatInsight();
753
781
 
754
782
  const widgets: AiWidget[] = [];
755
- if (params.insightName === 'LCPDiscovery' || params.insightName === 'LCPBreakdown') {
783
+ if (Trace.Insights.Models.LCPDiscovery.isLCPDiscoveryInsight(insight) ||
784
+ Trace.Insights.Models.LCPBreakdown.isLCPBreakdownInsight(insight)) {
756
785
  const lcpMetric = Trace.Insights.Common.getLCP(insightSet);
757
786
  const lcpEvent = lcpMetric?.event;
758
787
  if (lcpEvent && Trace.Types.Events.isAnyLargestContentfulPaintCandidate(lcpEvent)) {
@@ -767,15 +796,38 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
767
796
  const node = nodeMap?.get(nodeId);
768
797
  if (node) {
769
798
  const snapshot = await node.takeSnapshot();
799
+ let networkRequest;
800
+ const lcpSyntheticRequest = insight.lcpRequest;
801
+ if (lcpSyntheticRequest) {
802
+ networkRequest = {
803
+ url: lcpSyntheticRequest.args.data.url,
804
+ size: lcpSyntheticRequest.args.data.decodedBodyLength ??
805
+ lcpSyntheticRequest.args.data.encodedDataLength ?? 0,
806
+ resourceType: lcpSyntheticRequest.args.data.resourceType,
807
+ mimeType: lcpSyntheticRequest.args.data.mimeType ?? '',
808
+ imageUrl: await this.#getNetworkRequestImageData(lcpSyntheticRequest),
809
+ };
810
+ }
770
811
  widgets.push({
771
812
  name: 'DOM_TREE',
772
- data: {root: snapshot},
813
+ data: {
814
+ root: snapshot,
815
+ networkRequest,
816
+ },
773
817
  });
774
818
  processedNodeIds.add(nodeId);
775
819
  }
776
820
  }
777
821
  }
778
822
  }
823
+ if (params.insightName === 'LCPBreakdown') {
824
+ widgets.push({
825
+ name: 'LCP_BREAKDOWN',
826
+ data: {
827
+ lcpData: insight as Trace.Insights.Models.LCPBreakdown.LCPBreakdownInsightModel,
828
+ },
829
+ });
830
+ }
779
831
  }
780
832
 
781
833
  const key = `getInsightDetails('${params.insightSetId}', '${params.insightName}')`;
@@ -1258,4 +1310,24 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
1258
1310
  Annotations.AnnotationRepository.instance().addNetworkRequestAnnotation(annotationMessage, requestId);
1259
1311
  return {result: {success: true}};
1260
1312
  }
1313
+
1314
+ async #getNetworkRequestImageData(lcpRequest: Trace.Types.Events.SyntheticNetworkRequest): Promise<string|undefined> {
1315
+ const target = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
1316
+ const networkManager = target?.model(SDK.NetworkManager.NetworkManager);
1317
+ if (!target || !networkManager) {
1318
+ return undefined;
1319
+ }
1320
+
1321
+ const networkLog = Logs.NetworkLog.NetworkLog.instance();
1322
+ const requestId = lcpRequest.args.data.requestId;
1323
+ const sdkRequest = networkLog.requestByManagerAndId(networkManager, requestId);
1324
+
1325
+ if (sdkRequest?.contentType().isImage()) {
1326
+ const contentData = await sdkRequest.requestContentData();
1327
+ if (!TextUtils.ContentData.ContentData.isError(contentData)) {
1328
+ return contentData.asDataUrl() ?? undefined;
1329
+ }
1330
+ }
1331
+ return undefined;
1332
+ }
1261
1333
  }
@@ -3,7 +3,6 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import * as Host from '../../../core/host/host.js';
6
- import * as i18n from '../../../core/i18n/i18n.js';
7
6
  import * as Root from '../../../core/root/root.js';
8
7
  import type {AICallTree} from '../performance/AICallTree.js';
9
8
  import type {AgentFocus} from '../performance/AIContext.js';
@@ -11,14 +10,6 @@ import type {AgentFocus} from '../performance/AIContext.js';
11
10
  import {AiAgent, type ContextResponse, type ConversationContext, type RequestOptions, ResponseType} from './AiAgent.js';
12
11
  import {PerformanceTraceContext} from './PerformanceAgent.js';
13
12
 
14
- const UIStringsNotTranslated = {
15
- analyzingCallTree: 'Analyzing call tree',
16
- /**
17
- * @description Shown when the agent is investigating network activity
18
- */
19
- } as const;
20
- const lockedString = i18n.i18n.lockedString;
21
-
22
13
  /**
23
14
  * Preamble clocks in at ~970 tokens.
24
15
  * The prose is around 4.5 chars per token.
@@ -124,7 +115,6 @@ export class PerformanceAnnotationsAgent extends AiAgent<AgentFocus> {
124
115
 
125
116
  yield {
126
117
  type: ResponseType.CONTEXT,
127
- title: lockedString(UIStringsNotTranslated.analyzingCallTree),
128
118
  details: [
129
119
  {
130
120
  title: 'Selected call tree',
@@ -216,7 +216,6 @@ Content:
216
216
  [
217
217
  {
218
218
  "type": "context",
219
- "title": "Analyzing the prompt",
220
219
  "details": [
221
220
  {
222
221
  "title": "Data used",
@@ -296,7 +295,6 @@ Content:
296
295
  [
297
296
  {
298
297
  "type": "context",
299
- "title": "Analyzing the prompt",
300
298
  "details": [
301
299
  {
302
300
  "title": "Data used",
@@ -321,7 +319,6 @@ Content:
321
319
  [
322
320
  {
323
321
  "type": "context",
324
- "title": "Analyzing the prompt",
325
322
  "details": [
326
323
  {
327
324
  "title": "Data used",
@@ -349,7 +346,6 @@ Content:
349
346
  [
350
347
  {
351
348
  "type": "context",
352
- "title": "Analyzing the prompt",
353
349
  "details": [
354
350
  {
355
351
  "title": "Data used",
@@ -374,7 +370,6 @@ Content:
374
370
  [
375
371
  {
376
372
  "type": "context",
377
- "title": "Analyzing the prompt",
378
373
  "details": [
379
374
  {
380
375
  "title": "Data used",
@@ -397,7 +392,6 @@ Content:
397
392
  [
398
393
  {
399
394
  "type": "context",
400
- "title": "Analyzing the prompt",
401
395
  "details": [
402
396
  {
403
397
  "title": "Data used",
@@ -37,10 +37,6 @@ import {
37
37
  * Strings that don't need to be translated at this time.
38
38
  */
39
39
  const UIStringsNotTranslate = {
40
- /**
41
- * @description Title for context details for Freestyler.
42
- */
43
- analyzingThePrompt: 'Analyzing the prompt',
44
40
  /**
45
41
  * @description Heading text for context details of Freestyler agent.
46
42
  */
@@ -1045,7 +1041,6 @@ const data = {
1045
1041
  }
1046
1042
  yield {
1047
1043
  type: ResponseType.CONTEXT,
1048
- title: lockedString(UIStringsNotTranslate.analyzingThePrompt),
1049
1044
  details: [{
1050
1045
  title: lockedString(UIStringsNotTranslate.dataUsed),
1051
1046
  text: await StylingAgent.describeElement(selectedElement.getItem()),
@@ -1078,6 +1073,7 @@ const data = {
1078
1073
  ];
1079
1074
  return answer;
1080
1075
  }
1076
+
1081
1077
  override async enhanceQuery(
1082
1078
  query: string, selectedElement: ConversationContext<SDK.DOMModel.DOMNode>|null,
1083
1079
  multimodalInputType?: MultimodalInputType): Promise<string> {
@@ -21,6 +21,7 @@ import * as BuiltInAi from './BuiltInAi.js';
21
21
  import * as ChangeManager from './ChangeManager.js';
22
22
  import * as ConversationHandler from './ConversationHandler.js';
23
23
  import * as FileFormatter from './data_formatters/FileFormatter.js';
24
+ import * as LighthouseFormatter from './data_formatters/LighthouseFormatter.js';
24
25
  import * as NetworkRequestFormatter from './data_formatters/NetworkRequestFormatter.js';
25
26
  import * as PerformanceInsightFormatter from './data_formatters/PerformanceInsightFormatter.js';
26
27
  import * as PerformanceTraceFormatter from './data_formatters/PerformanceTraceFormatter.js';
@@ -55,6 +56,7 @@ export {
55
56
  FileAgent,
56
57
  FileFormatter,
57
58
  Injected,
59
+ LighthouseFormatter,
58
60
  NetworkAgent,
59
61
  NetworkRequestFormatter,
60
62
  PatchAgent,
@@ -0,0 +1,84 @@
1
+ Title: LighthouseFormatter generates a summary
2
+ Content:
3
+ # Lighthouse Report Summary
4
+ URL: https://example.com
5
+ Fetch Time: 2025-01-01T00:00:00.000Z
6
+ Lighthouse Version: 12.0.0
7
+
8
+ ## Category Scores
9
+ - Performance: 80
10
+ - Accessibility: 100
11
+ === end content
12
+
13
+ Title: LighthouseFormatter generates audits for a category
14
+ Content:
15
+ # Audits for Performance
16
+
17
+ The following audits in this category have a score below 90 and may need attention:
18
+ - **First Audit**: 80 (1.2s)
19
+ * Description of first audit
20
+ === end content
21
+
22
+ Title: LighthouseFormatter indicates when all audits passed
23
+ Content:
24
+ # Audits for Accessibility
25
+
26
+ All audits in this category passed (score >= 90).
27
+ === end content
28
+
29
+ Title: LighthouseFormatter handles missing category
30
+ Content:
31
+ Category "seo" not found.
32
+ === end content
33
+
34
+ Title: LighthouseFormatter formats table details
35
+ Content:
36
+ # Audits for Performance
37
+
38
+ The following audits in this category have a score below 90 and may need attention:
39
+ - **Table Audit**: 50
40
+ * Audit with a table
41
+
42
+ | URL | Wasted Bytes | Node | Location |
43
+ | https://example.com/script.js | 1024 | div.main (path: 1,HTML,1,BODY,5,DIV) | https://example.com/script.js:10:5 |
44
+ | https://example.com/style.css | 512 | body > p (selector: body > p) (path: 1,HTML,1,BODY,10,P) | https://example.com/style.css |
45
+ === end content
46
+
47
+ Title: LighthouseFormatter formats opportunity details
48
+ Content:
49
+ # Audits for Performance
50
+
51
+ The following audits in this category have a score below 90 and may need attention:
52
+ - **Opportunity Audit**: 50
53
+ * Audit with an opportunity
54
+
55
+ Potential savings: 500ms, Potential savings: 2048 bytes
56
+ | URL | Wasted Bytes |
57
+ | https://example.com/large-script.js | 2048 |
58
+ === end content
59
+
60
+ Title: LighthouseFormatter formats table details with summary
61
+ Content:
62
+ # Audits for Performance
63
+
64
+ The following audits in this category have a score below 90 and may need attention:
65
+ - **Summary Audit**: 50
66
+ * Audit with a table summary
67
+
68
+ Wasted time: 100ms
69
+ Wasted bytes: 512
70
+ | Text |
71
+ | Some detail |
72
+ === end content
73
+
74
+ Title: LighthouseFormatter formats landmark-one-main audit with node explanation
75
+ Content:
76
+ # Audits for Accessibility
77
+
78
+ The following audits in this category have a score below 90 and may need attention:
79
+ - **Document does not have a main landmark.**: 0
80
+ * One main landmark helps screen reader users navigate a web page. [Learn more about landmarks](https://dequeuniversity.com/rules/axe/4.11/landmark-one-main).
81
+
82
+ | Failing Elements |
83
+ | html (selector: html) (path: 1,HTML) (explanation: Fix all of the following: Document does not have a main landmark) |
84
+ === end content
@@ -0,0 +1,172 @@
1
+ // Copyright 2026 The Chromium Authors
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import type * as LHModel from '../../lighthouse/lighthouse.js';
6
+
7
+ /**
8
+ * A formatter that takes a raw Lighthouse report JSON and creates a markdown
9
+ * summary for an AI Agent.
10
+ */
11
+ export class LighthouseFormatter {
12
+ /**
13
+ * Returns an overall summary and high-level overview of the Lighthouse report.
14
+ */
15
+ summary(report: LHModel.ReporterTypes.ReportJSON): string {
16
+ const lines: string[] = [];
17
+ lines.push('# Lighthouse Report Summary');
18
+ lines.push(`URL: ${report.finalDisplayedUrl}`);
19
+ lines.push(`Fetch Time: ${report.fetchTime}`);
20
+ lines.push(`Lighthouse Version: ${report.lighthouseVersion}`);
21
+ lines.push('');
22
+ lines.push('## Category Scores');
23
+ for (const category of Object.values(report.categories)) {
24
+ const score = category.score !== null ? Math.round(category.score * 100) : 'n/a';
25
+ lines.push(`- ${category.title}: ${score}`);
26
+ }
27
+ return lines.join('\n');
28
+ }
29
+
30
+ /**
31
+ * Returns a markdown list of all audits in a given category.
32
+ * Highlight failing audits (score < 90).
33
+ */
34
+ audits(report: LHModel.ReporterTypes.ReportJSON, categoryId: LHModel.RunTypes.CategoryId): string {
35
+ const category = report.categories[categoryId];
36
+ if (!category) {
37
+ return `Category "${categoryId}" not found.`;
38
+ }
39
+
40
+ const lines: string[] = [];
41
+ lines.push(`# Audits for ${category.title}`);
42
+ if (category.description) {
43
+ lines.push(`${category.description.replace(/\n/g, ' ')}`);
44
+ }
45
+ lines.push('');
46
+
47
+ const failingAudits = category.auditRefs.filter(ref => {
48
+ const audit = report.audits[ref.id];
49
+ return audit && audit.score !== null && audit.score < 0.9;
50
+ });
51
+
52
+ if (failingAudits.length === 0) {
53
+ lines.push('All audits in this category passed (score >= 90).');
54
+ return lines.join('\n');
55
+ }
56
+
57
+ lines.push('The following audits in this category have a score below 90 and may need attention:');
58
+ for (const ref of failingAudits) {
59
+ const audit = report.audits[ref.id];
60
+ if (!audit) {
61
+ continue;
62
+ }
63
+ const score = audit.score !== null ? Math.round(audit.score * 100) : 'n/a';
64
+ let line = `- **${audit.title}**: ${score}`;
65
+ if (audit.displayValue) {
66
+ line += ` (${audit.displayValue})`;
67
+ }
68
+ lines.push(line);
69
+ lines.push(` * ${audit.description.replace(/\n/g, ' ')}`);
70
+ if (audit.details) {
71
+ const formattedDetails = this.#formatDetails(audit.details);
72
+ if (formattedDetails) {
73
+ lines.push('');
74
+ lines.push(formattedDetails.split('\n').map(l => ` ${l}`).join('\n'));
75
+ }
76
+ }
77
+ }
78
+
79
+ return lines.join('\n');
80
+ }
81
+
82
+ #formatDetails(details: LHModel.ReporterTypes.DetailsJSON): string {
83
+ switch (details.type) {
84
+ case 'table': {
85
+ const lines: string[] = [];
86
+ if (details.summary) {
87
+ const summaryParts = [];
88
+ // Purposefully rule out 0 because we want to skip if there is 0 wasted time.
89
+ if (details.summary.wastedMs) {
90
+ summaryParts.push(`Wasted time: ${details.summary.wastedMs}ms`);
91
+ }
92
+ // Purposefully rule out 0 because we want to skip if there is 0 wasted time.
93
+ if (details.summary.wastedBytes) {
94
+ summaryParts.push(`Wasted bytes: ${details.summary.wastedBytes}`);
95
+ }
96
+ if (summaryParts.length > 0) {
97
+ lines.push(summaryParts.join('\n'));
98
+ }
99
+ }
100
+ lines.push(this.#formatTable(details.headings, details.items));
101
+ return lines.join('\n');
102
+ }
103
+ case 'opportunity': {
104
+ const lines: string[] = [];
105
+ const summaryParts = [];
106
+ if (details.overallSavingsMs) {
107
+ summaryParts.push(`Potential savings: ${details.overallSavingsMs}ms`);
108
+ }
109
+ if (details.overallSavingsBytes) {
110
+ summaryParts.push(`Potential savings: ${details.overallSavingsBytes} bytes`);
111
+ }
112
+ if (summaryParts.length > 0) {
113
+ lines.push(summaryParts.join(', '));
114
+ }
115
+ lines.push(this.#formatTable(details.headings, details.items));
116
+ return lines.join('\n');
117
+ }
118
+ default:
119
+ return '';
120
+ }
121
+ }
122
+
123
+ #formatTable(headings: LHModel.ReporterTypes.TableHeadingJSON[], items: Array<Record<string, unknown>>): string {
124
+ const lines: string[] = [];
125
+ lines.push(`| ${headings.map(h => h.label).join(' | ')} |`);
126
+ for (const item of items) {
127
+ const row = headings.map(h => this.#formatTableValue(item[h.key] as LHModel.ReporterTypes.TableItemValue));
128
+ lines.push(`| ${row.join(' | ')} |`);
129
+ }
130
+ return lines.join('\n');
131
+ }
132
+
133
+ #formatTableValue(value: LHModel.ReporterTypes.TableItemValue|undefined): string {
134
+ if (value === undefined || value === null) {
135
+ return '';
136
+ }
137
+ if (typeof value === 'string' || typeof value === 'number') {
138
+ return String(value);
139
+ }
140
+ if (typeof value === 'object' && 'type' in value) {
141
+ switch (value.type) {
142
+ case 'node': {
143
+ let label = value.nodeLabel || value.selector || value.snippet || '(node)';
144
+ if (value.selector) {
145
+ label += ` (selector: ${value.selector})`;
146
+ }
147
+ if (value.path) {
148
+ label += ` (path: ${value.path})`;
149
+ }
150
+ if (value.explanation) {
151
+ label += ` (explanation: ${value.explanation.replace(/\n/g, ' ')})`;
152
+ }
153
+ return label;
154
+ }
155
+ case 'source-location': {
156
+ const parts = [];
157
+ if (value.url) {
158
+ parts.push(value.url);
159
+ }
160
+ if (value.line) {
161
+ parts.push(String(value.line));
162
+ }
163
+ if (value.column) {
164
+ parts.push(String(value.column));
165
+ }
166
+ return parts.join(':');
167
+ }
168
+ }
169
+ }
170
+ return '';
171
+ }
172
+ }
@@ -19,7 +19,7 @@ const INITIAL_RESTORE_BREAKPOINT_COUNT = 100;
19
19
 
20
20
  export class BreakpointManager extends Common.ObjectWrapper.ObjectWrapper<EventTypes> implements
21
21
  SDK.TargetManager.SDKModelObserver<SDK.DebuggerModel.DebuggerModel> {
22
- readonly storage = new Storage();
22
+ readonly storage: Storage;
23
23
  readonly #workspace: Workspace.Workspace.WorkspaceImpl;
24
24
  readonly targetManager: SDK.TargetManager.TargetManager;
25
25
  readonly debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding;
@@ -38,11 +38,12 @@ export class BreakpointManager extends Common.ObjectWrapper.ObjectWrapper<EventT
38
38
  private constructor(
39
39
  targetManager: SDK.TargetManager.TargetManager, workspace: Workspace.Workspace.WorkspaceImpl,
40
40
  debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding,
41
- restoreInitialBreakpointCount?: number) {
41
+ settings: Common.Settings.Settings, restoreInitialBreakpointCount?: number) {
42
42
  super();
43
43
  this.#workspace = workspace;
44
44
  this.targetManager = targetManager;
45
45
  this.debuggerWorkspaceBinding = debuggerWorkspaceBinding;
46
+ this.storage = new Storage(settings);
46
47
 
47
48
  this.storage.mute();
48
49
  this.#setInitialBreakpoints(restoreInitialBreakpointCount ?? INITIAL_RESTORE_BREAKPOINT_COUNT);
@@ -73,18 +74,26 @@ export class BreakpointManager extends Common.ObjectWrapper.ObjectWrapper<EventT
73
74
  targetManager: SDK.TargetManager.TargetManager|null,
74
75
  workspace: Workspace.Workspace.WorkspaceImpl|null,
75
76
  debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding|null,
77
+ settings: Common.Settings.Settings|null,
76
78
  restoreInitialBreakpointCount?: number,
77
- } = {forceNew: null, targetManager: null, workspace: null, debuggerWorkspaceBinding: null}): BreakpointManager {
78
- const {forceNew, targetManager, workspace, debuggerWorkspaceBinding, restoreInitialBreakpointCount} = opts;
79
+ } = {
80
+ forceNew: null,
81
+ targetManager: null,
82
+ workspace: null,
83
+ debuggerWorkspaceBinding: null,
84
+ settings: null,
85
+ }): BreakpointManager {
86
+ const {forceNew, targetManager, workspace, debuggerWorkspaceBinding, settings, restoreInitialBreakpointCount} =
87
+ opts;
79
88
  if (!breakpointManagerInstance || forceNew) {
80
- if (!targetManager || !workspace || !debuggerWorkspaceBinding) {
89
+ if (!targetManager || !workspace || !debuggerWorkspaceBinding || !settings) {
81
90
  throw new Error(
82
- `Unable to create settings: targetManager, workspace, and debuggerWorkspaceBinding must be provided: ${
91
+ `Unable to create settings: targetManager, workspace, debuggerWorkspaceBinding, and settings must be provided: ${
83
92
  new Error().stack}`);
84
93
  }
85
94
 
86
- breakpointManagerInstance =
87
- new BreakpointManager(targetManager, workspace, debuggerWorkspaceBinding, restoreInitialBreakpointCount);
95
+ breakpointManagerInstance = new BreakpointManager(
96
+ targetManager, workspace, debuggerWorkspaceBinding, settings, restoreInitialBreakpointCount);
88
97
  }
89
98
 
90
99
  return breakpointManagerInstance;
@@ -986,8 +995,7 @@ export class ModelBreakpoint {
986
995
  const {lineNumber: uiLineNumber, columnNumber: uiColumnNumber} =
987
996
  BreakpointManager.uiLocationFromBreakpointLocation(uiSourceCode, lineNumber, columnNumber);
988
997
  const locations =
989
- await Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().uiLocationToRawLocations(
990
- uiSourceCode, uiLineNumber, uiColumnNumber);
998
+ await this.#debuggerWorkspaceBinding.uiLocationToRawLocations(uiSourceCode, uiLineNumber, uiColumnNumber);
991
999
  debuggerLocations = locations.filter(location => location.debuggerModel === this.#debuggerModel);
992
1000
  if (debuggerLocations.length) {
993
1001
  break;
@@ -1248,8 +1256,8 @@ class Storage {
1248
1256
  readonly breakpoints: Map<string, BreakpointStorageState>;
1249
1257
  #muted: boolean;
1250
1258
 
1251
- constructor() {
1252
- this.setting = Common.Settings.Settings.instance().createLocalSetting('breakpoints', []);
1259
+ constructor(settings: Common.Settings.Settings) {
1260
+ this.setting = settings.createLocalSetting('breakpoints', []);
1253
1261
  this.breakpoints = new Map();
1254
1262
  this.#muted = false;
1255
1263
  for (const breakpoint of this.setting.get()) {
@@ -680,7 +680,11 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
680
680
  }
681
681
 
682
682
  private applyUserAgent(userAgent: string, userAgentMetadata: Protocol.Emulation.UserAgentMetadata|null): void {
683
- SDK.NetworkManager.MultitargetNetworkManager.instance().setUserAgentOverride(userAgent, userAgentMetadata);
683
+ // When the user agent string is empty (e.g. custom desktop device without
684
+ // a UA override), metadata must also be cleared. The backend rejects
685
+ // setUserAgentOverride calls that provide metadata without a UA string.
686
+ SDK.NetworkManager.MultitargetNetworkManager.instance().setUserAgentOverride(
687
+ userAgent, userAgent ? userAgentMetadata : null);
684
688
  }
685
689
 
686
690
  private applyDeviceMetrics(
@@ -201,7 +201,8 @@ export class EmulatedDevice {
201
201
  const rawUserAgent = (parseValue(json, 'user-agent', 'string') as string);
202
202
  result.userAgent = SDK.NetworkManager.MultitargetNetworkManager.patchUserAgentWithChromeVersion(rawUserAgent);
203
203
 
204
- result.userAgentMetadata = parseValue(json, 'user-agent-metadata', 'object', null);
204
+ const userAgentMetadata = parseValue(json, 'user-agent-metadata', 'object', null);
205
+ result.userAgentMetadata = result.userAgent ? userAgentMetadata : null;
205
206
 
206
207
  const capabilities = parseValue(json, 'capabilities', 'object', []);
207
208
  if (!Array.isArray(capabilities)) {
@@ -366,7 +367,7 @@ export class EmulatedDevice {
366
367
  json['foldable-screen'] = this.isFoldableScreen;
367
368
  json['show'] = this.#show;
368
369
 
369
- if (this.userAgentMetadata) {
370
+ if (this.userAgent && this.userAgentMetadata) {
370
371
  json['user-agent-metadata'] = this.userAgentMetadata;
371
372
  }
372
373