chrome-devtools-frontend 1.0.1592129 → 1.0.1592362

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 (35) hide show
  1. package/front_end/core/host/UserMetrics.ts +2 -1
  2. package/front_end/core/protocol_client/InspectorBackend.ts +4 -0
  3. package/front_end/core/root/ExperimentNames.ts +1 -0
  4. package/front_end/core/sdk/EmulationModel.ts +41 -1
  5. package/front_end/entrypoints/main/MainImpl.ts +8 -0
  6. package/front_end/generated/InspectorBackendCommands.ts +1 -0
  7. package/front_end/generated/protocol-mapping.d.ts +6 -0
  8. package/front_end/generated/protocol-proxy-api.d.ts +7 -0
  9. package/front_end/generated/protocol.ts +16 -0
  10. package/front_end/models/ai_assistance/agents/BreakpointDebuggerAgent.ts +379 -25
  11. package/front_end/models/ai_assistance/agents/BreakpointDebuggerAgentOverlay.ts +87 -0
  12. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +8 -8
  13. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +65 -44
  14. package/front_end/models/computed_style/ComputedStyleModel.ts +40 -6
  15. package/front_end/models/emulation/DeviceModeModel.ts +47 -0
  16. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +11 -2
  17. package/front_end/panels/ai_assistance/components/MarkdownRendererWithCodeBlock.ts +55 -1
  18. package/front_end/panels/application/CookieItemsView.ts +194 -134
  19. package/front_end/panels/application/DeviceBoundSessionsTreeElement.ts +1 -1
  20. package/front_end/panels/application/StorageItemsToolbar.ts +25 -2
  21. package/front_end/panels/application/cookieItemsView.css +28 -26
  22. package/front_end/panels/console/ConsoleViewMessage.ts +1 -1
  23. package/front_end/panels/elements/ComputedStyleWidget.ts +11 -25
  24. package/front_end/panels/elements/ElementsPanel.ts +26 -3
  25. package/front_end/panels/elements/StylePropertyTreeElement.ts +33 -0
  26. package/front_end/panels/elements/StylesAiCodeCompletionProvider.ts +148 -1
  27. package/front_end/panels/elements/elements-meta.ts +3 -5
  28. package/front_end/panels/elements/stylePropertiesTreeOutline.css +6 -0
  29. package/front_end/panels/emulation/DeviceModeToolbar.ts +25 -0
  30. package/front_end/panels/timeline/components/metricCard.css +0 -4
  31. package/front_end/ui/components/text_editor/AiCodeCompletionProvider.ts +13 -2
  32. package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +44 -24
  33. package/front_end/ui/visual_logging/Debugging.ts +4 -0
  34. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  35. package/package.json +1 -1
@@ -20,9 +20,16 @@ import {
20
20
  type ContextResponse,
21
21
  ConversationContext,
22
22
  type FunctionCallHandlerResult,
23
+ type FunctionHandlerOptions,
24
+ type MultimodalInput,
23
25
  type RequestOptions,
26
+ type ResponseData,
24
27
  ResponseType,
25
28
  } from './AiAgent.js';
29
+ import {
30
+ injectOverlay,
31
+ removeOverlay,
32
+ } from './BreakpointDebuggerAgentOverlay.js';
26
33
 
27
34
  // This is a temporary agent for a GreenDev prototype.
28
35
  // The preamble is not on the server and you should not build on top of this.
@@ -39,20 +46,23 @@ You have two modes of operation that you can switch between and control:
39
46
  **Workflow**:
40
47
  1. **Hypothesize**: Read the code ('getFunctionSource', 'getPreviousLines', 'getNextLines') to understand the logic.
41
48
  2. **Set Trap**: Identify the critical line where state corruption likely occurred or lines that can lead you to that place. Use 'setBreakpoint' on that line.
42
- 3. **Wait**: Call 'waitForBreakpoint'. This will suspend your execution until the user triggers the breakpoint. You CANNOT proceed until this tool returns.
49
+ 3. **Wait**: Call 'waitForUserActionToTriggerBreakpoint'. This will suspend your execution until the user triggers the breakpoint. You CANNOT proceed until this tool returns.
43
50
  4. **Inspect**: Using 'getExecutionLocation' check exactly where you are paused.
