chrome-devtools-frontend 1.0.1638082 → 1.0.1640418

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 (105) hide show
  1. package/front_end/core/host/AidaClientTypes.ts +8 -6
  2. package/front_end/core/sdk/DOMStorageModel.ts +1 -1
  3. package/front_end/core/sdk/TargetManager.ts +14 -1
  4. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +147 -0
  5. package/front_end/generated/ARIAProperties.js +0 -4
  6. package/front_end/generated/InspectorBackendCommands.ts +7 -3
  7. package/front_end/generated/protocol-mapping.d.ts +7 -0
  8. package/front_end/generated/protocol-proxy-api.d.ts +14 -0
  9. package/front_end/generated/protocol.ts +80 -0
  10. package/front_end/global_typings/global_defs.d.ts +13 -0
  11. package/front_end/models/ai_assistance/AiAgent2.ts +116 -0
  12. package/front_end/models/ai_assistance/AiConversation.ts +22 -28
  13. package/front_end/models/ai_assistance/AiOrigins.ts +46 -0
  14. package/front_end/models/ai_assistance/README.md +16 -0
  15. package/front_end/models/ai_assistance/StorageItem.ts +30 -26
  16. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +2 -2
  17. package/front_end/models/ai_assistance/agents/AiAgent.ts +73 -36
  18. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +2 -2
  19. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +25 -10
  20. package/front_end/models/ai_assistance/agents/ConversationSummaryAgent.ts +1 -1
  21. package/front_end/models/ai_assistance/agents/FileAgent.ts +2 -2
  22. package/front_end/models/ai_assistance/agents/GreenDevAgent.ts +1 -1
  23. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +3 -4
  24. package/front_end/models/ai_assistance/agents/PerformanceAgent.snapshot.txt +2 -2
  25. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +41 -12
  26. package/front_end/models/ai_assistance/agents/StorageAgent.ts +442 -122
  27. package/front_end/models/ai_assistance/agents/StylingAgent.ts +2 -2
  28. package/front_end/models/ai_assistance/ai_assistance.ts +4 -0
  29. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +2 -2
  30. package/front_end/models/ai_assistance/performance/AIContext.ts +7 -8
  31. package/front_end/models/ai_assistance/skills/README.md +40 -0
  32. package/front_end/models/ai_assistance/skills/Skill.ts +13 -0
  33. package/front_end/models/ai_assistance/skills/SkillRegistry.ts +10 -0
  34. package/front_end/models/ai_assistance/skills/styling.md +6 -0
  35. package/front_end/models/heap_snapshot/HeapSnapshotModel.ts +20 -0
  36. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +5 -0
  37. package/front_end/models/javascript_metadata/NativeFunctions.js +6 -2
  38. package/front_end/models/live-metrics/web-vitals-injected/web-vitals-injected.ts +1 -1
  39. package/front_end/models/stack_trace/StackTraceImpl.ts +29 -9
  40. package/front_end/models/stack_trace/StackTraceModel.ts +23 -11
  41. package/front_end/models/stack_trace/Trie.ts +11 -1
  42. package/front_end/models/trace/extras/TraceTree.ts +20 -1
  43. package/front_end/models/trace/insights/Common.ts +9 -0
  44. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +8 -0
  45. package/front_end/panels/ai_assistance/components/ChatMessage.ts +114 -2
  46. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +2 -3
  47. package/front_end/panels/ai_assistance/components/chatMessage.css +27 -0
  48. package/front_end/panels/application/CookieItemsView.ts +24 -0
  49. package/front_end/panels/application/DOMStorageItemsView.ts +9 -4
  50. package/front_end/panels/application/preloading/components/PreloadingString.ts +6 -0
  51. package/front_end/panels/console/ConsoleViewMessage.ts +13 -12
  52. package/front_end/panels/elements/StandaloneStylesContainer.ts +10 -0
  53. package/front_end/panels/elements/StylePropertyTreeElement.ts +30 -1
  54. package/front_end/panels/elements/StylesContainer.ts +3 -0
  55. package/front_end/panels/elements/StylesSidebarPane.ts +45 -4
  56. package/front_end/panels/layer_viewer/layerDetailsView.css +1 -1
  57. package/front_end/panels/lighthouse/LighthouseController.ts +1 -1
  58. package/front_end/panels/lighthouse/LighthouseProtocolService.ts +4 -4
  59. package/front_end/panels/network/NetworkDataGridNode.ts +14 -0
  60. package/front_end/panels/network/NetworkLogViewColumns.ts +2 -2
  61. package/front_end/panels/network/networkTimingTable.css +2 -4
  62. package/front_end/panels/sources/ScopeChainSidebarPane.ts +169 -106
  63. package/front_end/panels/timeline/components/IgnoreListSetting.ts +1 -0
  64. package/front_end/third_party/chromium/README.chromium +1 -1
  65. package/front_end/third_party/marked/README.chromium +3 -6
  66. package/front_end/third_party/marked/package/README.md +5 -5
  67. package/front_end/third_party/marked/package/bin/main.js +27 -22
  68. package/front_end/third_party/marked/package/bin/marked.js +2 -1
  69. package/front_end/third_party/marked/package/lib/marked.esm.d.ts +346 -256
  70. package/front_end/third_party/marked/package/lib/marked.esm.js +67 -2698
  71. package/front_end/third_party/marked/package/lib/marked.esm.js.map +7 -1
  72. package/front_end/third_party/marked/package/lib/marked.umd.js +69 -2722
  73. package/front_end/third_party/marked/package/lib/marked.umd.js.map +7 -1
  74. package/front_end/third_party/marked/package/man/marked.1 +4 -2
  75. package/front_end/third_party/marked/package/man/marked.1.md +2 -1
  76. package/front_end/third_party/marked/package/package.json +49 -57
  77. package/front_end/tsconfig.json +1 -1
  78. package/front_end/ui/components/markdown_view/CodeBlock.ts +17 -6
  79. package/front_end/ui/components/markdown_view/MarkdownView.ts +39 -3
  80. package/front_end/ui/components/markdown_view/codeBlock.css +11 -0
  81. package/front_end/ui/components/markdown_view/markdownView.css +17 -0
  82. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +0 -79
  83. package/front_end/ui/legacy/components/object_ui/RemoteObjectPreviewFormatter.ts +16 -4
  84. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  85. package/inspector_overlay/testing/InspectorOverlayHelpers.ts +2 -0
  86. package/mcp/mcp.ts +1 -1
  87. package/package.json +7 -9
  88. package/front_end/third_party/marked/package/bin/marked +0 -215
  89. package/front_end/third_party/marked/package/lib/marked.cjs +0 -2726
  90. package/front_end/third_party/marked/package/lib/marked.cjs.map +0 -1
  91. package/front_end/third_party/marked/package/lib/marked.d.cts +0 -670
  92. package/front_end/third_party/marked/package/lib/marked.js +0 -2780
  93. package/front_end/third_party/marked/package/man/marked.1.txt +0 -86
  94. package/front_end/third_party/marked/package/marked.min.js +0 -6
  95. package/front_end/third_party/marked/package/src/Lexer.js +0 -492
  96. package/front_end/third_party/marked/package/src/Parser.js +0 -286
  97. package/front_end/third_party/marked/package/src/Renderer.js +0 -166
  98. package/front_end/third_party/marked/package/src/Slugger.js +0 -49
  99. package/front_end/third_party/marked/package/src/TextRenderer.js +0 -42
  100. package/front_end/third_party/marked/package/src/Tokenizer.js +0 -755
  101. package/front_end/third_party/marked/package/src/defaults.js +0 -29
  102. package/front_end/third_party/marked/package/src/helpers.js +0 -249
  103. package/front_end/third_party/marked/package/src/marked.js +0 -350
  104. package/front_end/third_party/marked/package/src/rules.js +0 -285
  105. /package/front_end/third_party/marked/package/{LICENSE.md → LICENSE} +0 -0
