chrome-devtools-frontend 1.0.1605219 → 1.0.1605390

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 (36) hide show
  1. package/front_end/Images/src/dots-circle.svg +10 -0
  2. package/front_end/core/sdk/WebMCPModel.ts +57 -2
  3. package/front_end/models/ai_assistance/agents/AiAgent.ts +9 -1
  4. package/front_end/models/ai_assistance/agents/ConversationSummaryAgent.ts +10 -9
  5. package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +301 -0
  6. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +19 -8
  7. package/front_end/models/ai_assistance/agents/StylingAgent.ts +12 -264
  8. package/front_end/panels/ai_assistance/components/ChatMessage.ts +132 -48
  9. package/front_end/panels/ai_assistance/components/ChatView.ts +2 -13
  10. package/front_end/panels/ai_assistance/components/ExportForAgentsDialog.ts +1 -1
  11. package/front_end/panels/ai_assistance/components/chatMessage.css +43 -1
  12. package/front_end/panels/ai_assistance/components/chatView.css +0 -8
  13. package/front_end/panels/application/WebMCPView.ts +363 -13
  14. package/front_end/panels/application/webMCPView.css +25 -0
  15. package/front_end/panels/elements/ElementsTreeOutline.ts +87 -0
  16. package/front_end/panels/elements/elementsTreeOutline.css +21 -0
  17. package/front_end/panels/profiler/HeapDetachedElementsView.ts +0 -4
  18. package/front_end/panels/profiler/HeapProfileView.ts +0 -4
  19. package/front_end/panels/profiler/HeapProfilerPanel.ts +4 -13
  20. package/front_end/panels/profiler/HeapSnapshotView.ts +12 -16
  21. package/front_end/panels/profiler/ProfileHeader.ts +0 -11
  22. package/front_end/panels/profiler/ProfileTypeRegistry.ts +4 -12
  23. package/front_end/panels/profiler/ProfileView.ts +1 -6
  24. package/front_end/panels/profiler/ProfilesPanel.ts +60 -7
  25. package/front_end/panels/timeline/EventsTimelineTreeView.ts +1 -1
  26. package/front_end/panels/timeline/ThirdPartyTreeView.ts +2 -2
  27. package/front_end/panels/timeline/TimelinePanel.ts +11 -0
  28. package/front_end/panels/timeline/TimelineTreeView.ts +136 -47
  29. package/front_end/panels/timeline/components/CWVMetrics.ts +18 -2
  30. package/front_end/panels/timeline/timeline-meta.ts +11 -0
  31. package/front_end/panels/timeline/utils/Helpers.ts +5 -0
  32. package/front_end/ui/legacy/FilterBar.ts +2 -0
  33. package/front_end/ui/legacy/SplitWidget.ts +33 -27
  34. package/front_end/ui/legacy/Widget.ts +36 -28
  35. package/front_end/ui/visual_logging/KnownContextValues.ts +6 -0
  36. package/package.json +1 -1
