chrome-devtools-frontend 1.0.1635648 → 1.0.1636056

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 (73) hide show
  1. package/.agents/skills/verification/SKILL.md +1 -1
  2. package/front_end/core/i18n/collect-ui-strings.js +3 -3
  3. package/front_end/core/root/Runtime.ts +5 -0
  4. package/front_end/core/sdk/SourceMapCache.ts +13 -11
  5. package/front_end/core/sdk/SourceMapManager.ts +7 -3
  6. package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +84 -22
  7. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +7 -33
  8. package/front_end/models/ai_assistance/agents/AiAgent.ts +11 -4
  9. package/front_end/models/ai_assistance/agents/GreenDevAgent.ts +68 -5
  10. package/front_end/models/ai_assistance/agents/GreenDevAgentAntigravityCliSocketClient.ts +53 -0
  11. package/front_end/models/ai_assistance/agents/GreenDevAgentGeminiCliSocketClient.ts +117 -0
  12. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +20 -2
  13. package/front_end/models/ai_assistance/ai_assistance.ts +4 -0
  14. package/front_end/models/greendev/Prototypes.ts +17 -5
  15. package/front_end/panels/ai_assistance/components/ChatMessage.ts +32 -1
  16. package/front_end/panels/network/NetworkLogView.ts +4 -1
  17. package/front_end/panels/settings/SettingsScreen.ts +3 -2
  18. package/front_end/panels/sources/PersistenceActions.ts +21 -7
  19. package/front_end/panels/timeline/TimelineDetailsView.ts +36 -0
  20. package/front_end/panels/whats_new/ReleaseNoteText.ts +9 -9
  21. package/front_end/panels/whats_new/resources/WNDT.md +9 -9
  22. package/front_end/third_party/chromium/README.chromium +1 -1
  23. package/front_end/third_party/puppeteer/README.chromium +2 -2
  24. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +8 -5
  25. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +35 -33
  26. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/TargetManager.d.ts.map +1 -1
  27. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/TargetManager.js +0 -1
  28. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/TargetManager.js.map +1 -1
  29. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/WebMCP.d.ts.map +1 -1
  30. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/WebMCP.js +0 -3
  31. package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/WebMCP.js.map +1 -1
  32. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts +8 -8
  33. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.d.ts.map +1 -1
  34. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js +8 -8
  35. package/front_end/third_party/puppeteer/package/lib/puppeteer/common/Debug.js.map +1 -1
  36. package/front_end/third_party/puppeteer/package/lib/puppeteer/generated/injected.d.ts +1 -1
  37. package/front_end/third_party/puppeteer/package/lib/puppeteer/generated/injected.d.ts.map +1 -1
  38. package/front_end/third_party/puppeteer/package/lib/puppeteer/generated/injected.js +1 -1
  39. package/front_end/third_party/puppeteer/package/lib/puppeteer/generated/injected.js.map +1 -1
  40. package/front_end/third_party/puppeteer/package/lib/puppeteer/injected/CustomQuerySelector.d.ts +2 -2
  41. package/front_end/third_party/puppeteer/package/lib/puppeteer/injected/CustomQuerySelector.d.ts.map +1 -1
  42. package/front_end/third_party/puppeteer/package/lib/puppeteer/injected/CustomQuerySelector.js +2 -1
  43. package/front_end/third_party/puppeteer/package/lib/puppeteer/injected/CustomQuerySelector.js.map +1 -1
  44. package/front_end/third_party/puppeteer/package/lib/puppeteer/injected/injected.d.ts +2 -7
  45. package/front_end/third_party/puppeteer/package/lib/puppeteer/injected/injected.d.ts.map +1 -1
  46. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/PuppeteerNode.d.ts +1 -1
  47. package/front_end/third_party/puppeteer/package/lib/puppeteer/node/PuppeteerNode.js +1 -1
  48. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +3 -3
  49. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +3 -3
  50. package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js.map +1 -1
  51. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/Mutex.d.ts +10 -7
  52. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/Mutex.d.ts.map +1 -1
  53. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/Mutex.js +16 -12
  54. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/Mutex.js.map +1 -1
  55. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
  56. package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
  57. package/front_end/third_party/puppeteer/package/lib/types.d.ts +8 -5
  58. package/front_end/third_party/puppeteer/package/package.json +5 -7
  59. package/front_end/third_party/puppeteer/package/src/cdp/TargetManager.ts +0 -1
  60. package/front_end/third_party/puppeteer/package/src/cdp/WebMCP.ts +0 -3
  61. package/front_end/third_party/puppeteer/package/src/common/Debug.ts +11 -11
  62. package/front_end/third_party/puppeteer/package/src/generated/injected.ts +1 -1
  63. package/front_end/third_party/puppeteer/package/src/injected/CustomQuerySelector.ts +3 -2
  64. package/front_end/third_party/puppeteer/package/src/node/PuppeteerNode.ts +1 -1
  65. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  66. package/front_end/third_party/puppeteer/package/src/util/Mutex.ts +17 -12
  67. package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
  68. package/front_end/ui/components/markdown_view/MarkdownLinksMap.ts +7 -0
  69. package/front_end/ui/legacy/Dialog.ts +1 -0
  70. package/front_end/ui/legacy/UIUtils.ts +4 -0
  71. package/front_end/ui/legacy/components/source_frame/FontView.ts +11 -3
  72. package/front_end/ui/visual_logging/KnownContextValues.ts +3 -0
  73. package/package.json +1 -12
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: devtools-verification
3
- description: MANDATORY: Activate this skill ANY TIME you need to build the project, run tests, or verify code health in DevTools. You MUST use this skill before executing commands like npm test, npm run build, autoninja, or linters, as it contains critical, repository-specific instructions on how to correctly format these commands, filter test runs, and interpret failures.
3
+ description: "MANDATORY: Activate this skill ANY TIME you need to build the project, run tests, or verify code health in DevTools. You MUST use this skill before executing commands like npm test, npm run build, autoninja, or linters, as it contains critical, repository-specific instructions on how to correctly format these commands, filter test runs, and interpret failures."
4
4
  ---