@@ -13,6 +13,7 @@ import type * as NetworkTimeCalculator from '../network_time_calculator/network_
13
13
 
14
14
  import {AccessibilityAgent, AccessibilityContext} from './agents/AccessibilityAgent.js';
15
15
  import {
16
+ type AgentOptions,
16
17
  type AiAgent,
17
18
  type AllowedOriginResult,
18
19
  type ContextDetail,
@@ -29,6 +30,7 @@ import {NetworkAgent, RequestContext} from './agents/NetworkAgent.js';
29
30
  import {PerformanceAgent, PerformanceTraceContext} from './agents/PerformanceAgent.js';
30
31
  import {StorageAgent, StorageContext} from './agents/StorageAgent.js';
31
32
  import {NodeContext, StylingAgent} from './agents/StylingAgent.js';
33
+ import {AiAgent2} from './AiAgent2.js';
32
34
  import {AiHistoryStorage, ConversationType, type SerializedConversation} from './AiHistoryStorage.js';
33
35
  import type {ChangeManager} from './ChangeManager.js';
34
36
 
@@ -356,35 +358,27 @@ export class AiConversation {
356
358
  allowedOrigin: this.allowedOrigin,
357
359
  history,
358
360
  };
361
+
362
+ this.#agent = Root.Runtime.hostConfig.devToolsAiV2Architecture?.enabled ? new AiAgent2(options) :
363
+ this.#createV1Agent(type, options);
364
+ }
365
+
366
+ #createV1Agent(type: ConversationType, options: AgentOptions): AiAgent<unknown> {
359
367
  switch (type) {
360
- case ConversationType.STYLING: {
361
- this.#agent = new StylingAgent(options);
362
- break;
363
- }
364
- case ConversationType.NETWORK: {
365
- this.#agent = new NetworkAgent(options);
366
- break;
367
- }
368
- case ConversationType.FILE: {
369
- this.#agent = new FileAgent(options);
370
- break;
371
- }
372
- case ConversationType.PERFORMANCE: {
373
- this.#agent = new PerformanceAgent(options);
374
- break;
375
- }
376
- case ConversationType.ACCESSIBILITY: {
377
- this.#agent = new AccessibilityAgent(options);
378
- break;
379
- }
380
- case ConversationType.STORAGE: {
381
- this.#agent = new StorageAgent(options);
382
- break;
383
- }
384
- case ConversationType.NONE: {
385
- this.#agent = new ContextSelectionAgent(options);
386
- break;
387
- }
368
+ case ConversationType.STYLING:
369
+ return new StylingAgent(options);
370
+ case ConversationType.NETWORK:
371
+ return new NetworkAgent(options);
372
+ case ConversationType.FILE:
373
+ return new FileAgent(options);
374
+ case ConversationType.PERFORMANCE:
375
+ return new PerformanceAgent(options);
376
+ case ConversationType.ACCESSIBILITY:
377
+ return new AccessibilityAgent(options);
378
+ case ConversationType.STORAGE:
379
+ return new StorageAgent(options);
380
+ case ConversationType.NONE:
381
+ return new ContextSelectionAgent(options);
388
382
  default:
389
383
  Platform.assertNever(type, 'Unknown conversation type');
390
384
  }