44
51
  5. **Analyze**: When paused (Runtime Mode), use 'getScopeVariables' and 'getCallStack' to verify your hypothesis. Check variables in multiple scopes and look up the call stack to see where bad data came from.
45
52
  6. **Step**: Use 'stepInto' to investigate function calls on the current line. Use 'stepOut' to return to the caller. Use 'stepOver' to move to the next line.
46
53
  7. **Trace Back**: If the current function isn't the root cause, use 'getCallStack' to find the caller, and repeat the analysis there.
47
54
  8. **Root Cause**: Explain exactly how the runtime state contradicts the expected logic and point to the specific line of code that is the root cause.
55
+ 9. **Test Fix**: Use the 'testFixInConsole' tool to run a JavaScript snippet in the current execution context to test your fix before making permanent changes. This will overwrite the problematic function or state. Follow the instructions to get the user's approval.
56
+ 10. **Verify**: IMMEDIATELY AFTER 'testFixInConsole' succeeds, you MUST call 'waitForUserActionToTriggerBreakpoint' (to pause and inspect) or 'resume' (if no inspection is needed), AND ask the user to trigger the buggy action again to verify the fix.
57
+ 11. **Patch**: Once the fix is successfully verified by the user, you MUST use the 'suggestFix' tool to apply the patch to the source code file. Provide the URL, the exact original code to change, and the new code. Finish the execution then.
48
58
 
49
59
  **Rules**:
50
60
  - **NEVER FINISH** execution until you have found the root cause or answer.
51
- - **ACTION OVER TALK**: If you need the user to trigger a breakpoint, do NOT just ask them in text. You **MUST** call 'waitForBreakpoint'. This tool will block and wait for the user to act.
52
- - **STATIC MODE**: If you are in STATIC MODE and need to see variables: 1. 'setBreakpoint', 2. 'waitForBreakpoint'. **DO NOT STOP** to ask the user. Investigate code and set breakpoints to find the root cause.
53
- - **ALREADY PAUSED?**: If 'setBreakpoint' warns you that you are already paused, **DO NOT** call 'waitForBreakpoint'. Start inspecting immediately. You can set more breakpoints while paused, but to call 'waitForBreakpoint' again you MUST be in static state.
61
+ - **ACTION OVER TALK**: If you need the user to trigger a breakpoint, do NOT just ask them in text. You **MUST** call 'waitForUserActionToTriggerBreakpoint'. This tool will block and wait for the user to act.
62
+ - **STATIC MODE**: If you are in STATIC MODE and need to see variables: 1. 'setBreakpoint', 2. 'waitForUserActionToTriggerBreakpoint'. **DO NOT STOP** to ask the user. Investigate code and set breakpoints to find the root cause.
63
+ - **ALREADY PAUSED?**: If 'setBreakpoint' warns you that you are already paused, **DO NOT** call 'waitForUserActionToTriggerBreakpoint'. Start inspecting immediately. You can set more breakpoints while paused, but to call 'waitForUserActionToTriggerBreakpoint' again you MUST be in static state.
54
64
  - **USE TOOLS EXCESSIVELY**: checking one thing is often not enough. Check everything you can thinks of.
55
- - **CHECK LOCATION**: If you are not sure where you are, call 'getExecutionLocation' after 'waitForBreakpoint' or any step command to confirm where you are.
65
+ - **CHECK LOCATION**: If you are not sure where you are, call 'getExecutionLocation' after 'waitForUserActionToTriggerBreakpoint' or any step command to confirm where you are.
56
66
 
57
67
  **Execution Control when you are currently on a breakpoint**:
58
68
  - **stepInto**: ESSENTIAL for entering function calls on the current line. Use this heavily when you suspect the issue is inside a called function.
@@ -60,6 +70,8 @@ You have two modes of operation that you can switch between and control:
60
70
  - **stepOut**: Return to the caller. If you are currently on a breakpoint, 'stepOut' will move you to the caller and pause again. **It often makes sense to 'stepOut' after you have investigated a function with 'stepInto' and verified it is correct.**
