Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/chrome/src/context-menu-storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ export const SELECTION_SHORTCUT_ACTIONS = Object.freeze({
summarize: 'Summarize this selected text clearly and concisely.',
explain: 'Explain this selected text in plain language.',
quiz: 'Quiz me on this selected text. Ask one question at a time and wait for my answer.',
proofread: 'Proofread this selected text. Identify errors and provide a corrected version while preserving its meaning and tone.',
proofread: 'Proofread this selected text. First verify that the selection is complete enough to edit. Never infer or reconstruct text beyond its boundaries. If an edge is visibly cut mid-word or the passage is otherwise clearly incomplete, say so and ask the user to select the complete passage instead of supplying a corrected version. For a complete selection, tie every claimed error to exact selected wording, distinguish actual errors from optional style suggestions, and provide one complete corrected version that fixes every listed error without unrelated additions.',
humanize: 'Rewrite this selected text so it reads as human writing rather than AI output. Keep every claim, the language, and the author\'s intent; return only the rewritten text.',
});

// Some fixed actions carry model-only guardrails that should not crowd the
// user-visible chat bubble or history title. Match the complete trusted action
// text so custom prompts that happen to start similarly remain untouched.
const SELECTION_SHORTCUT_DISPLAY_INSTRUCTIONS = Object.freeze({
proofread: 'Proofread this selected text.',
});

// Selected-text runs carry no tools, so `load_skill` cannot rescue a writing
// request mid-run: a prose skill either rides in at run start or never. Keep
// this limited to explicit structured writing actions; `custom` is the
Expand Down Expand Up @@ -168,6 +175,12 @@ export function formatSelectionPromptForDisplay(promptText) {
instruction = instruction.slice(CUSTOM_QUESTION_PREFIX.length).trim();
} else {
instruction = stripResponseLanguageInstruction(instruction);
for (const [actionId, displayInstruction] of Object.entries(SELECTION_SHORTCUT_DISPLAY_INSTRUCTIONS)) {
if (instruction === SELECTION_SHORTCUT_ACTIONS[actionId]) {
instruction = displayInstruction;
break;
}
}
if (instruction === GENERIC_CONTEXT_MENU_INSTRUCTION) instruction = '';
}

Expand Down
15 changes: 14 additions & 1 deletion src/firefox/src/context-menu-storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ export const SELECTION_SHORTCUT_ACTIONS = Object.freeze({
summarize: 'Summarize this selected text clearly and concisely.',
explain: 'Explain this selected text in plain language.',
quiz: 'Quiz me on this selected text. Ask one question at a time and wait for my answer.',
proofread: 'Proofread this selected text. Identify errors and provide a corrected version while preserving its meaning and tone.',
proofread: 'Proofread this selected text. First verify that the selection is complete enough to edit. Never infer or reconstruct text beyond its boundaries. If an edge is visibly cut mid-word or the passage is otherwise clearly incomplete, say so and ask the user to select the complete passage instead of supplying a corrected version. For a complete selection, tie every claimed error to exact selected wording, distinguish actual errors from optional style suggestions, and provide one complete corrected version that fixes every listed error without unrelated additions.',
humanize: 'Rewrite this selected text so it reads as human writing rather than AI output. Keep every claim, the language, and the author\'s intent; return only the rewritten text.',
});

// Some fixed actions carry model-only guardrails that should not crowd the
// user-visible chat bubble or history title. Match the complete trusted action
// text so custom prompts that happen to start similarly remain untouched.
const SELECTION_SHORTCUT_DISPLAY_INSTRUCTIONS = Object.freeze({
proofread: 'Proofread this selected text.',
});

// Selected-text runs carry no tools, so `load_skill` cannot rescue a writing
// request mid-run: a prose skill either rides in at run start or never. Keep
// this limited to explicit structured writing actions; `custom` is the
Expand Down Expand Up @@ -168,6 +175,12 @@ export function formatSelectionPromptForDisplay(promptText) {
instruction = instruction.slice(CUSTOM_QUESTION_PREFIX.length).trim();
} else {
instruction = stripResponseLanguageInstruction(instruction);
for (const [actionId, displayInstruction] of Object.entries(SELECTION_SHORTCUT_DISPLAY_INSTRUCTIONS)) {
if (instruction === SELECTION_SHORTCUT_ACTIONS[actionId]) {
instruction = displayInstruction;
break;
}
}
if (instruction === GENERIC_CONTEXT_MENU_INSTRUCTION) instruction = '';
}

Expand Down
25 changes: 25 additions & 0 deletions test/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -50109,6 +50109,13 @@ test('selection shortcut builds allowlisted prompts with an untrusted selection
assert.match(prompt, /<untrusted_page_content id="ctx-[^"]+">\nselected page words\n<\/untrusted_page_content>/, `${label}: ${action} should wrap only the page selection`);
}

const proofread = buildSelectionPrompt('A visibly cut wor', 'proofread');
assert.match(proofread, /Never infer or reconstruct text beyond its boundaries/, `${label}: proofreading may not invent missing source text`);
assert.match(proofread, /visibly cut mid-word/, `${label}: proofreading should detect an incomplete selection edge`);
assert.match(proofread, /tie every claimed error to exact selected wording/, `${label}: proofreading critiques must stay source-grounded`);
assert.match(proofread, /distinguish actual errors from optional style suggestions/, `${label}: proofreading should not present preferences as errors`);
assert.match(proofread, /one complete corrected version that fixes every listed error without unrelated additions/, `${label}: proofreading should reconcile its own issue list and avoid stale output`);

const localizedPreset = buildSelectionPrompt('这里有 Electron 和 Tauri', 'explain', '', 'zh');
assert.match(localizedPreset, /^Explain this selected text in plain language\. Respond in Chinese\./, `${label}: fixed selection actions should request the interface language`);
assert.match(localizedPreset, /This English template does not set the reply language/, `${label}: English explain/quiz templates should not override the interface language`);
Expand Down Expand Up @@ -51503,6 +51510,24 @@ test('selection prompt display formatter hides untrusted wrappers from the chat
`${label}: fixed actions should keep their instruction and show the selection cleanly`,
);

const proofread = buildSelectionPrompt('A visibly cut wor', 'proofread');
const proofreadDisplay = formatSelectionPromptForDisplay(proofread);
assert.equal(
proofreadDisplay,
'Proofread this selected text.\n\nSelected text:\nA visibly cut wor',
`${label}: proofread should keep its guardrails model-facing and use a concise display label`,
);
assert.doesNotMatch(proofreadDisplay, /Never infer|visibly cut mid-word|exact selected wording/, `${label}: proofread display must hide model-only guardrails`);
assert.ok(proofreadDisplay.indexOf('A visibly cut wor') < 140, `${label}: proofread history titles should reach the selected text before truncation`);
assert.match(proofread, /Never infer or reconstruct text beyond its boundaries/, `${label}: model prompt must retain the proofread guardrails`);

const localizedProofread = buildSelectionPrompt('需要校对的文本', 'proofread', '', 'zh');
assert.equal(
formatSelectionPromptForDisplay(localizedProofread),
'Proofread this selected text.\n\nSelected text:\n需要校对的文本',
`${label}: localized proofread prompts should use the same concise display label`,
);

const generic = buildContextMenuPrompt('native fallback');
assert.equal(
formatSelectionPromptForDisplay(generic),
Expand Down
Loading