@@ -0,0 +1,46 @@
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 * as Common from '../../core/common/common.js';
6
+ import type * as Platform from '../../core/platform/platform.js';
7
+
8
+ /**
9
+ * Returns true if the origin is considered opaque and should be blocked from
10
+ * AI assistance to prevent potential data leakage.
11
+ *
12
+ * @see https://crbug.com/513732588
13
+ */
14
+ export function isOpaqueOrigin(origin: string): boolean {
15
+ /**
16
+ * Origins starting with 'about' (like about:blank or about:srcdoc) are
17
+ * considered opaque. 'about://' is the sentinel used by DevTools
18
+ * ParsedURL.securityOrigin() for these.
19
+ */
20
+ return origin === 'null' || origin === 'data:' || origin.startsWith('about') || origin.startsWith('detached');
21
+ }
22
+
23
+ /**
24
+ * Extracts the origin from a context URL or identifier.
25
+ * Handles special cases like "detached" nodes and trace identifiers.
26
+ */
27
+ export function extractContextOrigin(contextURL: string): string {
28
+ if (isOpaqueOrigin(contextURL)) {
29
+ return contextURL;
30
+ }
31
+ if (contextURL.startsWith('trace-')) {
32
+ return contextURL;
33
+ }
34
+ return Common.ParsedURL.ParsedURL.extractOrigin(contextURL as Platform.DevToolsPath.UrlString);
35
+ }
36
+
37
+ /**
38
+ * Determines if two origins are equivalent and safe to be used together.
39
+ * Opaque origins are never equivalent to anything, not even themselves.
40
+ */
41
+ export function areOriginsEquivalent(origin1: string, origin2: string): boolean {
42
+ if (isOpaqueOrigin(origin1) || isOpaqueOrigin(origin2)) {
43
+ return false;
44
+ }
45
+ return origin1 === origin2;
46
+ }
@@ -22,6 +22,22 @@ When the user begins a conversation with the AI, we want to include information
22
22
 
23
23
  To deal with the work to take an object that is the AI agent's context and turn it into a text representation that can be sent to the AI, we use _formatters_. These are typically classes with `static` methods that can take in objects and return their text representation that we want to pass to the AI.
24
24
 
25
+ ## Future Architecture (V2) - WIP
26
+
27
+ We are currently working on migrating the DevTools AI Assistance from a siloed multi-agent architecture to a unified, skill-based single-agent architecture (`AIAgent2`).
28
+
29
+ In this new architecture:
30
+ - A single agent (`AIAgent2`) handles multiple domains.
31
+ - Capabilities are defined as **Skills** in Markdown files.
32
+ - The agent can dynamically load skills as needed via a `learnSkill` tool.
33
+
34
+ This work is currently in progress and behind a feature flag.
35
+
36
+ ### Skills Build System
37
+
38
+ To support dynamic loading of skills, we generate JavaScript files from Markdown files containing skill definitions.
39
+ We use a nested `BUILD.gn` file in the `skills/` subdirectory specifically for this purpose. This ensures that GN's `target_gen_dir` points to `gen/front_end/models/ai_assistance/skills/`, placing the generated `.skill.js` files in the same relative structure as their source `.md` files. This allows TypeScript files in the `skills/` directory (like `map.ts`) to import the generated files using relative paths (e.g., `./styling.skill.js`) seamlessly.
40
+
25
41
  ## Performance specific documentation