61
71
  - **stepInto, stepOver, stepOut**: After any step command, always call 'getScopeVariables' to see how the state evolved.
62
72
  - **listBreakpoints**: Use this to see all active breakpoints. Do not try to set a breakpoint that is already active.
73
+ - **removeBreakpoint / removeAllBreakpoints**: Use this to remove breakpoints. This is especially useful when you want to speed up verifying a fix.
74
+ - **CLEANUP AFTER FIX**: After a fix is suggested and worked, you MUST remove all breakpoints and call 'resume' to resume the execution of the page.
63
75
  `;
64
76
 
65
77
  export class BreakpointContext extends ConversationContext<Workspace.UISourceCode.UILocation> {
@@ -108,6 +120,11 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
108
120
  },
109
121
  required: ['url', 'lineNumber'],
110
122
  },
123
+ displayInfoFromArgs: (args: {url: string, lineNumber: number}) => {
124
+ return {
125
+ title: `Reading function source for ${args.url}:${args.lineNumber}`,
126
+ };
127
+ },
111
128
  handler: async (args: {url: string, lineNumber: number}) => {
112
129
  const result = await this.#getFunctionSource(args);
113
130
  debugLog('getFunctionSource for ', JSON.stringify(args), '->', JSON.stringify(result));
@@ -137,6 +154,11 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
137
154
  },
138
155
  required: ['url', 'lineNumber', 'direction'],
139
156
  },
157
+ displayInfoFromArgs: (args: {url: string, lineNumber: number, direction: 'before'|'after'}) => {
158
+ return {
159
+ title: `Reading code ${args.direction} ${args.url}:${args.lineNumber}`,
160
+ };
161
+ },
140
162
  handler: async (args: {url: string, lineNumber: number, direction: 'before'|'after'}) => {
141
163
  const result = await this.#getCodeLines(args);
142
164
  debugLog('getCodeLines result', JSON.stringify(result));
@@ -151,6 +173,11 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
151
173
  properties: {},
152
174
  required: [],
153
175
  },
176
+ displayInfoFromArgs: () => {
177
+ return {
178
+ title: 'Reading call stack',
179
+ };
180
+ },
154
181
  handler: async () => {
155
182
  const result = await this.#getCallStack();
156
183
  debugLog('getCallStack result', JSON.stringify(result));
@@ -165,6 +192,11 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
165
192
  properties: {},
166
193
  required: [],
167
194
  },
195
+ displayInfoFromArgs: () => {
196
+ return {
197
+ title: 'Reading scope variables',
198
+ };
199
+ },
168
200
  handler: async () => {
169
201
  const result = await this.#getScopeVariables();
170
202
  debugLog('getScopeVariables result', JSON.stringify(result));
@@ -179,6 +211,11 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
179
211
  properties: {},
180
212
  required: [],
181
213
  },
214
+ displayInfoFromArgs: () => {
215
+ return {
216
+ title: 'Listing breakpoints',
217
+ };
218
+ },
182
219
  handler: async () => {
183
220
  const result = await this.#listBreakpoints();
184
221
  debugLog('listBreakpoints result', JSON.stringify(result));
@@ -202,6 +239,11 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
202
239
  },
203
240
  required: ['url', 'lineNumber'],
204
241
  },
242
+ displayInfoFromArgs: (args: {url: string, lineNumber: number}) => {
243
+ return {
244
+ title: `Setting breakpoint at ${args.url}:${args.lineNumber}`,
245
+ };
246
+ },
205
247
  handler: async (args: {url: string, lineNumber: number}) => {
206
248
  debugLog('setBreakpoint requested', args);
207
249
  const result = await this.#setBreakpoint(args);
@@ -209,18 +251,78 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
209
251
  return result;
210
252
  },
211
253
  });
254
+ this.declareFunction('removeBreakpoint', {
255
+ description: 'Remove a breakpoint at a specific location.',
256
+ parameters: {
257
+ type: Host.AidaClient.ParametersTypes.OBJECT,
258
+ description: 'Location to remove the breakpoint from',
259
+ properties: {
260
+ url: {
261
+ type: Host.AidaClient.ParametersTypes.STRING,
262
+ description: 'The URL of the file',
263
+ },
264
+ lineNumber: {
265
+ type: Host.AidaClient.ParametersTypes.INTEGER,
266
+ description: 'The 1-based line number to remove the breakpoint from',
267
+ },
268
+ },
269
+ required: ['url', 'lineNumber'],
270
+ },
271
+ displayInfoFromArgs: (args: {url: string, lineNumber: number}) => {
272
+ return {
273
+ title: `Removing breakpoint at ${args.url}:${args.lineNumber}`,
274
+ };
275
+ },
276
+ handler: async (args: {url: string, lineNumber: number}) => {
277
+ debugLog('removeBreakpoint requested', args);
278
+ const result = await this.#removeBreakpoint(args);
279
+ debugLog('removeBreakpoint result', JSON.stringify(result));
280
+ return result;
281
+ },
282
+ });
283
+ this.declareFunction('removeAllBreakpoints', {
284
+ description: 'Remove all active breakpoints.',
285
+ parameters: {
286
+ type: Host.AidaClient.ParametersTypes.OBJECT,
287
+ description: 'No parameters required',
288
+ properties: {},
289
+ required: [],
290
+ },
291
+ displayInfoFromArgs: () => {
292
+ return {
293
+ title: 'Removing all breakpoints',
294
+ };
295
+ },
296
+ handler: async () => {
297
+ debugLog('removeAllBreakpoints requested');
298
+ const breakpointManager = Breakpoints.BreakpointManager.BreakpointManager.instance();
299
+ const allBreakpoints = breakpointManager.allBreakpointLocations();
300
+ for (const bp of allBreakpoints) {
301
+ await bp.breakpoint.remove(false);
302
+ }
303
+ return {result: {status: 'All breakpoints removed.'}};
304
+ },
305
+ });
212
306
  this.declareFunction('resume', {
213
- description: 'Resume execution until the next breakpoint is hit.',
307
+ description: 'Resume execution. Always use this after applying a fix to resume the page execution.',
214
308
  parameters: {
215
309
  type: Host.AidaClient.ParametersTypes.OBJECT,
216
310
  description: 'No parameters required',
217
311
  properties: {},
218
312
  required: [],
219
313
  },
314
+ displayInfoFromArgs: () => {
315
+ return {
316
+ title: 'Resuming execution',
317
+ };
318
+ },
220
319
  handler: async () => {
221
- const result = await this.#debuggerAction(model => model.resume());
222
- debugLog('resume result', JSON.stringify(result));
223
- return result;
320
+ const targetManager = SDK.TargetManager.TargetManager.instance();
321
+ const debuggerModel = targetManager.models(SDK.DebuggerModel.DebuggerModel).find(m => m.isPaused());
322
+ if (debuggerModel) {
323
+ debuggerModel.resume();
324
+ }
325
+ return {result: {status: 'Execution resumed.'}};
224
326
  },
225
327
  });
226
328
  this.declareFunction('stepOver', {
@@ -231,6 +333,11 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
231
333
  properties: {},
232
334
  required: [],
233
335
  },
336
+ displayInfoFromArgs: () => {
337
+ return {
338
+ title: 'Stepping over',
339
+ };
340
+ },
234
341
  handler: async () => {
235
342
  const result = await this.#debuggerAction(model => model.stepOver());
236
343
  debugLog('stepOver result', JSON.stringify(result));
@@ -246,6 +353,11 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
246
353
  properties: {},
247
354
  required: [],
248
355
  },
356
+ displayInfoFromArgs: () => {
357
+ return {
358
+ title: 'Stepping into',
359
+ };
360
+ },
249
361
  handler: async () => {
250
362
  const result = await this.#debuggerAction(model => model.stepInto());
251
363
  debugLog('stepInto result', JSON.stringify(result));
@@ -261,6 +373,11 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
261
373
  properties: {},
262
374
  required: [],
263
375
  },
376
+ displayInfoFromArgs: () => {
377
+ return {
378
+ title: 'Stepping out',
379
+ };
380
+ },
264
381
  handler: async () => {
265
382
  const result = await this.#debuggerAction(model => model.stepOut());
266
383
  debugLog('stepOut result', JSON.stringify(result));
@@ -298,12 +415,169 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
298
415
  properties: {},
299
416
  required: [],
300
417
  },
418
+ displayInfoFromArgs: () => {
419
+ return {
420
+ title: 'Getting execution location',
421
+ };
422
+ },
301
423
  handler: async () => {
302
424
  const result = await this.#getExecutionLocation();
303
425
  debugLog('getExecutionLocation ', JSON.stringify(result));
304
426
  return result;
305
427
  },
306
428
  });
429
+
430
+ this.declareFunction('testFixInConsole', {
431
+ description:
432
+ 'Tests a JavaScript code snippet in the current execution context to overwrite the problematic code or state. Use this to verify the fix works before patching the source file.',
433
+ parameters: {
434
+ type: Host.AidaClient.ParametersTypes.OBJECT,
435
+ description: 'Provide the code to evaluate to test the fix',
436
+ properties: {
437
+ code: {
438
+ type: Host.AidaClient.ParametersTypes.STRING,
439
+ description: 'The JavaScript code to evaluate in the console to test the fix.',
440
+ },
441
+ explanation: {
442
+ type: Host.AidaClient.ParametersTypes.STRING,
443
+ description: 'Explanation for why this code fixes the issue.',
444
+ },
445
+ },
446
+ required: ['code', 'explanation'],
447
+ },
448
+ displayInfoFromArgs: (args: {code: string, explanation: string}) => {
449
+ return {
450
+ title: 'Testing a fix in console',
451
+ thought: args.explanation,
452
+ action: args.code,
453
+ };
454
+ },
455
+ handler: async (args: {code: string, explanation: string}, options?: FunctionHandlerOptions) => {
456
+ debugLog('testFixInConsole requested', args);
457
+ if (options?.approved === false) {
458
+ return {error: 'Fix rejected by the user.'};
459
+ }
460
+ if (!options?.approved) {
461
+ return {requiresApproval: true};
462
+ }
463
+
464
+ const targetManager = SDK.TargetManager.TargetManager.instance();
465
+ const debuggerModel = targetManager.models(SDK.DebuggerModel.DebuggerModel).find(m => m.isPaused());
466
+
467
+ if (!debuggerModel) {
468
+ return {error: 'Execution is not paused.'};
469
+ }
470
+
471
+ const details = debuggerModel.debuggerPausedDetails();
472
+ const callFrame = details?.callFrames[0];
473
+ if (!callFrame) {
474
+ return {error: 'No call frame available.'};
475
+ }
476
+
477
+ const result = await callFrame.evaluate({
478
+ expression: args.code,
479
+ objectGroup: 'console',
480
+ includeCommandLineAPI: true,
481
+ silent: false,
482
+ returnByValue: false,
483
+ generatePreview: true
484
+ });
485
+
486
+ if (!result) {
487
+ return {error: 'Failed to evaluate the fix.'};
488
+ }
489
+
490
+ if ('error' in result) {
491
+ return {error: 'Error applying fix: ' + (result as unknown as {error: string}).error};
492
+ }
493
+
494
+ if (result.exceptionDetails) {
495
+ return {error: 'Fix threw an exception: ' + result.exceptionDetails.text};
496
+ }
497
+
498
+ return {
499
+ result: {
500
+ status:
501
+ 'Code evaluated successfully. You MUST now call waitForUserActionToTriggerBreakpoint or resume, and ask the user to trigger the action again to verify the fix.'
502
+ }
503
+ };
504
+ },
505
+ });
506
+
507
+ this.declareFunction('suggestFix', {
508
+ description:
509
+ 'Suggests a JavaScript code snippet to fix the root cause of the issue and applies it to the source code file if the user approves. Call this function AFTER you have verified the fix works using testFixInConsole.',
510
+ parameters: {
511
+ type: Host.AidaClient.ParametersTypes.OBJECT,
512
+ description: 'Provide the fix to the user',
513
+ properties: {
514
+ url: {
515
+ type: Host.AidaClient.ParametersTypes.STRING,
516
+ description: 'The URL of the file to fix.',
517
+ },
518
+ originalCode: {
519
+ type: Host.AidaClient.ParametersTypes.STRING,
520
+ description:
521
+ 'The exact original code to replace. Must match the file content exactly, including whitespace.',
522
+ },
523
+ suggestion: {
524
+ type: Host.AidaClient.ParametersTypes.STRING,
525
+ description: 'The new JavaScript code to replace the original code with.',
526
+ },
527
+ explanation: {
528
+ type: Host.AidaClient.ParametersTypes.STRING,
529
+ description: 'Explanation for why this code fixes the issue.',
530
+ },
531
+ },
532
+ required: ['url', 'originalCode', 'suggestion', 'explanation'],
533
+ },
534
+ displayInfoFromArgs: (args: {suggestion: string, explanation: string}) => {
535
+ return {
536
+ title: 'Suggesting a fix',
537
+ thought: args.explanation,
538
+ action: args.suggestion,
539
+ };
540
+ },
541
+ handler: async (
542
+ args: {url: string, originalCode: string, suggestion: string, explanation: string},
543
+ options?: FunctionHandlerOptions) => {
544
+ debugLog('suggestFix requested', args);
545
+ if (options?.approved === false) {
546
+ return {error: 'Fix rejected by the user.'};
547
+ }
548
+ if (!options?.approved) {
549
+ return {requiresApproval: true};
550
+ }
551
+
552
+ const uiSourceCode = Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodeForURL(
553
+ args.url as Platform.DevToolsPath.UrlString);
554
+ if (!uiSourceCode) {
555
+ return {error: `File not found: ${args.url}`};
556
+ }
557
+
558
+ const contentData = await uiSourceCode.requestContentData();
559
+ if ('error' in contentData || !contentData.isTextContent) {
560
+ return {error: `Could not read content for file: ${args.url}`};
561
+ }
562
+
563
+ let currentContent = uiSourceCode.workingCopy();
564
+ if (!uiSourceCode.isDirty()) {
565
+ currentContent = contentData.text;
566
+ }
567
+
568
+ if (!currentContent.includes(args.originalCode)) {
569
+ return {
570
+ error: 'originalCode not found in the file. Ensure the originalCode is an exact match including whitespace.'
571
+ };
572
+ }
573
+
574
+ const newContent = currentContent.replace(args.originalCode, args.suggestion);
575
+ uiSourceCode.setWorkingCopy(newContent);
576
+ uiSourceCode.commitWorkingCopy();
577
+
578
+ return {result: {status: 'Fix applied to the source file successfully.'}};
579
+ },
580
+ });
307
581
  }
308
582
 
309
583
  async #getFunctionSource(args: {url: string, lineNumber: number}):
@@ -407,7 +681,7 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
407
681
  if (!debuggerModel) {
408
682
  return {
409
683
  error:
410
- 'Execution is not paused. I cannot access runtime variables or the call stack. I am currently in STATIC MODE. I must set a breakpoint and use waitForBreakpoint to enter RUNTIME MODE.'
684
+ 'Execution is not paused. I cannot access runtime variables or the call stack. I am currently in STATIC MODE. I must set a breakpoint and use waitForUserActionToTriggerBreakpoint to enter RUNTIME MODE.'
411
685
  };
412
686
  }
413
687
 
@@ -439,7 +713,7 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
439
713
  if (!debuggerModel) {
440
714
  return {
441
715
  error:
442
- 'Execution is not paused. I cannot access runtime variables or the call stack. I am currently in STATIC MODE. I must set a breakpoint and use waitForBreakpoint to enter RUNTIME MODE.'
716
+ 'Execution is not paused. I cannot access runtime variables or the call stack. I am currently in STATIC MODE. I must set a breakpoint and use waitForUserActionToTriggerBreakpoint to enter RUNTIME MODE.'
443
717
  };
444
718
  }
445
719
 
@@ -566,7 +840,7 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
566
840
  if (callFrame) {
567
841
  const pausedLoc = `${callFrame.script.contentURL()}:${callFrame.location().lineNumber + 1}`;
568
842
  warning = ` WARNING: You are already PAUSED at ${
569
- pausedLoc}. \n1. If this is where you want to be, call 'getExecutionLocation' and inspect variables. \n2. If you want to wait for the NEW breakpoint, you MUST call 'waitForBreakpoint' (which will resume execution).`;
843
+ pausedLoc}. \n1. If this is where you want to be, call 'getExecutionLocation' and inspect variables. \n2. If you want to wait for the NEW breakpoint, you MUST call 'waitForUserActionToTriggerBreakpoint' (which will resume execution).`;
570
844
  }
