Skip to content

Commit 2d13e51

Browse files
author
Hardening v2.7
committed
fix(direct-assist): fall back to plain text when a skill-prefix guess doesn't resolve
resolveDirectAssistSkill treated any leading "/word" or "$word" as a mandatory skill invocation and rejected the whole request with SKILL_NOT_FOUND when that word wasn't a real skill id -- so ordinary text like "$50 is that a fair price for this laptop?" or "/explain this regex" broke Direct Assist end-to-end. The renderer's matching prefix detector (directAssistSkillId) is advisory only; main is authoritative. Only an explicit UI skill selection (skillId) is unambiguous enough to hard-fail on a miss -- a bare text-prefix guess that doesn't resolve now falls back to sending the request as plain text.
1 parent 1556523 commit 2d13e51

2 files changed

Lines changed: 24 additions & 0 deletions

File tree

electron/ipcHandlers.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6209,6 +6209,15 @@ export function initializeIpcHandlers(appState: AppState): void {
62096209

62106210
const skill = SkillsManager.getInstance().getSkill(requestedSkillId);
62116211
if (!skill) {
6212+
// A slash/dollar-prefixed leading word that doesn't resolve to a real
6213+
// skill is ordinary text far more often than an intended skill
6214+
// invocation ("$50 is that a fair price...", "/explain this regex") —
6215+
// the renderer's matching detector (directAssistSkillId) is advisory
6216+
// only, main is authoritative. Only hard-fail when a skill was
6217+
// explicitly selected via the UI's skill picker (explicitSkillId),
6218+
// where there is no ambiguity about intent; a bare text-prefix guess
6219+
// that misses just falls back to plain text.
6220+
if (!explicitSkillId) return { currentRequest: request.currentRequest, skill: null };
62126221
return { error: directAssistError('SKILL_NOT_FOUND', 'The requested Direct Assist skill was not found.') };
62136222
}
62146223
if (skill.enabled === false) {

electron/services/__tests__/DirectAssistIpcBridge2026_08_29.test.mjs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,21 @@ test('main resolves and strips enabled skills, including underscore IDs', () =>
9494
assert.match(ipc, /instructions: skill\.instructions/);
9595
});
9696

97+
test('an unresolved text-prefix skill guess falls back to plain text instead of rejecting the request', () => {
98+
// A leading "/" or "$" word that doesn't resolve to a real skill is ordinary
99+
// text far more often than an intended skill invocation ("$50 is that a
100+
// fair price...", "/explain this regex") — only an explicit UI skill
101+
// selection (skillId) should hard-fail with SKILL_NOT_FOUND on a miss.
102+
const resolveStart = ipc.indexOf('const resolveDirectAssistSkill =');
103+
const resolveEnd = ipc.indexOf('\n safeHandle(', resolveStart);
104+
const resolveBlock = ipc.slice(resolveStart, resolveEnd);
105+
assert.ok(resolveStart >= 0 && resolveEnd > resolveStart);
106+
assert.match(
107+
resolveBlock,
108+
/if \(!explicitSkillId\) return \{ currentRequest: request\.currentRequest, skill: null \};\s*\n\s*return \{ error: directAssistError\('SKILL_NOT_FOUND'/,
109+
);
110+
});
111+
97112
test('attachments and captured page data are validated before Direct Assist dispatch', () => {
98113
assert.match(ipc, /const DIRECT_ASSIST_MAX_IMAGES = 5/);
99114
assert.match(ipc, /candidate\.imagePaths\.length > DIRECT_ASSIST_MAX_IMAGES/);

0 commit comments

Comments
 (0)