@@ -0,0 +1,10 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <g clip-path="url(#clip0_367_65863)">
3
+ <path d="M5.2 8.875C5.44306 8.875 5.64722 8.79236 5.8125 8.62708C5.9875 8.45208 6.075 8.24306 6.075 8C6.075 7.75694 5.9875 7.55278 5.8125 7.3875C5.64722 7.2125 5.44306 7.125 5.2 7.125C4.95694 7.125 4.74792 7.2125 4.57292 7.3875C4.40764 7.55278 4.325 7.75694 4.325 8C4.325 8.24306 4.40764 8.45208 4.57292 8.62708C4.74792 8.79236 4.95694 8.875 5.2 8.875ZM8 8.875C8.24306 8.875 8.44722 8.79236 8.6125 8.62708C8.7875 8.45208 8.875 8.24306 8.875 8C8.875 7.75694 8.7875 7.55278 8.6125 7.3875C8.44722 7.2125 8.24306 7.125 8 7.125C7.75694 7.125 7.54792 7.2125 7.37292 7.3875C7.20764 7.55278 7.125 7.75694 7.125 8C7.125 8.24306 7.20764 8.45208 7.37292 8.62708C7.54792 8.79236 7.75694 8.875 8 8.875ZM10.8 8.875C11.0431 8.875 11.2472 8.79236 11.4125 8.62708C11.5875 8.45208 11.675 8.24306 11.675 8C11.675 7.75694 11.5875 7.55278 11.4125 7.3875C11.2472 7.2125 11.0431 7.125 10.8 7.125C10.5569 7.125 10.3479 7.2125 10.1729 7.3875C10.0076 7.55278 9.925 7.75694 9.925 8C9.925 8.24306 10.0076 8.45208 10.1729 8.62708C10.3479 8.79236 10.5569 8.875 10.8 8.875ZM8 13.6C7.23194 13.6 6.50764 13.4542 5.82708 13.1625C5.14653 12.8708 4.54861 12.4722 4.03333 11.9667C3.52778 11.4514 3.12917 10.8535 2.8375 10.1729C2.54583 9.49236 2.4 8.76806 2.4 8C2.4 7.22222 2.54583 6.49792 2.8375 5.82708C3.12917 5.14653 3.52778 4.55347 4.03333 4.04792C4.54861 3.53264 5.14653 3.12917 5.82708 2.8375C6.50764 2.54583 7.23194 2.4 8 2.4C8.77778 2.4 9.50208 2.54583 10.1729 2.8375C10.8535 3.12917 11.4465 3.53264 11.9521 4.04792C12.4674 4.55347 12.8708 5.14653 13.1625 5.82708C13.4542 6.49792 13.6 7.22222 13.6 8C13.6 8.76806 13.4542 9.49236 13.1625 10.1729C12.8708 10.8535 12.4674 11.4514 11.9521 11.9667C11.4465 12.4722 10.8535 12.8708 10.1729 13.1625C9.50208 13.4542 8.77778 13.6 8 13.6ZM8 12.55C9.26389 12.55 10.3382 12.1076 11.2229 11.2229C12.1076 10.3382 12.55 9.26389 12.55 8C12.55 6.73611 12.1076 5.66181 11.2229 4.77708C10.3382 3.89236 9.26389 3.45 8 3.45C6.73611 3.45 5.66181 3.89236 4.77708 4.77708C3.89236 5.66181 3.45 6.73611 3.45 8C3.45 9.26389 3.89236 10.3382 4.77708 11.2229C5.66181 12.1076 6.73611 12.55 8 12.55Z" fill="#474747"/>
4
+ </g>
5
+ <defs>
6
+ <clipPath id="clip0_367_65863">
7
+ <rect width="16" height="16" fill="white"/>
8
+ </clipPath>
9
+ </defs>
10
+ </svg>
@@ -13,15 +13,32 @@ import {Capability, type Target} from './Target.js';
13
13
  export const enum Events {
14
14
  TOOLS_ADDED = 'ToolsAdded',
15
15
  TOOLS_REMOVED = 'ToolsRemoved',
16
+ TOOL_INVOKED = 'ToolInvoked',
17
+ TOOL_RESPONDED = 'ToolResponded',
18
+ }
19
+
20
+ export interface Call {
21
+ invocationId: string;
22
+ input: string;
23
+ tool: Protocol.WebMCP.Tool;
24
+ result?: {
25
+ status: Protocol.WebMCP.InvocationStatus,
26
+ output?: unknown,
27
+ errorText?: string,
28
+ exception?: Protocol.Runtime.RemoteObject,
29
+ };
16
30
  }
17
31
 
18
32
  export interface EventTypes {
19
33
  [Events.TOOLS_ADDED]: ReadonlyArray<Readonly<Protocol.WebMCP.Tool>>;
20
34
  [Events.TOOLS_REMOVED]: ReadonlyArray<Readonly<Protocol.WebMCP.Tool>>;
35
+ [Events.TOOL_INVOKED]: Call;
36
+ [Events.TOOL_RESPONDED]: Call;
21
37
  }
22
38
 