571
845
  }
572
846
 
@@ -575,8 +849,9 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
575
849
  result: {
576
850
  status: `Breakpoint requested at ${args.url}:${args.lineNumber}, but ACTUALLY resolved to line ${
577
851
  actualLineNumber}.${
578
- warning ? '\n' + warning :
579
- ' You must now call waitForBreakpoint and ask the user to trigger the action.'}`
852
+ warning ?
853
+ '\n' + warning :
854
+ ' You must now call waitForUserActionToTriggerBreakpoint and ask the user to trigger the action.'}`
580
855
  }
581
856
  };
582
857
  }
@@ -584,11 +859,33 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
584
859
  return {
585
860
  result: {
586
861
  status: `Breakpoint set at ${args.url}:${args.lineNumber}.${
587
- warning ? '\n' + warning : ' You must now call waitForBreakpoint and ask the user to trigger the action.'}`
862
+ warning ?
863
+ '\n' + warning :
864
+ ' You must now call waitForUserActionToTriggerBreakpoint and ask the user to trigger the action.'}`
588
865
  }
589
866
  };
590
867
  }
591
868
 
869
+ async #removeBreakpoint(args: {url: string, lineNumber: number}):
870
+ Promise<FunctionCallHandlerResult<{status: string}>> {
871
+ const uiSourceCode =
872
+ Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodeForURL(args.url as Platform.DevToolsPath.UrlString);
873
+ if (!uiSourceCode) {
874
+ return {error: `File not found: ${args.url}`};
875
+ }
876
+
877
+ const breakpointLocations =
878
+ Breakpoints.BreakpointManager.BreakpointManager.instance().breakpointLocationsForUISourceCode(uiSourceCode);
879
+ const breakpointLocation = breakpointLocations.find(bp => bp.uiLocation.lineNumber === args.lineNumber - 1);
880
+
881
+ if (!breakpointLocation) {
882
+ return {result: {status: `Breakpoint not found at ${args.url}:${args.lineNumber}.`}};
883
+ }
884
+
885
+ await breakpointLocation.breakpoint.remove(false);
886
+ return {result: {status: `Breakpoint removed at ${args.url}:${args.lineNumber}.`}};
887
+ }
888
+
592
889
  async #debuggerAction(action: (model: SDK.DebuggerModel.DebuggerModel) => void):
