Skip to content

Commit 42a6a40

Browse files
wolfibDevtools-frontend LUCI CQ
authored andcommitted
Store context in AiAgent
The StylingAgent needs to evaluate JS code in the correct execution context. Before this CL the currently selected element was used both for determining the execution context, and for evaluating `$0`. This can be wrong for external requests with cross-proccess iframes, and also if the user selects a different UI element while the conversation is actively running. Both problems are fixed by keeping the context constant while the conversation is being executed. Selecting a new element in the UI and restarting the conversation with a new context still works after this CL. Bug: 422019184, 428690606, 419246352 Change-Id: Ica9abdc2e7c165f8b6577817b78cec733620e7a1 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6687895 Reviewed-by: Alex Rudenko <[email protected]> Reviewed-by: Ergün Erdoğmuş <[email protected]> Commit-Queue: Wolfgang Beyer <[email protected]>
1 parent f94cf12 commit 42a6a40

File tree

2 files changed

+29
-24
lines changed

2 files changed

+29
-24
lines changed

front_end/models/ai_assistance/agents/AiAgent.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,14 @@ export abstract class AiAgent<T> {
268268
* historical conversations.
269269
*/
270270
#origin?: string;
271-
#context?: ConversationContext<T>;
271+
272+
/**
273+
* `context` does not change during `AiAgent.run()`, ensuring that calls to JS
274+
* have the correct `context`. We don't want element selection by the user to
275+
* change the `context` during an `AiAgent.run()`.
276+
*/
277+
protected context?: ConversationContext<T>;
278+
272279
#id: string = crypto.randomUUID();
273280
#history: Host.AidaClient.Content[] = [];
274281

@@ -418,13 +425,14 @@ export abstract class AiAgent<T> {
418425
multimodalInput?: MultimodalInput): AsyncGenerator<ResponseData, void, void> {
419426
await options.selected?.refresh();
420427

421-
// First context set on the agent determines its origin from now on.
422-
if (options.selected && this.#origin === undefined && options.selected) {
423-
this.#origin = options.selected.getOrigin();
424-
}
425-
// Remember if the context that is set.
426-
if (options.selected && !this.#context) {
427-
this.#context = options.selected;
428+
if (options.selected) {
429+
// First context set on the agent determines its origin from now on.
430+
if (this.#origin === undefined) {
431+
this.#origin = options.selected.getOrigin();
432+
}
433+
if (options.selected.isOriginAllowed(this.#origin)) {
434+
this.context = options.selected;
435+
}
428436
}
429437

430438
const enhancedQuery = await this.enhanceQuery(initialQuery, options.selected, multimodalInput?.type);

front_end/models/ai_assistance/agents/StylingAgent.ts

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -182,16 +182,20 @@ const MULTIMODAL_ENHANCEMENT_PROMPTS: Record<MultimodalInputType, string> = {
182182
};
183183

184184
async function executeJsCode(
185-
functionDeclaration: string, {throwOnSideEffect}: {throwOnSideEffect: boolean}): Promise<string> {
186-
const selectedNode = UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode);
187-
const target = selectedNode?.domModel().target() ?? UI.Context.Context.instance().flavor(SDK.Target.Target);
185+
functionDeclaration: string,
186+
{throwOnSideEffect, contextNode}: {throwOnSideEffect: boolean, contextNode: SDK.DOMModel.DOMNode|null}):
187+
Promise<string> {
188+
if (!contextNode) {
189+
throw new Error('Cannot execute JavaScript because of missing context node');
190+
}
191+
const target = contextNode.domModel().target() ?? UI.Context.Context.instance().flavor(SDK.Target.Target);
188192

189193
if (!target) {
190194
throw new Error('Target is not found for executing code');
191195
}
192196

193197
const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel);
194-
const frameId = selectedNode?.frameId() ?? resourceTreeModel?.mainFrame?.id;
198+
const frameId = contextNode.frameId() ?? resourceTreeModel?.mainFrame?.id;
195199

196200
if (!frameId) {
197201
throw new Error('Main frame is not found for executing code');
@@ -211,19 +215,12 @@ async function executeJsCode(
211215
return formatError('Cannot evaluate JavaScript because the execution is paused on a breakpoint.');
212216
}
213217

214-
const result = await executionContext.evaluate(
215-
{
216-
expression: '$0',
217-
returnByValue: false,
218-
includeCommandLineAPI: true,
219-
},
220-
false, false);
221-
222-
if ('error' in result) {
223-
return formatError('Cannot find $0');
218+
const remoteObject = await contextNode.resolveToObject(undefined, executionContextId);
219+
if (!remoteObject) {
220+
throw new Error('Cannot execute JavaScript because remote object cannot be resolved');
224221
}
225222

226-
return await EvaluateAction.execute(functionDeclaration, [result.object], executionContext, {throwOnSideEffect});
223+
return await EvaluateAction.execute(functionDeclaration, [remoteObject], executionContext, {throwOnSideEffect});
227224
}
228225

229226
const MAX_OBSERVATION_BYTE_LENGTH = 25_000;
@@ -640,7 +637,7 @@ const data = {
640637
const result = await Promise.race([
641638
this.#execJs(
642639
functionDeclaration,
643-
{throwOnSideEffect},
640+
{throwOnSideEffect, contextNode: this.context?.getItem() || null},
644641
),
645642
new Promise<never>((_, reject) => {
646643
setTimeout(

0 commit comments

Comments
 (0)