Skip to content

Commit 98fd4d7

Browse files
committed
Verify inactive Firefox tabs before completion
1 parent 145bb45 commit 98fd4d7

2 files changed

Lines changed: 77 additions & 1 deletion

File tree

src/firefox/src/agent/agent.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12497,7 +12497,10 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
1249712497
if (this._isActionMode(mode)) {
1249812498
try {
1249912499
const tab = await browser.tabs.get(tabId);
12500-
if (tab?.active) {
12500+
// Scheduled URL-target tabs intentionally stay inactive. Firefox can
12501+
// execute scripts in and capture those tabs directly, so completion
12502+
// verification must not depend on active-tab state.
12503+
if (tab) {
1250112504
// Probe page URL, title, and "work in progress" signals: open
1250212505
// dialogs/modals and visible forms. If any of these are present
1250312506
// while the model claims it created/added/saved/submitted, the

test/run.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,79 @@ test('remaining model-facing screenshot fallbacks apply redaction', () => {
11031103
'Firefox done verification should not embed base64 in the done result');
11041104
});
11051105

1106+
test('Firefox done verifies inactive scheduled tabs before accepting completion', async () => {
1107+
const originalBrowser = globalThis.browser;
1108+
const tabId = 417;
1109+
let executeScriptCalls = 0;
1110+
const captures = [];
1111+
try {
1112+
globalThis.browser = {
1113+
tabs: {
1114+
get: async () => ({
1115+
id: tabId,
1116+
active: false,
1117+
url: 'https://example.test/items/new',
1118+
title: 'Create item',
1119+
}),
1120+
executeScript: async () => {
1121+
executeScriptCalls += 1;
1122+
return [{
1123+
url: 'https://example.test/items/new',
1124+
title: 'Create item',
1125+
openDialogCount: 1,
1126+
dialogTitles: ['Create item'],
1127+
visibleFormCount: 1,
1128+
relevantFormCount: 1,
1129+
formDescriptors: [{
1130+
label: 'Create item',
1131+
relevant: true,
1132+
utility: false,
1133+
editableCount: 2,
1134+
submitCount: 1,
1135+
}],
1136+
liveRegionMessages: [],
1137+
successMessages: [],
1138+
}];
1139+
},
1140+
captureTab: async (capturedTabId, options) => {
1141+
captures.push({ tabId: capturedTabId, options });
1142+
return 'data:image/png;base64,AA==';
1143+
},
1144+
sendMessage: async () => ({}),
1145+
},
1146+
};
1147+
1148+
const agent = new AgentFx({
1149+
getActive: () => ({ supportsVision: true }),
1150+
getVisionProvider: async () => null,
1151+
});
1152+
agent.conversationModes.set(tabId, 'act');
1153+
1154+
const result = await agent.executeTool(tabId, 'done', {
1155+
summary: 'Created the item.',
1156+
outcome: 'success',
1157+
});
1158+
1159+
assert.equal(executeScriptCalls, 1,
1160+
'inactive Firefox runs should still probe dialogs and forms before done');
1161+
assert.deepEqual(captures, [{
1162+
tabId,
1163+
options: { format: 'png', quality: 80 },
1164+
}], 'inactive Firefox runs should capture their own tab for done verification');
1165+
assert.equal(result.blockedDone, true,
1166+
'an unfinished inactive-tab form should block a successful done result');
1167+
1168+
const source = fs.readFileSync(path.join(ROOT, 'src/firefox/src/agent/agent.js'), 'utf8');
1169+
const doneStart = source.indexOf("if (name === 'done')");
1170+
const doneEnd = source.indexOf('// Network & download tools', doneStart);
1171+
assert.doesNotMatch(source.slice(doneStart, doneEnd), /if \(tab\?\.active\)/,
1172+
'Firefox done verification must not be gated on active-tab state');
1173+
} finally {
1174+
if (originalBrowser === undefined) delete globalThis.browser;
1175+
else globalThis.browser = originalBrowser;
1176+
}
1177+
});
1178+
11061179
test('firefox auto and media screenshot helpers redact model-facing data URLs', () => {
11071180
const source = fs.readFileSync(path.join(ROOT, 'src/firefox/src/agent/agent.js'), 'utf8');
11081181
// Signature may include optional opts for Chrome call-site parity.

0 commit comments

Comments
 (0)