593
890
  Promise<FunctionCallHandlerResult<{status: string}>> {
594
891
  const targetManager = SDK.TargetManager.TargetManager.instance();
@@ -598,8 +895,8 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
598
895
  return {error: 'Execution is not paused. I cannot step or resume in STATIC MODE.'};
599
896
  }
600
897
 
601
- // Only resolve when next pause event is triggered
602
- return await this.#waitForNextPause(() => action(debuggerModel));
898
+ // Only resolve when next pause event is triggered (with a 3 second timeout so the agent doesn't hang)
899
+ return await this.#waitForNextPause(() => action(debuggerModel), 3000);
603
900
  }
604
901
 
605
902
  async #waitForUserActionToTriggerBreakpoint(): Promise<FunctionCallHandlerResult<{status: string}>> {
@@ -610,14 +907,23 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
610
907
  return {error: 'No debugger attached'};
611
908
  }
612
909
 
613
- return await this.#waitForNextPause(() => {
614
- // Resume all paused models before waiting for the next breakpoint
615
- for (const model of debuggerModels) {
616
- if (model.isPaused()) {
617
- model.resume();
910
+ // While waiting for the user to trigger the breakpoint, show an overlay on top of the page.
911
+ // This is to make it clear to the user that they need to trigger the
912
+ // breakpoint for the agent to continue the debugging process.
913
+ void injectOverlay();
914
+
915
+ try {
916
+ return await this.#waitForNextPause(() => {
917
+ // Resume all paused models before waiting for the next breakpoint
918
+ for (const model of debuggerModels) {
919
+ if (model.isPaused()) {
920
+ model.resume();
921
+ }
618
922
  }
619
- }
620
- });
923
+ });
924
+ } finally {
925
+ void removeOverlay();
926
+ }
621
927
  }
