diff --git a/src/chrome/src/agent/tools.js b/src/chrome/src/agent/tools.js index 6fdbf0aec8..158080d253 100644 --- a/src/chrome/src/agent/tools.js +++ b/src/chrome/src/agent/tools.js @@ -1538,6 +1538,32 @@ function askResearchConsentTool(tool) { }; } +function ordinaryClarifyTool(tool) { + const properties = { ...tool.function.parameters.properties }; + delete properties.purpose; + delete properties.research_request; + delete properties.approve_option; + return { + ...tool, + function: { + ...tool.function, + parameters: { + ...tool.function.parameters, + properties: { + ...properties, + require_explicit_answer: { + ...properties.require_explicit_answer, + description: 'Wait for a direct user reply and ignore the global clarify timeout/Instant setting. Use when consent or a user-controlled value must not be inferred or auto-selected.', + }, + }, + required: (tool.function.parameters.required || []).filter(name => ( + name !== 'purpose' && name !== 'research_request' && name !== 'approve_option' + )), + }, + }, + }; +} + /** * Get tools filtered by mode. * @@ -1570,7 +1596,8 @@ export function getToolsForMode(mode, opts = {}) { } if (opts.researchEscalationEnabled !== true) { base = base.filter(t => t.function.name !== 'delegate_research' - && !(normalizedMode === 'ask' && t.function.name === 'clarify')); + && !(normalizedMode === 'ask' && t.function.name === 'clarify')) + .map(t => (t.function.name === 'clarify' ? ordinaryClarifyTool(t) : t)); } const requestedTreePageChars = tier !== 'compact' && Number(opts.accessibilityTreeMaxChars) === EXPANDED_TREE_PAGE_CHARS diff --git a/src/firefox/src/agent/tools.js b/src/firefox/src/agent/tools.js index fd55ba6370..59f5491703 100644 --- a/src/firefox/src/agent/tools.js +++ b/src/firefox/src/agent/tools.js @@ -1385,6 +1385,32 @@ function askResearchConsentTool(tool) { }; } +function ordinaryClarifyTool(tool) { + const properties = { ...tool.function.parameters.properties }; + delete properties.purpose; + delete properties.research_request; + delete properties.approve_option; + return { + ...tool, + function: { + ...tool.function, + parameters: { + ...tool.function.parameters, + properties: { + ...properties, + require_explicit_answer: { + ...properties.require_explicit_answer, + description: 'Wait for a direct user reply and ignore the global clarify timeout/Instant setting. Use when consent or a user-controlled value must not be inferred or auto-selected.', + }, + }, + required: (tool.function.parameters.required || []).filter(name => ( + name !== 'purpose' && name !== 'research_request' && name !== 'approve_option' + )), + }, + }, + }; +} + /** * Get tools filtered by mode. * @@ -1416,7 +1442,8 @@ export function getToolsForMode(mode, opts = {}) { } if (opts.researchEscalationEnabled !== true) { base = base.filter(t => t.function.name !== 'delegate_research' - && !(normalizedMode === 'ask' && t.function.name === 'clarify')); + && !(normalizedMode === 'ask' && t.function.name === 'clarify')) + .map(t => (t.function.name === 'clarify' ? ordinaryClarifyTool(t) : t)); } const requestedTreePageChars = tier !== 'compact' && Number(opts.accessibilityTreeMaxChars) === EXPANDED_TREE_PAGE_CHARS diff --git a/test/run.js b/test/run.js index 6a54370325..258582da46 100644 --- a/test/run.js +++ b/test/run.js @@ -2354,6 +2354,7 @@ test('research escalation is opt-in, tier-complete when enabled, and absent othe assert.equal(getTools('ask').some(tool => tool.function.name === 'delegate_research'), false, `${label}: default Ask exposed research delegation`); assert.equal(getTools('act').some(tool => tool.function.name === 'delegate_research'), false, `${label}: default Act exposed research delegation`); assert.ok(getTools('act').some(tool => tool.function.name === 'clarify'), `${label}: opt-in default removed normal Act clarification`); + const researchOnlyClarifyFields = ['purpose', 'research_request', 'approve_option']; for (const [mode, tier] of [['ask', 'full'], ['act', 'compact'], ['act', 'mid'], ['act', 'full']]) { const enabled = getTools(mode, { tier, researchEscalationEnabled: true }); assert.ok(enabled.some(tool => tool.function.name === 'delegate_research'), `${label}: ${mode}/${tier} did not expose research escalation`); @@ -2367,6 +2368,37 @@ test('research escalation is opt-in, tier-complete when enabled, and absent othe mode !== 'ask', `${label}: disabled research escalation must hide Ask clarification without removing normal ${mode} clarification`, ); + const disabledClarify = disabled.find(tool => tool.function.name === 'clarify'); + if (disabledClarify) { + for (const field of researchOnlyClarifyFields) { + assert.equal( + field in disabledClarify.function.parameters.properties, + false, + `${label}: disabled ${mode}/${tier} ordinary clarification exposed ${field}`, + ); + assert.equal( + disabledClarify.function.parameters.required.includes(field), + false, + `${label}: disabled ${mode}/${tier} ordinary clarification required ${field}`, + ); + } + assert.ok( + disabledClarify.function.parameters.properties.require_explicit_answer, + `${label}: disabled ${mode}/${tier} ordinary clarification lost explicit-answer support`, + ); + } + } + for (const [mode, tier] of [['dev', 'mid'], ['dev', 'full']]) { + const disabledClarify = getTools(mode, { tier, researchEscalationEnabled: false }) + .find(tool => tool.function.name === 'clarify'); + assert.ok(disabledClarify, `${label}: disabled research escalation removed normal ${mode}/${tier} clarification`); + for (const field of researchOnlyClarifyFields) { + assert.equal( + field in disabledClarify.function.parameters.properties, + false, + `${label}: disabled ${mode}/${tier} ordinary clarification exposed ${field}`, + ); + } } const askConsent = getTools('ask', { researchEscalationEnabled: true }) @@ -2390,8 +2422,18 @@ test('research escalation is opt-in, tier-complete when enabled, and absent othe const actClarify = getTools('act', { researchEscalationEnabled: true }) .find(tool => tool.function.name === 'clarify'); assert.ok(actClarify.function.parameters.properties.require_explicit_answer, `${label}: narrowing Ask changed normal Act clarification`); + for (const field of researchOnlyClarifyFields) { + assert.ok(actClarify.function.parameters.properties[field], `${label}: enabled Act clarification lost ${field}`); + } assert.deepEqual(actClarify.function.parameters.required, ['question']); assert.doesNotMatch(actClarify.function.description, /not a generic clarification tool/i); + + const defaultActClarify = getTools('act').find(tool => tool.function.name === 'clarify'); + assert.match( + defaultActClarify.function.parameters.properties.require_explicit_answer.description, + /user-controlled value must not be inferred or auto-selected/i, + `${label}: ordinary clarification still advertises research escalation semantics`, + ); } });