5
5
 
6
6
  # Instructions on how to verify your changes
@@ -2,7 +2,7 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
- import glob from 'glob';
5
+ import {globSync} from 'node:fs';
6
6
  import * as path from 'node:path';
7
7
  import yargs from 'yargs';
8
8
  import {hideBin} from 'yargs/helpers';
@@ -68,9 +68,9 @@ for (const directory of inputDirectories) {
68
68
  ...collectAllStringsInDir(directory),
69
69
  };
70
70
 
71
- const fs = glob.sync('**/*.{js,ts,gn}', {
71
+ const fs = globSync('**/*.{js,ts,gn}', {
72
72
  cwd: directory,
73
- ignore: IGNORED_PATH_COMPONENTS,
73
+ exclude: IGNORED_PATH_COMPONENTS,
74
74
  });
75
75
  files.push(...fs.map(f => path.join(directory, f)));
76
76
  }
@@ -625,6 +625,10 @@ interface UseGcaApi {
625
625
  enabled: boolean;
626
626
  }
627
627
 
628
+ interface DevToolsAIV2Architecture {
629
+ enabled: boolean;
630
+ }
631
+
628
632
  interface DevToolsProtocolMonitor {
629
633
  enabled: boolean;
630
634
  }
@@ -663,6 +667,7 @@ export type HostConfig = Platform.TypeScriptUtilities.RecursivePartial<{
663
667
  devToolsAiAssistanceAccessibilityAgent: HostConfigAiAssistanceAccessibilityAgent,
664
668
  devToolsAiAssistanceStorageAgent: HostConfigAiAssistanceStorageAgent,
665
669
  devToolsAiAssistanceV2: HostConfigAiAssistanceV2,
670
+ devToolsAIV2Architecture: DevToolsAIV2Architecture,
666
671
  devToolsAiCodeCompletion: HostConfigAiCodeCompletion,
667
672
  devToolsAiCodeGeneration: HostConfigAiCodeGeneration,
668
673
  devToolsAiCodeCompletionStyles: HostConfigAiCodeCompletionStyles,
@@ -2,6 +2,8 @@
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 type * as Platform from '../platform/platform.js';
6
+
5
7
  import type {DebugId, SourceMapV3} from './SourceMap.js';
6
8
 
7
9
  /** A thin wrapper around the Cache API to store source map JSONs keyed on Debug IDs */
@@ -28,14 +30,14 @@ export class SourceMapCache {
28
30
  this.#name = name;
29
31
  }
30
32
 
31
- async set(debugId: DebugId, sourceMap: SourceMapV3): Promise<void> {
33
+ async set(debugId: DebugId, securityOrigin: Platform.DevToolsPath.UrlString, sourceMap: SourceMapV3): Promise<void> {
32
34
  const cache = await this.#cache();
33
- await cache.put(SourceMapCache.#urlForDebugId(debugId), new Response(JSON.stringify(sourceMap)));
35
+ await cache.put(SourceMapCache.#urlForDebugId(debugId, securityOrigin), new Response(JSON.stringify(sourceMap)));
34
36
  }
35
37
 
36
- async get(debugId: DebugId): Promise<SourceMapV3|null> {
38
+ async get(debugId: DebugId, securityOrigin: Platform.DevToolsPath.UrlString): Promise<SourceMapV3|null> {
37
39
  const cache = await this.#cache();
38
- const response = await cache.match(SourceMapCache.#urlForDebugId(debugId));
40
+ const response = await cache.match(SourceMapCache.#urlForDebugId(debugId, securityOrigin));
39
41
  return await response?.json() ?? null;
40
42
  }
41
43
 
@@ -49,8 +51,8 @@ export class SourceMapCache {
49
51
  }
50
52
 
51
53
  /** The Cache API only allows URL as keys, so we construct a simple one. Given that we have our own cache, we have no risk of conflicting URLs */
52
- static #urlForDebugId(debugId: DebugId): string {
53
- return 'http://debug.id/' + encodeURIComponent(debugId);
54
+ static #urlForDebugId(debugId: DebugId, securityOrigin: Platform.DevToolsPath.UrlString): string {
55
+ return `http://debug.id/${encodeURIComponent(debugId)}?origin=${encodeURIComponent(securityOrigin)}`;
54
56
  }
55
57
 
56
58
  async disposeForTest(): Promise<void> {
@@ -59,14 +61,14 @@ export class SourceMapCache {
59
61
  }
60
62
 
61
63
  const IN_MEMORY_INSTANCE = new (class implements Pick<SourceMapCache, 'get'|'set'|'disposeForTest'> {
62
- readonly #cache = new Map<DebugId, SourceMapV3>();
64
+ readonly #cache = new Map<string, SourceMapV3>();
63
65
 
64
- async set(debugId: DebugId, sourceMap: SourceMapV3): Promise<void> {
65
- this.#cache.set(debugId, sourceMap);
66
+ async set(debugId: DebugId, securityOrigin: Platform.DevToolsPath.UrlString, sourceMap: SourceMapV3): Promise<void> {
67
+ this.#cache.set(`${debugId}|${securityOrigin}`, sourceMap);
66
68
  }
67
69
 
68
- async get(debugId: DebugId): Promise<SourceMapV3|null> {
69
- return this.#cache.get(debugId) ?? null;
70
+ async get(debugId: DebugId, securityOrigin: Platform.DevToolsPath.UrlString): Promise<SourceMapV3|null> {
71
+ return this.#cache.get(`${debugId}|${securityOrigin}`) ?? null;
70
72
  }
71
73
 
72
74
  async disposeForTest(): Promise<void> {
@@ -190,7 +190,9 @@ async function loadSourceMap(
190
190
  initiator: PageResourceLoadInitiator): Promise<SourceMapV3> {
191
191
  try {
192
192
  if (debugId) {
193
- const cachedSourceMap = await SourceMapCache.instance().get(debugId);
193
+ const securityOrigin = initiator.initiatorUrl ? Common.ParsedURL.ParsedURL.extractOrigin(initiator.initiatorUrl) :
194
+ Platform.DevToolsPath.EmptyUrlString;
195
+ const cachedSourceMap = await SourceMapCache.instance().get(debugId, securityOrigin);
194
196
  if (cachedSourceMap) {
195
197
  return cachedSourceMap;
196
198
  }
@@ -198,9 +200,11 @@ async function loadSourceMap(
198
200
 
199
201
  const {content} = await resourceLoader.loadResource(url, initiator);
200
202
  const sourceMap = parseSourceMap(content);
201
- if ('debugId' in sourceMap && sourceMap.debugId) {
203
+ if (debugId && 'debugId' in sourceMap && sourceMap.debugId === debugId) {
202
204
  // In case something goes wrong with updating the cache, we still want to use the source map.
203
- await SourceMapCache.instance().set(sourceMap.debugId as DebugId, sourceMap).catch();
205
+ const securityOrigin = initiator.initiatorUrl ? Common.ParsedURL.ParsedURL.extractOrigin(initiator.initiatorUrl) :
206
+ Platform.DevToolsPath.EmptyUrlString;
207
+ await SourceMapCache.instance().set(sourceMap.debugId as DebugId, securityOrigin, sourceMap).catch();
204
208
  }
205
209
  return sourceMap;
206
210
  } catch (cause) {
@@ -45,6 +45,9 @@ class GreenDevFloaty {
45
45
  #backendNodeId?: Protocol.DOM.BackendNodeId;
46
46
  #syncChannel: BroadcastChannel;
47
47
  #isFloatyWindow: boolean;
48
+ #socketClient:
49
+ AiAssistance.GreenDevAgentAntigravityCliSocketClient.GreenDevAgentAntigravityCliSocketClient|
50
+ AiAssistance.GreenDevAgentGeminiCliSocketClient.GreenDevAgentGeminiCliSocketClient;
48
51
 
49
52
  constructor(document: Document) {
50
53
  const params = new URLSearchParams(window.location.hash.substring(1));
@@ -57,6 +60,12 @@ class GreenDevFloaty {
57
60
  };
58
61
 
59
62
  this.#initFloatyMode(document);
63
+ if (Greendev.Prototypes.instance().isEnabled('beyondStylingAntigravity')) {
64
+ this.#socketClient =
65
+ new AiAssistance.GreenDevAgentAntigravityCliSocketClient.GreenDevAgentAntigravityCliSocketClient();
66
+ } else {
67
+ this.#socketClient = new AiAssistance.GreenDevAgentGeminiCliSocketClient.GreenDevAgentGeminiCliSocketClient();
68
+ }
60
69
  }
61
70
 
62
71
  #initFloatyMode(doc: Document): void {
@@ -269,12 +278,67 @@ class GreenDevFloaty {
269
278
  const query = this.#textField.value || this.#textField.placeholder;
270
279
  this.#textField.value = '';
271
280
 
272
- const useGreenDevAgent = Greendev.Prototypes.instance().isEnabled('beyondStyling');
281
+ const useAntigravity = Greendev.Prototypes.instance().isEnabled('beyondStylingAntigravity');
282
+ const useGemini = Greendev.Prototypes.instance().isEnabled('beyondStylingGemini');
283
+ const useGreenDevAgent = useAntigravity || useGemini;
284
+ const isAntigravity = useAntigravity;
273
285
 
274
286
  if (!this.#agent) {
275
287
  const aidaClient = new AidaClient();
276
288
  if (useGreenDevAgent) {
289
+ if (isAntigravity) {
290
+ this.#socketClient =
291
+ new AiAssistance.GreenDevAgentAntigravityCliSocketClient.GreenDevAgentAntigravityCliSocketClient();
292
+ } else {
293
+ this.#socketClient = new AiAssistance.GreenDevAgentGeminiCliSocketClient.GreenDevAgentGeminiCliSocketClient();
294
+ }
295
+
277
296
  this.#agent = new AiAssistance.GreenDevAgent.GreenDevAgent({aidaClient});
297
+ this.#agent.addEventListener(
298
+ AiAssistance.GreenDevAgent.Events.CLI_PROMPT_REQUESTED,
299
+ async (
300
+ event: Common.EventTarget.EventTargetEvent<
301
+ AiAssistance.GreenDevAgent.EventTypes[AiAssistance.GreenDevAgent.Events.CLI_PROMPT_REQUESTED]>) => {
302
+ const {prompt} = event.data;
303
+ const remoteName = isAntigravity ? 'Antigravity' : 'Gemini';
304
+ const aiContent =
305
+ this.#addMessageInternal(`Fix has been submitted to ${remoteName} CLI, please wait...`, false);
306
+
307
+ if (isAntigravity) {
308
+ let fullResponse = '';
309
+ void (
310
+ this.#socketClient as
311
+ AiAssistance.GreenDevAgentAntigravityCliSocketClient.GreenDevAgentAntigravityCliSocketClient)
312
+ .sendPrompt(prompt, (chunk: string) => {
313
+ fullResponse += chunk;
314
+ const responseText = `Antigravity CLI responds: ${fullResponse}`;
315
+ const markdown = new MarkdownView.MarkdownView.MarkdownView();
316
+ markdown.data = {
317
+ tokens: Marked.Marked.lexer(responseText),
318
+ };
319
+
320
+ const style = document.createElement('style');
321
+ style.textContent =
322
+ '.message { font-size: 1.0rem; } .message code { font-size: 1.0rem; font-family: \'Roboto Mono\', ' +
323
+ 'monospace; color: green; } .message ul { margin-left: 20px; }';
324
+ markdown.shadowRoot?.appendChild(style);
325
+
326
+ this.#updateAiMessage(aiContent, markdown, responseText);
327
+ });
328
+ } else {
329
+ const response =
330
+ await (this.#socketClient as
331
+ AiAssistance.GreenDevAgentGeminiCliSocketClient.GreenDevAgentGeminiCliSocketClient)
332
+ .sendPrompt(prompt);
333
+
334
+ const responseText = `Gemini CLI responds: ${response}`;
335
+ const markdown = new MarkdownView.MarkdownView.MarkdownView();
336
+ markdown.data = {
337
+ tokens: Marked.Marked.lexer(responseText),
338
+ };
339
+ this.#updateAiMessage(aiContent, markdown, responseText);
340
+ }
341
+ });
278
342
  } else {
279
343
  this.#agent = new AiAssistance.StylingAgent.StylingAgent({aidaClient});
280
344
  this.#nodeContext = new AiAssistance.StylingAgent.NodeContext(this.#node);
@@ -282,22 +346,7 @@ class GreenDevFloaty {
282
346
  }
283
347
 
284
348
  this.#addMessageInternal(query, true);
285
- this.#syncChannel.postMessage({
286
- type: 'new-message',
287
- text: query,
288
- isUser: true,
289
- sessionId: this.#backendNodeId,
290
- nodeDescription: document.querySelector('.green-dev-floaty-dialog-node-description')?.textContent,
291
- });
292
-
293
349
  const aiContent = this.#addMessageInternal('Thinking...', false);
294
- this.#syncChannel.postMessage({
295
- type: 'new-message',
296
- text: 'Thinking...',
297
- isUser: false,
298
- sessionId: this.#backendNodeId,
299
- nodeDescription: document.querySelector('.green-dev-floaty-dialog-node-description')?.textContent,
300
- });
301
350
 
302
351
  let agentFinished = false;
303
352
  let steps = 0;
@@ -433,7 +482,8 @@ ${axTree}`;
433
482
  markdown.data = {
434
483
  tokens: Marked.Marked.lexer(sanitizedText),
435
484
  };
436
- aiContent.append(markdown);
485
+ this.#updateAiMessage(aiContent, markdown, result.text);
486
+
437
487
  void new Promise(resolve => setTimeout(resolve, 0)).then(() => {
438
488
  const style = document.createElement('style');
439
489
  style.textContent =
@@ -456,15 +506,12 @@ ${axTree}`;
456
506
  }
457
507
  }
458
508
  });
459
- this.#syncChannel.postMessage(
460
- {type: 'update-last-message', text: result.text, sessionId: this.#backendNodeId});
461
509
  agentFinished = true;
462
510
  break;
463
511
  }
464
512
  case ResponseType.ERROR:
465
- aiContent.textContent = this.#formatError(result.error);
466
- this.#syncChannel.postMessage(
467
- {type: 'update-last-message', text: this.#formatError(result.error), sessionId: this.#backendNodeId});
513
+ this.#updateAiMessage(
514
+ aiContent, document.createTextNode(this.#formatError(result.error)), this.#formatError(result.error));
468
515
  agentFinished = true;
469
516
  break;
470
517
  case ResponseType.SIDE_EFFECT:
@@ -503,8 +550,23 @@ ${axTree}`;
503
550
  this.#chatContainer.appendChild(messageElement);
504
551
  this.#chatContainer.scrollTop = this.#chatContainer.scrollHeight;
505
552
  }
553
+
554
+ this.#syncChannel.postMessage({
555
+ type: 'new-message',
556
+ text,
557
+ isUser,
558
+ sessionId: this.#backendNodeId,
559
+ nodeDescription: document.querySelector('.green-dev-floaty-dialog-node-description')?.textContent,
560
+ });
561
+
506
562
  return content;
507
563
  }
564
+
565
+ #updateAiMessage(aiContentElement: HTMLDivElement, newContent: Node, newText: string): void {
566
+ aiContentElement.textContent = '';
567
+ aiContentElement.append(newContent);
568
+ this.#syncChannel.postMessage({type: 'update-last-message', text: newText, sessionId: this.#backendNodeId});
569
+ }
508
570
  }
509
571
 
510
572
  let greenDevFloatyInstance: GreenDevFloaty;
@@ -29,25 +29,6 @@ import {
29
29
  JavascriptExecutor
30
30
  } from './ExecuteJavascript.js';
31
31
 
32
- /**
33
- * The subset of computed CSS properties relevant to accessibility audits.
34
- * These are used to filter the Computed Styles widget in the AI Chat panel to keep it focused and minimal.
35
- */
36
- const ACCESSIBILITY_CSS_PROPERTIES = [
37
- 'color',
38
- 'background-color',
39
- 'display',
40
- 'visibility',
41
- 'opacity',
42
- 'clip',
43
- 'clip-path',
44
- 'font-size',
45
- 'font-weight',
46
- 'line-height',
47
- 'letter-spacing',
48
- 'text-transform',
49
- ];
50
-
51
32
  /**
52
33
  * WARNING: preamble defined in code is only used when userTier is
53
34
  * TESTERS. Otherwise, a server-side preamble is used (see
@@ -486,20 +467,13 @@ export class AccessibilityAgent extends AiAgent<LHModel.ReporterTypes.ReportJSON
486
467
  };
487
468
 
488
469
  const widgets: AiWidget[] = [];
489
- const cssModel = node.domModel().cssModel();
490
- const styles = await cssModel.getComputedStyle(node.id);
491
- const matchedStyles = await cssModel.getMatchedStyles(node.id);
492
- if (styles && matchedStyles) {
493
- widgets.push({
494
- name: 'COMPUTED_STYLES',
495
- data: {
496
- computedStyles: styles,
497
- backendNodeId: node.backendNodeId(),
498
- matchedCascade: matchedStyles,
499
- properties: ACCESSIBILITY_CSS_PROPERTIES,
500
- },
501
- });
502
- }
470
+ const snapshot = await node.takeSnapshot();
471
+ widgets.push({
472
+ name: 'DOM_TREE',
473
+ data: {
474
+ root: snapshot,
475
+ },
476
+ });
503
477
 
504
478
  return {
505
479
  result: JSON.stringify(result, null, 2),
@@ -330,10 +330,17 @@ export interface LighthouseReportAiWidget {
330
330
  };
331
331
  }
332
332
 
333
- // This type will grow as we add more widgets.
334
- export type AiWidget =
335
- ComputedStyleAiWidget|CoreVitalsAiWidget|StylePropertiesAiWidget|DomTreeAiWidget|PerformanceTraceAiWidget|
336
- PerfInsightAiWidget|TimelineRangeSummaryAiWidget|BottomUpTreeAiWidget|SourceFileAiWidget|LighthouseReportAiWidget;
333
+ export interface TimelineEventSummaryAiWidget {
334
+ name: 'TIMELINE_EVENT_SUMMARY';
335
+ data: {
336
+ event: Trace.Types.Events.Event,
337
+ parsedTrace: Trace.TraceModel.ParsedTrace,
338
+ };
339
+ }
340
+
341
+ export type AiWidget = ComputedStyleAiWidget|CoreVitalsAiWidget|StylePropertiesAiWidget|DomTreeAiWidget|
342
+ PerformanceTraceAiWidget|PerfInsightAiWidget|TimelineRangeSummaryAiWidget|BottomUpTreeAiWidget|SourceFileAiWidget|
343
+ LighthouseReportAiWidget|TimelineEventSummaryAiWidget;
337
344
 
338
345
  export type FunctionCallHandlerResult<Result> = {
339
346
  requiresApproval: true,
@@ -2,6 +2,7 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
+ import * as Common from '../../../core/common/common.js';
5
6
  import * as Host from '../../../core/host/host.js';
6
7
  import * as Root from '../../../core/root/root.js';
7
8
  import * as SDK from '../../../core/sdk/sdk.js';
@@ -53,9 +54,9 @@ additional context and resolve the user request.
53
54
  directly relevant to the user's problem. Do not get distracted by generic framework
54
55
  messages.
55
56
 
56
- 6. **Formulate a Hypothesis**: Based on your code investigation, explain the likely root
57
- cause to the user and suggest a concrete fix or next step. If you suspect an issue in a
58
- JavaScript function, point it out.
57
+ 6. **Formulate a Hypothesis**: Based on your code investigation, and if you have a promising
58
+ fix, ALWAYS apply it using the provided 'applyFix' function. If you can identify more than
59
+ one fix, ask the user which one to apply.
59
60
 
60
61
  ### Available Information
61
62
 
@@ -82,6 +83,7 @@ To help you further, you can call the following functions:
82
83
  request list.
83
84
  - 'getReactComponentProps': This function takes a uid (the backend DOM node id) and returns
84
85
  the React component props for that element.
86
+ - 'applyFix': This function accepts a code diff to apply to the code base.
85
87
 
86
88
  Stick to what you have evidence for and refrain from speculating on things you
87
89
  don't have concrete evidence for, such as CORS or Ad-blockers.
@@ -112,11 +114,38 @@ export class GreenDevContext extends ConversationContext<string> {
112
114
  }
113
115
  }
114
116
 
117
+ export const enum Events {
118
+ CLI_PROMPT_REQUESTED = 'CliPromptRequested',
119
+ }
120
+
121
+ export interface EventTypes {
122
+ [Events.CLI_PROMPT_REQUESTED]: {prompt: string};
123
+ }
124
+
125
+ export const enum RemoteEndpoint {
126
+ GEMINI_CLI_SOCKET = 'GeminiCliSocket',
127
+ ANTIGRAVITY_CLI_SOCKET = 'AntigravityCliSocket',
128
+ }
129
+
115
130
  /**
116
131
  * This agent is a general-purpose web page troubleshooting agent for GreenDev
117
132
  * prototypes.
118
133
  */
119
134
  export class GreenDevAgent extends AiAgent<string> {
135
+ #eventTarget = new Common.ObjectWrapper.ObjectWrapper<EventTypes>();
136
+
137
+ addEventListener<T extends keyof EventTypes>(
138
+ eventType: T, listener: (arg0: Common.EventTarget.EventTargetEvent<EventTypes[T]>) => void,
139
+ thisObject?: Object): Common.EventTarget.EventDescriptor<EventTypes, T> {
140
+ return this.#eventTarget.addEventListener(eventType, listener, thisObject);
141
+ }
142
+
143
+ removeEventListener<T extends keyof EventTypes>(
144
+ eventType: T, listener: (arg0: Common.EventTarget.EventTargetEvent<EventTypes[T]>) => void,
145
+ thisObject?: Object): void {
146
+ this.#eventTarget.removeEventListener(eventType, listener, thisObject);
147
+ }
148
+
120
149
  constructor(options: AgentOptions) {
121
150
  super(options);
122
151
 
@@ -327,6 +356,38 @@ export class GreenDevAgent extends AiAgent<string> {
327
356
  };
328
357
  },
329
358
  });
359
+
360
+ this.declareFunction<{
361
+ codeSuggestionDiff: string,
362
+ }>('applyFix', {
363
+ description: 'Apply a code fix for the user to review.',
364
+ parameters: {
365
+ type: Host.AidaClient.ParametersTypes.OBJECT,
366
+ description: '',
367
+ nullable: false,
368
+ properties: {
369
+ codeSuggestionDiff: {
370
+ type: Host.AidaClient.ParametersTypes.STRING,
371
+ description: 'The diff of the suggested code change.',
372
+ nullable: false,
373
+ },
374
+ },
375
+ required: ['codeSuggestionDiff'],
376
+ },
377
+ handler: async (params: {codeSuggestionDiff: string}) => {
378
+ const result = await this.applyFix(params.codeSuggestionDiff);
379
+ return {
380
+ result,
381
+ };
382
+ },
383
+ });
384
+ }
385
+
386
+ async applyFix(codeSuggestionDiff: string): Promise<string> {
387
+ console.warn('[GreenDevAgent] applyFix called with:', codeSuggestionDiff);
388
+ this.#eventTarget.dispatchEventToListeners(
389
+ Events.CLI_PROMPT_REQUESTED, {prompt: `Apply this diff:\n${codeSuggestionDiff}`});
390
+ return 'The fix suggestion has been submitted.';
330
391
  }
331
392
 
332
393
  override preamble = preamble;
@@ -377,8 +438,10 @@ export class GreenDevAgent extends AiAgent<string> {
377
438
  }
378
439
 
379
440
  static isEnabled(): boolean {
380
- console.warn('BeyondStyling prototype is enabled:', Greendev.Prototypes.instance().isEnabled('beyondStyling'));
381
- return Greendev.Prototypes.instance().isEnabled('beyondStyling');
441
+ const isGeminiEnabled = Greendev.Prototypes.instance().isEnabled('beyondStylingGemini');
442
+ const isAntigravityEnabled = Greendev.Prototypes.instance().isEnabled('beyondStylingAntigravity');
443
+ console.warn('BeyondStyling prototype is enabled:', isGeminiEnabled || isAntigravityEnabled);
444
+ return isGeminiEnabled || isAntigravityEnabled;
382
445
  }
383
446
 
384
447
  static formatConsoleMessage(message: SDK.ConsoleModel.ConsoleMessage, index: number): string {
@@ -0,0 +1,53 @@
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
+ export class GreenDevAgentAntigravityCliSocketClient {
6
+ #websocket: WebSocket;
7
+ sessionReady: Promise<void>;
8
+ #sessionReadyResolve: ((value: void|PromiseLike<void>) => void)|null = null;
9
+
10
+ constructor() {
11
+ this.sessionReady = new Promise(resolve => {
12
+ this.#sessionReadyResolve = resolve;
13
+ });
14
+ this.#websocket = new WebSocket('ws://localhost:5566');
15
+ this.#websocket.onopen = this.#onOpen.bind(this);
16
+ this.#websocket.onmessage = this.#onMessage.bind(this);
17
+ this.#websocket.onclose = this.#onClose.bind(this);
18
+ this.#websocket.onerror = this.#onError.bind(this);
19
+ }
20
+
21
+ #onOpen(): void {
22
+ console.warn('WebSocket connected (Antigravity).');
23
+ if (this.#sessionReadyResolve) {
24
+ this.#sessionReadyResolve();
25
+ this.#sessionReadyResolve = null;
26
+ }
27
+ }
28
+
29
+ #onMessage(event: MessageEvent): void {
30
+ console.warn('Antigravity WebSocket message received:', event.data);
31
+ if (this.#onChunkCallback) {
32
+ this.#onChunkCallback(event.data);
33
+ }
34
+ }
35
+
36
+ #onClose(): void {
37
+ console.warn('WebSocket disconnected (Antigravity).');
38
+ }
39
+
40
+ #onError(error: Event): void {
41
+ console.error('WebSocket error (Antigravity):', error);
42
+ }
43
+
44
+ #onChunkCallback: ((chunk: string) => void)|null = null;
45
+
46
+ sendPrompt(promptText: string, onChunk: (chunk: string) => void): Promise<void> {
47
+ this.#onChunkCallback = onChunk;
48
+
49
+ console.warn(`Sending Antigravity prompt: "${promptText}"`);
50
+ this.#websocket.send(promptText);
51
+ return Promise.resolve();
52
+ }
53
+ }
@@ -0,0 +1,117 @@
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
+ export class GreenDevAgentGeminiCliSocketClient {
6
+ #websocket: WebSocket;
7
+ #sessionId?: string;
8
+ #activeMessage = '';
9
+ #messageLog: string[] = [];
10
+ #promptResolve: ((response: string) => void)|null = null;
11
+ sessionReady: Promise<void>;
12
+ #sessionReadyResolve: ((value: void|PromiseLike<void>) => void)|null = null;
13
+
14
+ constructor() {
15
+ this.sessionReady = new Promise(resolve => {
16
+ this.#sessionReadyResolve = resolve;
17
+ });
18
+ this.#websocket = new WebSocket('ws://localhost:6655');
19
+ this.#websocket.onopen = this.#onOpen.bind(this);
20
+ this.#websocket.onmessage = this.#onMessage.bind(this);
21
+ this.#websocket.onclose = this.#onClose.bind(this);
22
+ this.#websocket.onerror = this.#onError.bind(this);
23
+ }
24
+
25
+ #onOpen(): void {
26
+ console.warn('WebSocket connected.');
27
+ this.#websocket.send(
28
+ JSON.stringify({jsonrpc: '2.0', method: 'session/new', params: {cwd: '.', mcpServers: []}, id: 14}));
29
+ }
30
+
31
+ #onMessage(event: MessageEvent): void {
32
+ this.#messageLog.push(event.data);
33
+
34
+ try {
35
+ const data = JSON.parse(event.data);
36
+
37
+ if (data?.result?.sessionId && !this.#sessionId) {
38
+ this.#sessionId = data.result.sessionId;
39
+ console.warn(`Successfully created new session with ID: ${this.#sessionId}`);
40
+ if (this.#sessionReadyResolve) {
41
+ this.#sessionReadyResolve();
42
+ this.#sessionReadyResolve = null;
43
+ }
44
+ }
45
+
46
+ const update = data?.params?.update;
47
+ if (update?.sessionUpdate === 'agent_message_chunk') {
48
+ this.#activeMessage += update.content?.text || '';
49
+ }
50
+
51
+ if (data?.result?.stopReason) {
52
+ if (this.#activeMessage) {
53
+ console.warn(this.#activeMessage);
54
+ }
55
+ console.warn(`Stop Reason: ${data.result.stopReason}`, this.#messageLog);
56
+
57
+ if (this.#promptResolve) {
58
+ this.#promptResolve(this.#activeMessage);
59
+ this.#promptResolve = null;
60
+ }
61
+
62
+ this.#activeMessage = '';
63
+ this.#messageLog = [];
64
+ } else if (data?.method === 'session/request_permission') {
65
+ // TODO(finnur): Needs augmenting once I turn off YOLO in the bridge.
66
+ this.#websocket.send(JSON.stringify({
67
+ jsonrpc: '2.0',
68
+ id: data.id,
69
+ result: {
70
+ outcome: {
71
+ selected: {
72
+ optionId: 'proceed_always',
73
+ },
74
+ },
75
+ },
76
+ }));
77
+ }
78
+ } catch (e) {
79
+ // Log the raw data if it's not valid JSON.
80
+ console.error('Failed to parse WebSocket message or process data:', event.data, e);
81
+ }
82
+ }
83
+
84
+ #onClose(): void {
85
+ console.warn('WebSocket disconnected.');
86
+ }
87
+
88
+ #onError(error: Event): void {
89
+ console.error('WebSocket error:', error);
90
+ }
91
+
92
+ sendPrompt(promptText: string): Promise<string> {
93
+ return new Promise((resolve, reject) => {
94
+ if (this.#promptResolve) {
95
+ reject(new Error('Another prompt is already in progress.'));
96
+ return;
97
+ }
98
+ if (!this.#sessionId) {
99
+ reject(new Error('Cannot send prompt without a session ID.'));
100
+ return;
101
+ }
102
+ this.#promptResolve = resolve;
103
+
104
+ console.warn(`Sending prompt: "${promptText}"`);
105
+ console.warn('Thinking...');
106
+ this.#websocket.send(JSON.stringify({
107
+ jsonrpc: '2.0',
108
+ method: 'session/prompt',
109
+ params: {
110
+ prompt: [{type: 'text', text: promptText}],
111
+ sessionId: this.#sessionId,
112
+ },
113
+ id: 15,
114
+ }));
115
+ });
116
+ }
117
+ }