23
39
  export class WebMCPModel extends SDKModel<EventTypes> {
24
40
  readonly #tools = new Map<Protocol.Page.FrameId, Map<string, Protocol.WebMCP.Tool>>();
41
+ readonly #calls = new Map<string, Call>();
25
42
  readonly agent: ProtocolProxyApi.WebMCPApi;
26
43
  #enabled = false;
27
44
 
@@ -43,6 +60,14 @@ export class WebMCPModel extends SDKModel<EventTypes> {
43
60
  return this.#tools.values().flatMap(toolMap => toolMap.values());
44
61
  }
45
62
 
63
+ get toolCalls(): Call[] {
64
+ return [...this.#calls.values()];
65
+ }
66
+
67
+ clearCalls(): void {
68
+ this.#calls.clear();
69
+ }
70
+
46
71
  async enable(): Promise<void> {
47
72
  if (this.#enabled) {
48
73
  return;
@@ -78,6 +103,34 @@ export class WebMCPModel extends SDKModel<EventTypes> {
78
103
  }
79
104
  this.dispatchEventToListeners(Events.TOOLS_ADDED, tools);
80
105
  }
106
+
107
+ toolInvoked(params: Protocol.WebMCP.ToolInvokedEvent): void {
108
+ const tool = this.#tools.get(params.frameId)?.get(params.toolName);
109
+ if (!tool) {
110
+ return;
111
+ }
112
+ const call: Call = {
113
+ invocationId: params.invocationId,
114
+ input: params.input,
115
+ tool,
116
+ };
117
+ this.#calls.set(params.invocationId, call);
118
+ this.dispatchEventToListeners(Events.TOOL_INVOKED, call);
119
+ }
120
+
121
+ toolResponded(params: Protocol.WebMCP.ToolRespondedEvent): void {
122
+ const call = this.#calls.get(params.invocationId);
123
+ if (!call) {
124
+ return;
125
+ }
126
+ call.result = {
127
+ status: params.status,
128
+ output: params.output,
129
+ errorText: params.errorText,
130
+ exception: params.exception,
131
+ };
132
+ this.dispatchEventToListeners(Events.TOOL_RESPONDED, call);
133
+ }
81
134
  }
82
135
 
83
136
  class WebMCPDispatcher implements ProtocolProxyApi.WebMCPDispatcher {
@@ -94,10 +147,12 @@ class WebMCPDispatcher implements ProtocolProxyApi.WebMCPDispatcher {
94
147
  this.#model.onToolsRemoved(params.tools);
95
148
  }
96
149
 
97
- toolInvoked(): void {
150
+ toolInvoked(params: Protocol.WebMCP.ToolInvokedEvent): void {
151
+ this.#model.toolInvoked(params);
98
152
  }
99
153
 
100
- toolResponded(): void {
154
+ toolResponded(params: Protocol.WebMCP.ToolRespondedEvent): void {
155
+ this.#model.toolResponded(params);
101
156
  }
102
157
  }
103
158
 
@@ -290,9 +290,17 @@ export interface TimelineRangeSummaryAiWidget {
290
290
  };
291
291
  }
292
292
 
293
+ export interface BottomUpTreeAiWidget {
294
+ name: 'BOTTOM_UP_TREE';
295
+ data: {
296
+ bounds: Trace.Types.Timing.TraceWindowMicro,
297
+ parsedTrace: Trace.TraceModel.ParsedTrace,
298
+ };
299
+ }
300
+
293
301
  // This type will grow as we add more widgets.
294
302
  export type AiWidget = ComputedStyleAiWidget|CoreVitalsAiWidget|StylePropertiesAiWidget|DomTreeAiWidget|
295
- PerformanceTraceAiWidget|LcpBreakdownAiWidget|TimelineRangeSummaryAiWidget;
303
+ PerformanceTraceAiWidget|LcpBreakdownAiWidget|TimelineRangeSummaryAiWidget|BottomUpTreeAiWidget;
296
304
 
297
305
  export type FunctionCallHandlerResult<Result> = {
298
306
  requiresApproval: true,
@@ -8,30 +8,31 @@ import * as Root from '../../../core/root/root.js';
8
8
  import {AiAgent, type ContextResponse, ConversationContext, type RequestOptions, ResponseType} from './AiAgent.js';
9
9
 
10
10
  const preamble = `### Role
11
- You are a Performance Expert. Your task is to extract a diagnostic narrative from raw DevTools logs and present it as a self-contained, actionable Markdown summary. You provide high-density technical analysis without conversational fluff.
11
+ You are a Conversation Summarizer. Your task is to take a transcript of a conversation between a user and a DevTools AI agent and produce a succinct, actionable Markdown summary. This summary will be used to help apply fixes in an IDE, so it must capture all relevant technical details, findings, and proposed code changes without any conversational fluff.
12
12
 
13
13
  ### Critical Constraints
14
14
  - **Persona:** Do not mention that you are an AI or refer to yourself in the third person.
15
15
  - **Domain Scope:** Do not provide answers on non-web-development topics (e.g., legal, financial, medical, or personal advice).
16
- - **Sensitive Topics:** If the conversation history touches on sensitive topics (religion, race, politics, sexuality, gender, etc.), respond only with: "My expertise is limited to website performance analysis. I cannot provide information on that topic."
17
- - **Data Portability:** The recipient of this summary does NOT have access to the raw logs.
16
+ - **Sensitive Topics:** If the conversation history touches on sensitive topics (religion, race, politics, sexuality, gender, etc.), respond only with: "My expertise is limited to summarizing DevTools AI conversations. I cannot provide information on that topic."
17
+ - **Data Portability:** The recipient of this summary does NOT have access to the raw logs or the full conversation transcript.
18
18
  - **No UIDs/Internal IDs:** Never refer to elements by internal IDs (e.g., \`uid=123\`).
19
19
  - **Standard Selectors:** Identify elements using HTML tags, classes, or IDs (e.g., \`button.submit-form\`).
20
20
  - **No Metadata:** Remove internal constants like \`NAVIGATION_0\` or \`INSIGHT_0\`.
21
- - **No Process Narration:** Do not describe internal "thinking" or API calls. Skip phrases like "The agent investigated..." or "The user then asked...". Jump straight to the findings.
21
+ - **No Process Narration:** Do not describe internal "thinking" or API calls. Skip phrases like "The agent investigated..." or "The user then asked...". Jump straight to the findings and their technical context.
22
+ - **Suggest, Don't Prescribe:** When summarizing code changes made during the session (e.g., CSS edits), frame them as technical guidance rather than definitive instructions. Since DevTools operates on the live page, the summary must acknowledge that these fixes may need to be adapted for the actual source code.
22
23
 
23
24
  ### Objectives
24
25
  1. **Identify Intent:** Define the core technical goal of the session.
25
- 2. **Value-Only Diagnostics:** List only the technical data points discovered. Omit steps that didn't yield a result.
26
- 3. **Focus on Code Intent:** When code is executed in the logs, summarize the **purpose** and the **result**. Do not include the raw JavaScript unless it is a specific fix for the user to implement.
27
- 4. **Actionable Recommendations:** Provide specific code/strategy fixes based on the findings.
26
+ 2. **Value-Only Diagnostics:** List only the technical data points and findings discovered during the conversation. Omit steps that didn't yield a result.
27
+ 3. **Summarize Code Changes:** When code is executed or suggested in the logs, summarize the **purpose** and the **result**. Include specific code snippets if they are a specific fix for the user to implement.
28
+ 4. **Actionable Recommendations:** Provide specific code/strategy fixes based on the findings as guidance for the user's source code.
28
29
 
29
30
  ### Formatting Rules
30
31
  - **Header:** Use ## [Brief Topic Title]
31
- - **Context:** Describe the target element/page and the core metric being analyzed.
32
+ - **Context:** Describe the target element/page and the core issue or technical goal being analyzed.
32
33
  - **Diagnostics:** A bulleted list of technical findings.
33
34
  - **Tabular Data:** Use a **Markdown Table** for any lists of URLs, metrics, or comparison data.
34
- - **Code Fixes:** Use fenced code blocks for suggested CSS/JS optimizations.
35
+ - **Code Fixes:** Use fenced code blocks for suggested code optimizations. Use language that frames them as illustrative examples or context (e.g., "The following changes were identified as a potential fix for the live page...") rather than strict instructions.
35
36
 
36
37
  ---
37
38
 
@@ -0,0 +1,301 @@
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 Host from '../../../core/host/host.js';
6
+ import * as i18n from '../../../core/i18n/i18n.js';
7
+ import * as Platform from '../../../core/platform/platform.js';
8
+ import * as Root from '../../../core/root/root.js';
9
+ import * as SDK from '../../../core/sdk/sdk.js';
10
+ import type {ChangeManager} from '../ChangeManager.js';
11
+ import {debugLog} from '../debug.js';
12
+ import {EvaluateAction, formatError, SideEffectError} from '../EvaluateAction.js';
13
+ import {FREESTYLER_WORLD_NAME} from '../injected.js';
14
+
15
+ import type {FunctionCallHandlerResult, FunctionDeclaration, FunctionHandlerOptions} from './AiAgent.js';
16
+
17
+ const lockedString = i18n.i18n.lockedString;
18
+
19
+ export function executeJavaScriptFunction(executor: JavascriptExecutor): FunctionDeclaration<
20
+ {
21
+ title: string,
22
+ explanation: string,
23
+ code: string,
24
+ },
25
+ unknown> {
26
+ return {
27
+ description:
28
+ 'This function allows you to run JavaScript code on the inspected page to access the element styles and page content.\nCall this function to gather additional information or modify the page state. Call this function enough times to investigate the user request.',
29
+ parameters: {
30
+ type: Host.AidaClient.ParametersTypes.OBJECT,
31
+ description: '',
32
+ nullable: false,
33
+ properties: {
34
+ code: {
35
+ type: Host.AidaClient.ParametersTypes.STRING,
36
+ description:
37
+ `JavaScript code snippet to run on the inspected page. Make sure the code is formatted for readability.
38
+
39
+ # Instructions
40
+
41
+ * To return data, define a top-level \`data\` variable and populate it with data you want to get. Only JSON-serializable objects can be assigned to \`data\`.
42
+ * If you modify styles on an element, ALWAYS call the pre-defined global \`async setElementStyles(el: Element, styles: object)\` function. This function is an internal mechanism for you and should never be presented as a command/advice to the user.
43
+ * **CRITICAL** Only get styles that might be relevant to the user request.
44
+ * **CRITICAL** Never assume a selector for the elements unless you verified your knowledge.
45
+ * **CRITICAL** Consider that \`data\` variable from the previous function calls are not available in a new function call.
46
+
47
+ For example, the code to change element styles:
48
+
49
+ \`\`\`
50
+ await setElementStyles($0, {
51
+ color: 'blue',
52
+ });
53
+ \`\`\`
54
+
55
+ For example, the code to get overlapping elements:
56
+
57
+ \`\`\`
58
+ const data = {
59
+ overlappingElements: Array.from(document.querySelectorAll('*'))
60
+ .filter(el => {
61
+ const rect = el.getBoundingClientRect();
62
+ const popupRect = $0.getBoundingClientRect();
63
+ return (
64
+ el !== $0 &&
65
+ rect.left < popupRect.right &&
66
+ rect.right > popupRect.left &&
67
+ rect.top < popupRect.bottom &&
68
+ rect.bottom > popupRect.top
69
+ );
70
+ })
71
+ .map(el => ({
72
+ tagName: el.tagName,
73
+ id: el.id,
74
+ className: el.className,
75
+ zIndex: window.getComputedStyle(el)['z-index']
76
+ }))
77
+ };
78
+ \`\`\`
79
+ `,
80
+ },
81
+ explanation: {
82
+ type: Host.AidaClient.ParametersTypes.STRING,
83
+ description: 'Explain why you want to run this code',
84
+ },
85
+ title: {
86
+ type: Host.AidaClient.ParametersTypes.STRING,
87
+ description: 'Provide a summary of what the code does. For example, "Checking related element styles".',
88
+ },
89
+ },
90
+ required: ['code', 'explanation', 'title']
91
+ },
92
+ displayInfoFromArgs: params => {
93
+ return {
94
+ title: params.title,
95
+ thought: params.explanation,
96
+ action: params.code,
97
+ };
98
+ },
99
+ handler: async (
100
+ params,
101
+ options,
102
+ ) => {
103
+ return await executor.executeAction(params.code, options);
104
+ },
105
+ };
106
+ }
107
+
108
+ export async function executeJsCode(
109
+ functionDeclaration: string,
110
+ {throwOnSideEffect, contextNode}: {throwOnSideEffect: boolean, contextNode: SDK.DOMModel.DOMNode|null}):
111
+ Promise<string> {
112
+ if (!contextNode) {
113
+ throw new Error('Cannot execute JavaScript because of missing context node');
114
+ }
115
+ const target = contextNode.domModel().target();
116
+
117
+ if (!target) {
118
+ throw new Error('Target is not found for executing code');
119
+ }
120
+
121
+ const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel);
122
+ const frameId = contextNode.frameId() ?? resourceTreeModel?.mainFrame?.id;
123
+
124
+ if (!frameId) {
125
+ throw new Error('Main frame is not found for executing code');
126
+ }
127
+
128
+ const runtimeModel = target.model(SDK.RuntimeModel.RuntimeModel);
129
+ const pageAgent = target.pageAgent();
130
+
131
+ // This returns previously created world if it exists for the frame.
132
+ const {executionContextId} = await pageAgent.invoke_createIsolatedWorld({frameId, worldName: FREESTYLER_WORLD_NAME});
133
+ const executionContext = runtimeModel?.executionContext(executionContextId);
134
+ if (!executionContext) {
135
+ throw new Error('Execution context is not found for executing code');
136
+ }
137
+
138
+ if (executionContext.debuggerModel.selectedCallFrame()) {
139
+ return formatError('Cannot evaluate JavaScript because the execution is paused on a breakpoint.');
140
+ }
141
+
142
+ const remoteObject = await contextNode.resolveToObject(undefined, executionContextId);
143
+ if (!remoteObject) {
144
+ throw new Error('Cannot execute JavaScript because remote object cannot be resolved');
145
+ }
146
+
147
+ return await EvaluateAction.execute(functionDeclaration, [remoteObject], executionContext, {throwOnSideEffect});
148
+ }
149
+
150
+ const MAX_OBSERVATION_BYTE_LENGTH = 25_000;
151
+ const OBSERVATION_TIMEOUT = 5_000;
152
+
153
+ export interface JavascriptExecutorOptions {
154
+ readonly executionMode: Root.Runtime.HostConfigFreestylerExecutionMode;
155
+ readonly getContextNode: () => SDK.DOMModel.DOMNode | null;
156
+ readonly createExtensionScope: (changes: ChangeManager) => {
157
+ install(): Promise<void>, uninstall(): Promise<void>,
158
+ };
159
+ readonly changes: ChangeManager;
160
+ }
161
+
162
+ export class JavascriptExecutor {
163
+ #options: JavascriptExecutorOptions;
164
+ #execJs: typeof executeJsCode;
165
+
166
+ constructor(options: JavascriptExecutorOptions, execJs: typeof executeJsCode = executeJsCode) {
167
+ this.#options = options;
168
+ this.#execJs = execJs;
169
+ }
170
+
171
+ async executeAction(action: string, options?: FunctionHandlerOptions): Promise<FunctionCallHandlerResult<unknown>> {
172
+ debugLog(`Action to execute: ${action}`);
173
+
174
+ if (options?.approved === false) {
175
+ return {
176
+ error: 'Error: User denied code execution with side effects.',
177
+ };
178
+ }
179
+
180
+ if (this.#options.executionMode === Root.Runtime.HostConfigFreestylerExecutionMode.NO_SCRIPTS) {
181
+ return {
182
+ error: 'Error: JavaScript execution is currently disabled.',
183
+ };
184
+ }
185
+
186
+ const selectedNode = this.#options.getContextNode();
187
+ if (!selectedNode) {
188
+ return {error: 'Error: no selected node found.'};
189
+ }
190
+ const target = selectedNode.domModel().target();
191
+ if (target.model(SDK.DebuggerModel.DebuggerModel)?.selectedCallFrame()) {
192
+ return {
193
+ error: 'Error: Cannot evaluate JavaScript because the execution is paused on a breakpoint.',
194
+ };
195
+ }
196
+
197
+ const scope = this.#options.createExtensionScope(this.#options.changes);
198
+ await scope.install();
199
+ try {
200
+ let throwOnSideEffect = true;
201
+ if (options?.approved) {
202
+ throwOnSideEffect = false;
203
+ }
204
+
205
+ const result = await this.generateObservation(action, {throwOnSideEffect});
206
+ debugLog(`Action result: ${JSON.stringify(result)}`);
207
+ if (result.sideEffect) {
208
+ if (this.#options.executionMode ===
209
+ Root.Runtime.HostConfigFreestylerExecutionMode.SIDE_EFFECT_FREE_SCRIPTS_ONLY) {
210
+ return {
211
+ error: 'Error: JavaScript execution that modifies the page is currently disabled.',
212
+ };
213
+ }
214
+
215
+ if (options?.signal?.aborted) {
216
+ return {
217
+ error: 'Error: evaluation has been cancelled',
218
+ };
219
+ }
220
+
221
+ return {
222
+ requiresApproval: true,
223
+ description: lockedString('This code may modify page content. Continue?'),
224
+ };
225
+ }
226
+ if (result.canceled) {
227
+ return {
228
+ error: result.observation,
229
+ };
230
+ }
231
+
232
+ return {
233
+ result: result.observation,
234
+ };
235
+ } finally {
236
+ await scope.uninstall();
237
+ }
238
+ }
239
+
240
+ async generateObservation(
241
+ action: string,
242
+ {
243
+ throwOnSideEffect,
244
+ }: {
245
+ throwOnSideEffect: boolean,
246
+ },
247
+ ): Promise<{
248
+ observation: string,
249
+ sideEffect: boolean,
250
+ canceled: boolean,
251
+ }> {
252
+ const functionDeclaration = `async function ($0) {
253
+ try {
254
+ ${action}
255
+ ;
256
+ return ((typeof data !== "undefined") ? data : undefined);
257
+ } catch (error) {
258
+ return error;
259
+ }
260
+ }`;
261
+ try {
262
+ const result = await Promise.race([
263
+ this.#execJs(
264
+ functionDeclaration,
265
+ {
266
+ throwOnSideEffect,
267
+ contextNode: this.#options.getContextNode(),
268
+ },
269
+ ),
270
+ new Promise<never>((_, reject) => {
271
+ setTimeout(
272
+ () => reject(new Error('Script execution exceeded the maximum allowed time.')), OBSERVATION_TIMEOUT);
273
+ }),
274
+ ]);
275
+ const byteCount = Platform.StringUtilities.countWtf8Bytes(result);
276
+ Host.userMetrics.freestylerEvalResponseSize(byteCount);
277
+ if (byteCount > MAX_OBSERVATION_BYTE_LENGTH) {
278
+ throw new Error('Output exceeded the maximum allowed length.');
279
+ }
280
+ return {
281
+ observation: result,
282
+ sideEffect: false,
283
+ canceled: false,
284
+ };
285
+ } catch (error) {
286
+ if (error instanceof SideEffectError) {
287
+ return {
288
+ observation: error.message,
289
+ sideEffect: true,
290
+ canceled: false,
291
+ };
292
+ }
293
+
294
+ return {
295
+ observation: `Error: ${error.message}`,
296
+ sideEffect: false,
297
+ canceled: false,
298
+ };
299
+ }
300
+ }
301
+ }
@@ -967,16 +967,27 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
967
967
 
968
968
  const key = `getMainThreadTrackSummary({min: ${bounds.min}, max: ${bounds.max}})`;
969
969
  this.#cacheFunctionResult(focus, key, summary);
970
+ const widgets: AiWidget[] = [];
971
+ widgets.push({
972
+ name: 'TIMELINE_RANGE_SUMMARY',
973
+ data: {
974
+ parsedTrace,
975
+ bounds,
976
+ track: 'main',
977
+ },
978
+ });
979
+
980
+ widgets.push({
981
+ name: 'BOTTOM_UP_TREE',
982
+ data: {
983
+ bounds,
984
+ parsedTrace,
985
+ },
986
+ });
987
+
970
988
  return {
971
989
  result: {summary},
972
- widgets: [{
973
- name: 'TIMELINE_RANGE_SUMMARY',
974
- data: {
975
- parsedTrace,
976
- bounds,
977
- track: 'main',
978
- },
979
- }],
990
+ widgets,
980
991
  };
981
992
  },
982
993