From 6b5422d0d1a7aa44a748ceb03a6a83f56d6ea03a Mon Sep 17 00:00:00 2001 From: Vellum Assistant Date: Mon, 23 Feb 2026 21:35:40 -0500 Subject: [PATCH] fix: narrow target-app-hints to avoid false-positive matches - Add word boundary (\b) before action verb group in contextPattern to prevent partial matches like "login" matching "in" or "domain" matching "in" - Switch Cursor from simple \bcursor\b to contextPattern('cursor') so "move cursor to the submit button" no longer triggers a match - Switch Zoom from simple \bzoom\b to contextPattern('zoom') so "zoom in on the chart" no longer triggers a match - Reorder iTerm before Terminal in APP_HINTS array so "open terminal in iterm2" resolves to iTerm instead of Terminal - Add regression tests for all five false-positive scenarios Co-Authored-By: Claude Opus 4.6 --- .../src/__tests__/target-app-hints.test.ts | 41 +++++++++++++++++-- assistant/src/daemon/target-app-hints.ts | 16 ++++---- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/assistant/src/__tests__/target-app-hints.test.ts b/assistant/src/__tests__/target-app-hints.test.ts index 9024cd8f100..66a1dba602d 100644 --- a/assistant/src/__tests__/target-app-hints.test.ts +++ b/assistant/src/__tests__/target-app-hints.test.ts @@ -60,8 +60,13 @@ describe('resolveComputerUseTargetAppHint', () => { expect(result).toEqual({ appName: 'Discord', bundleId: 'com.hnc.Discord' }); }); - test('matches "zoom"', () => { - const result = resolveComputerUseTargetAppHint('join the zoom meeting'); + test('matches "open zoom"', () => { + const result = resolveComputerUseTargetAppHint('open zoom and join the call'); + expect(result).toEqual({ appName: 'zoom.us', bundleId: 'us.zoom.xos' }); + }); + + test('matches "zoom app"', () => { + const result = resolveComputerUseTargetAppHint('check the zoom app'); expect(result).toEqual({ appName: 'zoom.us', bundleId: 'us.zoom.xos' }); }); @@ -102,6 +107,11 @@ describe('resolveComputerUseTargetAppHint', () => { const result = resolveComputerUseTargetAppHint('use iterm2 for this'); expect(result).toEqual({ appName: 'iTerm', bundleId: 'com.googlecode.iterm2' }); }); + + test('"open terminal in iterm2" resolves to iTerm', () => { + const result = resolveComputerUseTargetAppHint('open terminal in iterm2'); + expect(result).toEqual({ appName: 'iTerm', bundleId: 'com.googlecode.iterm2' }); + }); }); // ── IDEs ─────────────────────────────────────────────────────────── @@ -121,11 +131,16 @@ describe('resolveComputerUseTargetAppHint', () => { expect(result).toEqual({ appName: 'Visual Studio Code', bundleId: 'com.microsoft.VSCode' }); }); - test('matches "cursor"', () => { + test('matches "open cursor"', () => { const result = resolveComputerUseTargetAppHint('open cursor and edit the file'); expect(result).toEqual({ appName: 'Cursor', bundleId: 'com.todesktop.230313mzl4w4u92' }); }); + test('matches "cursor app"', () => { + const result = resolveComputerUseTargetAppHint('check the cursor app'); + expect(result).toEqual({ appName: 'Cursor', bundleId: 'com.todesktop.230313mzl4w4u92' }); + }); + test('matches "xcode"', () => { const result = resolveComputerUseTargetAppHint('build the project in xcode'); expect(result).toEqual({ appName: 'Xcode', bundleId: 'com.apple.dt.Xcode' }); @@ -245,6 +260,26 @@ describe('resolveComputerUseTargetAppHint', () => { expect(result).toBeUndefined(); }); + test('"move cursor to the submit button" does NOT return Cursor', () => { + const result = resolveComputerUseTargetAppHint('move cursor to the submit button'); + expect(result).toBeUndefined(); + }); + + test('"zoom in on the chart" does NOT return Zoom', () => { + const result = resolveComputerUseTargetAppHint('zoom in on the chart'); + expect(result).toBeUndefined(); + }); + + test('"login terminal" does NOT return Terminal (word boundary)', () => { + const result = resolveComputerUseTargetAppHint('login terminal'); + expect(result).toBeUndefined(); + }); + + test('"domain mail server" does NOT return Mail (word boundary)', () => { + const result = resolveComputerUseTargetAppHint('domain mail server'); + expect(result).toBeUndefined(); + }); + test('empty string returns undefined', () => { const result = resolveComputerUseTargetAppHint(''); expect(result).toBeUndefined(); diff --git a/assistant/src/daemon/target-app-hints.ts b/assistant/src/daemon/target-app-hints.ts index 815319baa48..32f0a5a0208 100644 --- a/assistant/src/daemon/target-app-hints.ts +++ b/assistant/src/daemon/target-app-hints.ts @@ -19,7 +19,7 @@ function contextPattern(word: string): RegExp { // Deliberately excludes "the" — too many false positives // ("the settings in the config", "the messages carefully"). return new RegExp( - `(?:(?:(?:open|launch|switch\\s+to|in|test|qa|check|use)\\s+)${word}|${word}\\s+app)\\b`, + `(?:(?:(?:^|\\b)(?:open|launch|switch\\s+to|in|test|qa|check|use)\\s+)${word}|\\b${word}\\s+app)\\b`, 'i', ); } @@ -79,7 +79,7 @@ export const APP_HINTS: AppHintEntry[] = [ bundleId: 'com.hnc.Discord', }, { - patterns: [/\bzoom\b/], + patterns: [contextPattern('zoom')], appName: 'zoom.us', bundleId: 'us.zoom.xos', }, @@ -94,16 +94,16 @@ export const APP_HINTS: AppHintEntry[] = [ appName: 'Warp', bundleId: 'dev.warp.Warp-Stable', }, - { - patterns: [contextPattern('terminal')], - appName: 'Terminal', - bundleId: 'com.apple.Terminal', - }, { patterns: [/\biterm2?\b/], appName: 'iTerm', bundleId: 'com.googlecode.iterm2', }, + { + patterns: [contextPattern('terminal')], + appName: 'Terminal', + bundleId: 'com.apple.Terminal', + }, // IDEs { patterns: [/\b(vs\s*code|visual\s+studio\s+code)\b/], @@ -111,7 +111,7 @@ export const APP_HINTS: AppHintEntry[] = [ bundleId: 'com.microsoft.VSCode', }, { - patterns: [/\bcursor\b/], + patterns: [contextPattern('cursor')], appName: 'Cursor', bundleId: 'com.todesktop.230313mzl4w4u92', },