26
42
 
27
43
  ### `TimelineUtils.AIContext.AgentFocus`
@@ -2,35 +2,39 @@
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
- interface StorageItemOriginOnly {
6
- origin: string;
7
- storageKey?: string;
8
- storageType?: never;
9
- key?: never;
5
+ export class StorageItem {
6
+ constructor(
7
+ /**
8
+ * The origin of the top-level primary page target being inspected.
9
+ * Used to restrict AI agent tools from accessing unauthorized pages.
10
+ */
11
+ readonly primaryTargetOrigin: string,
12
+ /**
13
+ * The origin of the selected storage or cookie item (if any).
14
+ * If no item is selected, this is the same as primaryTargetOrigin.
15
+ */
16
+ readonly origin: string,
17
+ ) {
18
+ }
10
19
  }
11
20
 
12
- interface StorageItemWithKey {
13
- origin: string;
14
- storageKey?: string;
15
- storageType: 'cookie'|'localStorage'|'sessionStorage';
16
- key: string;
21
+ export class DOMStorageItem extends StorageItem {
22
+ constructor(
23
+ primaryTargetOrigin: string,
24
+ origin: string,
25
+ /** The storage key partition identifier used by the browser storage engine. */
26
+ readonly storageKey: string,
27
+ /** The sub-category of DOM storage: 'localStorage' or 'sessionStorage'. */
28
+ readonly type: string,
29
+ /** The optional specific key of the selected item in this storage partition. */
30
+ readonly key?: string,
31
+ ) {
32
+ super(primaryTargetOrigin, origin);
33
+ }
17
34
  }
18
35
 
19
- export type StorageItemData = StorageItemOriginOnly|StorageItemWithKey;
20
-
21
- // The StorageItem is used as context for the Ai Assistance.
22
- // If the user selects a row in e.g. the cookies table, the storageType and key
23
- // will be populated.
24
- export class StorageItem {
25
- readonly origin: string;
26
- readonly storageKey?: string;
27
- readonly storageType?: 'cookie'|'localStorage'|'sessionStorage';
28
- readonly key?: string;
29
-
30
- constructor(data: StorageItemData) {
31
- this.origin = data.origin;
32
- this.storageKey = data.storageKey;
33
- this.storageType = data.storageType;
34
- this.key = data.key;
36
+ export class CookieItem extends StorageItem {
37
+ constructor(primaryTargetOrigin: string, origin: string, readonly name?: string) {
38
+ super(primaryTargetOrigin, origin);
35
39
  }
36
40
  }
@@ -93,8 +93,8 @@ export class AccessibilityContext extends ConversationContext<LHModel.ReporterTy
93
93
  return this.#lh.finalUrl ?? this.#lh.finalDisplayedUrl;
94
94
  }
95
95
 
96
- override getOrigin(): string {
97
- return new URL(this.#url()).origin;
96
+ override getURL(): string {
97
+ return this.#url();
98
98
  }
99
99
 
100
100
  override getItem(): LHModel.ReporterTypes.ReportJSON {
@@ -3,6 +3,7 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import * as Host from '../../../core/host/host.js';
6
+ import type {UrlString} from '../../../core/platform/DevToolsPath.js';
6
7
  import * as Root from '../../../core/root/root.js';
7
8
  import type * as SDK from '../../../core/sdk/sdk.js';
8
9
  import type * as Protocol from '../../../generated/protocol.js';
@@ -10,6 +11,7 @@ import type * as LHModel from '../../lighthouse/lighthouse.js';
10
11
  import type * as TextUtils from '../../text_utils/text_utils.js';
11
12
  import type * as Trace from '../../trace/trace.js';
12
13
  import type * as Workspace from '../../workspace/workspace.js';
14
+ import {areOriginsEquivalent, extractContextOrigin, isOpaqueOrigin} from '../AiOrigins.js';
13
15
  import {debugLog, isStructuredLogEnabled} from '../debug.js';
14
16
 
15
17
  const MAX_SUGGESTION_LENGTH = 200;
@@ -36,21 +38,6 @@ export const enum ErrorType {
36
38
  CROSS_ORIGIN = 'cross-origin'
37
39
  }
38
40
 
39
- /**
40
- * Returns true if the origin is considered opaque and should be blocked from
41
- * AI assistance to prevent potential data leakage.
42
- *
43
- * @see https://crbug.com/513732588
44
- */
45
- export function isOpaqueOrigin(origin: string): boolean {
46
- /**
47
- * Origins starting with 'about' (like about:blank or about:srcdoc) are
48
- * considered opaque. 'about://' is the sentinel used by DevTools
49
- * ParsedURL.securityOrigin() for these.
50
- */
51
- return origin === 'null' || origin === 'data:' || origin.startsWith('about') || origin.startsWith('detached');
52
- }
53
-
54
41
  export const enum MultimodalInputType {
55
42
  SCREENSHOT = 'screenshot',
56
43
  UPLOADED_IMAGE = 'uploaded-image',
@@ -197,10 +184,14 @@ export interface ConversationSuggestion {
197
184
  export type ConversationSuggestions = [ConversationSuggestion, ...ConversationSuggestion[]];
198
185
 
199
186
  export abstract class ConversationContext<T> {
200
- abstract getOrigin(): string;
187
+ abstract getURL(): string;
201
188
  abstract getItem(): T;
202
189
  abstract getTitle(): string;
203
190
 
191
+ getOrigin(): string {
192
+ return extractContextOrigin(this.getURL());
193
+ }
194
+
204
195
  /**
205
196
  * Returns true if this data context (e.g., a DOM node or Network Request) is
206
197
  * allowed to be included in a conversation that is locked to the provided
@@ -214,17 +205,14 @@ export abstract class ConversationContext<T> {
214
205
  * If undefined, the conversation has not yet been locked to an origin.
215
206
  */
216
207
  isOriginAllowed(establishedOrigin: string|undefined): boolean {
217
- const dataOrigin = this.getOrigin();
218
- // Opaque origins are never allowed to be used as context.
219
- if (isOpaqueOrigin(dataOrigin)) {
220
- return false;
221
- }
208
+ const origin = this.getOrigin();
222
209
  // If no origin is established yet, this context will be the one to lock the conversation.
210
+ // Opaque origins are never allowed to be used as context.
223
211
  if (!establishedOrigin) {
224
- return true;
212
+ return !isOpaqueOrigin(origin);
225
213
  }
226
214
  // Only allow data that matches the origin the conversation is already locked to.
227
- return dataOrigin === establishedOrigin;
215
+ return areOriginsEquivalent(origin, establishedOrigin);
228
216
  }
229
217
 
230
218
  /**
@@ -321,6 +309,13 @@ export interface SourceFileAiWidget {
321
309
  };
322
310
  }
323
311
 
312
+ export interface SourceFilesListAiWidget {
313
+ name: 'SOURCE_FILES_LIST';
314
+ data: {
315
+ uiSourceCodes: Workspace.UISourceCode.UISourceCode[],
316
+ };
317
+ }
318
+
324
319
  export interface LighthouseReportAiWidget {
325
320
  name: 'LIGHTHOUSE_REPORT';
326
321
  data: {
@@ -344,9 +339,21 @@ export interface NetworkRequestGeneralHeadersAiWidget {
344
339
  };
345
340
  }
346
341
 
347
- export type AiWidget = ComputedStyleAiWidget|CoreVitalsAiWidget|StylePropertiesAiWidget|DomTreeAiWidget|
348
- PerformanceTraceAiWidget|PerfInsightAiWidget|TimelineRangeSummaryAiWidget|BottomUpTreeAiWidget|SourceFileAiWidget|
349
- LighthouseReportAiWidget|TimelineEventSummaryAiWidget|NetworkRequestGeneralHeadersAiWidget;
342
+ // If the line and column are not provided, we assume the whole file was sent to the agent.s
343
+ export interface SourceCodeAiWidget {
344
+ name: 'SOURCE_CODE';
345
+ data: {
346
+ url: UrlString,
347
+ code: string,
348
+ line?: number,
349
+ column?: number,
350
+ };
351
+ }
352
+
353
+ export type AiWidget =
354
+ ComputedStyleAiWidget|CoreVitalsAiWidget|StylePropertiesAiWidget|DomTreeAiWidget|PerformanceTraceAiWidget|
355
+ PerfInsightAiWidget|TimelineRangeSummaryAiWidget|BottomUpTreeAiWidget|SourceFileAiWidget|LighthouseReportAiWidget|
356
+ TimelineEventSummaryAiWidget|NetworkRequestGeneralHeadersAiWidget|SourceCodeAiWidget|SourceFilesListAiWidget;
350
357
 
351
358
  export type FunctionCallHandlerResult<Result> = {
352
359
  requiresApproval: true,
@@ -409,6 +416,13 @@ interface AidaFetchResult {
409
416
  rpcId?: Host.AidaClient.RpcGlobalId;
410
417
  }
411
418
 
419
+ class CrossOriginError extends Error {
420
+ constructor() {
421
+ super('Cross-origin navigation detected');
422
+ this.name = 'CrossOriginError';
423
+ }
424
+ }
425
+
412
426
  /**
413
427
  * AiAgent is a base class for implementing an interaction with AIDA
414
428
  * that involves one or more requests being sent to AIDA optionally
@@ -504,6 +518,14 @@ export abstract class AiAgent<T> {
504
518
  this.#facts.clear();
505
519
  }
506
520
 
521
+ /**
522
+ * Clears any subclass-specific caches. This is called when a run encounters
523
+ * an error (e.g., cross-origin navigation, abort, or execution error) to
524
+ * prevent unvalidated cached data from being replayed in subsequent runs.
525
+ */
526
+ clearCache(): void {
527
+ }
528
+
507
529
  popPendingMultimodalInput(): MultimodalInput|undefined {
508
530
  return undefined;
509
531
  }
@@ -778,11 +800,6 @@ export abstract class AiAgent<T> {
778
800
  },
779
801
  );
780
802
 
781
- if ('result' in result && result.result === 'BLOCKED_CROSS_ORIGIN') {
782
- yield this.#createErrorResponse(ErrorType.CROSS_ORIGIN);
783
- break;
784
- }
785
-
786
803
  if (options.signal?.aborted) {
787
804
  yield this.#createErrorResponse(ErrorType.ABORT);
788
805
  break;
@@ -808,6 +825,10 @@ export abstract class AiAgent<T> {
808
825
  };
809
826
  request = this.buildRequest(query, Host.AidaClient.Role.ROLE_UNSPECIFIED);
810
827
  } catch (err) {
828
+ if (err instanceof CrossOriginError) {
829
+ yield this.#createErrorResponse(ErrorType.CROSS_ORIGIN);
830
+ break;
831
+ }
811
832
  debugLog('Error handling function call', err);
812
833
  yield this.#createErrorResponse(ErrorType.UNKNOWN);
813
834
  break;
@@ -876,8 +897,19 @@ export abstract class AiAgent<T> {
876
897
  }
877
898
  }
878
899
 
900
+ const isOriginBlocked = (): boolean => {
901
+ const allowedOriginResult = this.#allowedOrigin?.();
902
+ return Boolean(allowedOriginResult && 'blocked' in allowedOriginResult);
903
+ };
904
+
879
905
  let result = await call.handler(args, options);
880
906
 
907
+ // Check 1: After first handler execution.
908
+ // Navigation could have occurred during the async handler execution.
909
+ if (isOriginBlocked()) {
910
+ throw new CrossOriginError();
911
+ }
912
+
881
913
  if ('requiresApproval' in result) {
882
914
  if (code) {
883
915
  yield {
@@ -926,17 +958,21 @@ export abstract class AiAgent<T> {
926
958
  // Re-check allowed origin after the approval await to prevent a TOCTOU (Time-of-Check
927
959
  // to Time-of-Use) race condition where the page might have navigated cross-origin
928
960
  // while the user was confirming the action.
929
- const allowedOriginResult = this.#allowedOrigin?.();
930
- if (allowedOriginResult && 'blocked' in allowedOriginResult) {
931
- return {
932
- result: 'BLOCKED_CROSS_ORIGIN',
933
- };
961
+ // Check 2: After waiting for user approval.
962
+ if (isOriginBlocked()) {
963
+ throw new CrossOriginError();
934
964
  }
935
965
 
936
966
  result = await call.handler(args, {
937
967
  ...options,
938
968
  approved: true,
939
969
  });
970
+
971
+ // Check 3: After second handler execution (approved run).
972
+ // Navigation could have occurred during the async execution of the approved action.
973
+ if (isOriginBlocked()) {
974
+ throw new CrossOriginError();
975
+ }
940
976
  }
941
977
 
942
978
  if ('result' in result) {
@@ -1012,6 +1048,7 @@ export abstract class AiAgent<T> {
1012
1048
 
1013
1049
  #createErrorResponse(error: ErrorType): ResponseData {
1014
1050
  this.#removeLastRunParts();
1051
+ this.clearCache();
1015
1052
  if (error !== ErrorType.ABORT) {
1016
1053
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiAssistanceError);
1017
1054
  }
@@ -91,7 +91,7 @@ Content:
91
91
  },
92
92
  {
93
93
  "name": "performanceRecordAndReload",
94
- "description": "Records a new performance trace. Use this to measure and debug performance metrics and Core Web Vitals like Largest Contentful Paint (LCP), Interaction to Next Paint (INP), and Cumulative Layout Shift (CLS).",
94
+ "description": "Records a new performance trace. Use this to measure, analyze, and debug page performance, general performance issues, performance metrics, and Core Web Vitals like Largest Contentful Paint (LCP), Interaction to Next Paint (INP), and Cumulative Layout Shift (CLS).",
95
95
  "parameters": {
96
96
  "type": 6,
97
97
  "description": "",
@@ -102,7 +102,7 @@ Content:
102
102
  },
103
103
  {
104
104
  "name": "runLighthouseAudits",
105
- "description": "Records a Lighthouse audit on the current page. Use this to debug accessibility, SEO, and best practices. (For performance metrics like LCP, use performanceRecordAndReload instead).",
105
+ "description": "Records a Lighthouse audit on the current page. Use this to debug accessibility, SEO, and best practices. (For any performance-related questions or performance issues, do NOT use this; use performanceRecordAndReload instead).",
106
106
  "parameters": {
107
107
  "type": 6,
108
108
  "description": "",
@@ -12,6 +12,7 @@ import * as Logs from '../../logs/logs.js';
12
12
  import * as NetworkTimeCalculator from '../../network_time_calculator/network_time_calculator.js';
13
13
  import type * as Trace from '../../trace/trace.js';
14
14
  import * as Workspace from '../../workspace/workspace.js';
15
+ import {isOpaqueOrigin} from '../AiOrigins.js';
15
16
  import {debugLog} from '../debug.js';
16
17
 
17
18
  import {AccessibilityContext} from './AccessibilityAgent.js';
@@ -20,7 +21,6 @@ import {
20
21
  AiAgent,
21
22
  type AllowedOriginResult,
22
23
  type ContextResponse,
23
- isOpaqueOrigin,
24
24
  type RequestOptions,
25
25
  } from './AiAgent.js';
26
26
  import {FileContext} from './FileAgent.js';
@@ -35,19 +35,26 @@ const lockedString = i18n.i18n.lockedString;
35
35
  * chrome_preambles.gcl). Sync local changes with the server-side.
36
36
  */
37
37
  const preamble = `
38
- You are a Web Development Assistant integrated into Chrome DevTools. Your tone is educational, supportive, and technically precise.
39
- You aim to help developers of all levels, prioritizing teaching web concepts as the primary entry point for any solution.
38
+ You are an advanced Web Development Assistant and AI routing agent integrated into Chrome DevTools. Your tone is educational, supportive, and technically precise. You aim to help developers of all levels, prioritizing teaching web concepts as the primary entry point for any solution.
39
+
40
+ Your role is to understand the user's query, identify the appropriate specialized agent to handle it, and select the relevant context from the page to assist that agent.
41
+
42
+ # Workflow
43
+ 1. **Analyze**: Understand the user's intent and what they are trying to achieve.
44
+ 2. **Classify**: Determine which specialized agent is best suited for the task (e.g., StylingAgent for CSS/styling issues, NetworkAgent for network requests, FileAgent for source files, PerformanceAgent for performance details, AccessibilityAgent for accessibility reports, or StorageAgent for storage issues).
45
+ 3. **Gather Context**: Identify what information the specialized agent will need. Proactively use your tools to find and select this context (e.g., finding the relevant DOM node, network request, file, or performance trace). Always try to select a single specific context before answering the question.
46
+ 4. **Delegate**: Once context is selected, hand over to the specialized agent. If you are unable to delegate or gather more information, provide a comprehensive guide on how to fix the issue using Chrome DevTools, explaining how and why, or suggest any panel/flow that may help.
40
47
 
41
48
  # Considerations
42
49
  * Determine what is the domain of the question - styling, network, sources, performance or other part of DevTools.
43
- * For questions about web performance metrics (e.g., LCP, INP, CLS) or page speed, use performanceRecordAndReload to record a performance trace.
44
- * Proactively try to gather additional data. If a select specific data can be selected, select one.
45
- * Always try select single specific context before answering the question.
50
+ * For questions about performance (e.g., general performance issues, page speed, performance metrics like LCP, INP, CLS), use performanceRecordAndReload to record a performance trace.
51
+ * Proactively try to gather additional data. If a specific piece of data can be selected, select it.
52
+ * Always try to select a single specific context before answering the question.
46
53
  * Avoid making assumptions without sufficient evidence, and always seek further clarification if needed.
47
54
  * When presenting solutions, clearly distinguish between the primary cause and contributing factors.
48
55
  * Please answer only if you are sure about the answer. Otherwise, explain why you're not able to answer.
49
56
  * 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.
50
- * You can suggest any panel or flow in Chrome DevTools that may help the user out
57
+ * You can suggest any panel or flow in Chrome DevTools that may help the user out.
51
58
 
52
59
  # Formatting Guidelines
53
60
  * Use Markdown for all code snippets.
@@ -55,7 +62,7 @@ You aim to help developers of all levels, prioritizing teaching web concepts as
55
62
  * **CRITICAL**: Use the precision of Strunk & White, the brevity of Hemingway, and the simple clarity of Vonnegut. Don't add repeated information, and keep the whole answer short.
56
63
 
57
64
  * **CRITICAL** If a tool returns an empty list, immediately pivot to the next logical tool (e.g., from sources to network).
58
- * **CRITICAL** Always exhaust all possible way to find and select context from different domains.
65
+ * **CRITICAL** Always exhaust all possible ways to find and select context from different domains.
59
66
  * **CRITICAL** NEVER write full Python programs - you should only write individual statements that invoke a single function from the provided library.
60
67
  * **CRITICAL** NEVER output text before a function call. Always do a function call first.
61
68
  * **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.
@@ -261,6 +268,7 @@ export class ContextSelectionAgent extends AiAgent<never> {
261
268
  const origin = allowedOriginResult.origin;
262
269
 
263
270
  const files: Array<{file: string, id: number | undefined}> = [];
271
+ const uiSourceCodes: Workspace.UISourceCode.UISourceCode[] = [];
264
272
  for (const file of ContextSelectionAgent.getUISourceCodes()) {
265
273
  const fileUrl = file.url();
266
274
  const fileOrigin = Common.ParsedURL.ParsedURL.extractOrigin(fileUrl);
@@ -273,10 +281,17 @@ export class ContextSelectionAgent extends AiAgent<never> {
273
281
  file: file.fullDisplayName(),
274
282
  id: ContextSelectionAgent.uiSourceCodeId.get(file),
275
283
  });
284
+ uiSourceCodes.push(file);
276
285
  }
277
286
 
278
287
  return {
279
288
  result: files,
289
+ widgets: [{
290
+ name: 'SOURCE_FILES_LIST',
291
+ data: {
292
+ uiSourceCodes,
293
+ },
294
+ }],
280
295
  };
281
296
  },
282
297
  });
@@ -342,7 +357,7 @@ export class ContextSelectionAgent extends AiAgent<never> {
342
357
 
343
358
  this.declareFunction('performanceRecordAndReload', {
344
359
  description:
345
- 'Records a new performance trace. Use this to measure and debug performance metrics and Core Web Vitals like Largest Contentful Paint (LCP), Interaction to Next Paint (INP), and Cumulative Layout Shift (CLS).',
360
+ 'Records a new performance trace. Use this to measure, analyze, and debug page performance, general performance issues, performance metrics, and Core Web Vitals like Largest Contentful Paint (LCP), Interaction to Next Paint (INP), and Cumulative Layout Shift (CLS).',
346
361
  parameters: {
347
362
  type: Host.AidaClient.ParametersTypes.OBJECT,
348
363
  description: '',
@@ -379,7 +394,7 @@ export class ContextSelectionAgent extends AiAgent<never> {
379
394
 
380
395
  this.declareFunction<{mode: LHSupportedRunMode}>('runLighthouseAudits', {
381
396
  description:
382
- 'Records a Lighthouse audit on the current page. Use this to debug accessibility, SEO, and best practices. (For performance metrics like LCP, use performanceRecordAndReload instead).',
397
+ 'Records a Lighthouse audit on the current page. Use this to debug accessibility, SEO, and best practices. (For any performance-related questions or performance issues, do NOT use this; use performanceRecordAndReload instead).',
383
398
  parameters: {
384
399
  type: Host.AidaClient.ParametersTypes.OBJECT,
385
400
  description: '',
@@ -105,7 +105,7 @@ export class ConversationSummaryContext extends ConversationContext<string> {
105
105
  this.#conversation = conversation;
106
106
  }
107
107
 
108
- getOrigin(): string {
108
+ override getURL(): string {
109
109
  return 'devtools://ai-assistance';
110
110
  }
111
111
 
@@ -83,8 +83,8 @@ export class FileContext extends ConversationContext<Workspace.UISourceCode.UISo
83
83
  this.#file = file;
84
84
  }
85
85
 
86
- override getOrigin(): string {
87
- return new URL(this.#file.url()).origin;
86
+ override getURL(): string {
87
+ return this.#file.url();
88
88
  }
89
89
 
90
90
  override getItem(): Workspace.UISourceCode.UISourceCode {
@@ -101,7 +101,7 @@ export class GreenDevContext extends ConversationContext<string> {
101
101
  this.#context = context;
102
102
  }
103
103
 
104
- getOrigin(): string {
104
+ override getURL(): string {
105
105
  return 'devtools://ai-assistance';
106
106
  }
107
107
 
@@ -2,7 +2,6 @@
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';
6
5
  import * as Host from '../../../core/host/host.js';
7
6
  import * as i18n from '../../../core/i18n/i18n.js';
8
7
  import * as Root from '../../../core/root/root.js';
@@ -111,13 +110,13 @@ export class RequestContext extends ConversationContext<SDK.NetworkRequest.Netwo
111
110
  }
112
111
 
113
112
  /**
114
- * Note: this is not the literal origin of the network request. This origin
113
+ * Note: this is not the literal origin of the network request. This URL
115
114
  * is used to determine when we should force the user to start a new AI
116
115
  * conversation when the context changes. We allow a single AI conversation to
117
116
  * inspect all network requests that were made for that given target URL.
118
117
  */
119
- override getOrigin(): string {
120
- return Common.ParsedURL.ParsedURL.extractOrigin(this.#request.documentURL);
118
+ override getURL(): string {
119
+ return this.#request.documentURL;
121
120
  }
122
121
 
123
122
  override getItem(): SDK.NetworkRequest.NetworkRequest {