622
928
 
623
929
  /**
@@ -626,14 +932,20 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
626
932
  *
627
933
  * @param triggerAction Optional action to execute (e.g. resume, step) that is expected to lead to a pause.
628
934
  */
629
- async #waitForNextPause(triggerAction: () => void = () => {}): Promise<FunctionCallHandlerResult<{status: string}>> {
935
+ async #waitForNextPause(triggerAction: () => void = () => {}, timeoutMs?: number):
936
+ Promise<FunctionCallHandlerResult<{status: string}>> {
630
937
  const targetManager = SDK.TargetManager.TargetManager.instance();
631
938
 
632
939
  return await new Promise(resolve => {
940
+ let timeoutId: ReturnType<typeof setTimeout>;
941
+
633
942
  const listener =
634
943
  async(event: Common.EventTarget.EventTargetEvent<SDK.DebuggerModel.DebuggerModel>): Promise<void> => {
635
944
  targetManager.removeModelListener(
636
945
  SDK.DebuggerModel.DebuggerModel, SDK.DebuggerModel.Events.DebuggerPaused, listener);
946
+ if (timeoutId) {
947
+ clearTimeout(timeoutId);
948
+ }
637
949
  const model = event.data;
638
950
  const details = model.debuggerPausedDetails();
639
951
  const callFrame = details?.callFrames[0];
@@ -655,6 +967,19 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
655
967
  targetManager.addModelListener(
656
968
  SDK.DebuggerModel.DebuggerModel, SDK.DebuggerModel.Events.DebuggerPaused, listener);
657
969
 
970
+ if (timeoutMs !== undefined) {
971
+ timeoutId = setTimeout(() => {
972
+ targetManager.removeModelListener(
973
+ SDK.DebuggerModel.DebuggerModel, SDK.DebuggerModel.Events.DebuggerPaused, listener);
974
+ resolve({
975
+ result: {
976
+ status:
977
+ 'Execution resumed but did not pause again. There is nothing to step into or the execution finished.'
978
+ }
979
+ });
980
+ }, timeoutMs);
981
+ }
982
+
658
983
  // Execute the action that will eventually trigger the pause
659
984
  triggerAction();
660
985
  });
@@ -719,4 +1044,33 @@ export class BreakpointDebuggerAgent extends AiAgent<Workspace.UISourceCode.UILo
719
1044
  get options(): RequestOptions {
720
1045
  return {temperature: 0, modelId: undefined};
721
1046
  }
1047
+
1048
+ override async *
1049
+ run(
1050
+ initialQuery: string,
1051
+ options: {
1052
+ selected: ConversationContext<Workspace.UISourceCode.UILocation>|null,
1053
+ signal?: AbortSignal,
1054
+ },
1055
+ multimodalInput?: MultimodalInput,
1056
+ ): AsyncGenerator<ResponseData, void, void> {
1057
+ try {
1058
+ yield* super.run(initialQuery, options, multimodalInput);
1059
+ } finally {
1060
+ // When the agent is done, remove all breakpoints and exit paused state.
1061
+ const breakpointManager = Breakpoints.BreakpointManager.BreakpointManager.instance();
1062
+ const allBreakpoints = breakpointManager.allBreakpointLocations();
1063
+ for (const bp of allBreakpoints) {
1064
+ await bp.breakpoint.remove(false);
1065
+ }
1066
+
1067
+ const targetManager = SDK.TargetManager.TargetManager.instance();
1068
+ const debuggerModels = targetManager.models(SDK.DebuggerModel.DebuggerModel);
1069
+ for (const model of debuggerModels) {
1070
+ if (model.isPaused()) {
1071
+ model.resume();
1072
+ }
1073
+ }
1074
+ }
1075
+ }
722
1076
  }