diff --git a/apps/desktop/src/pipeline/providers/formatting/formatter-prompt.ts b/apps/desktop/src/pipeline/providers/formatting/formatter-prompt.ts index 840ed143..0badf45a 100644 --- a/apps/desktop/src/pipeline/providers/formatting/formatter-prompt.ts +++ b/apps/desktop/src/pipeline/providers/formatting/formatter-prompt.ts @@ -7,7 +7,7 @@ import { GetAccessibilityContextResult } from "@amical/types"; /** * Application type for formatting context */ -export type AppType = "email" | "chat" | "notes" | "amical-notes" | "default"; +export type AppType = "email" | "chat" | "notes" | "amical-notes" | "terminal" | "default"; /** * App-type specific formatting rules inserted into the system prompt @@ -38,6 +38,8 @@ const APP_TYPE_RULES: Record = { - Use code blocks (\`\`\`) for technical content, commands, or code snippets - Keep formatting minimal and purposeful - don't over-format simple content - Preserve natural speech flow while adding structure where it improves clarity`, + // Terminal apps use universal rules only — no app-specific overrides. + terminal: "", default: "", }; @@ -106,6 +108,8 @@ We decided to ship on Friday. - Benchmark performance - Update docs`, + // Terminal apps use universal examples only — no app-specific examples. + terminal: "", default: `### Filler removal + grammar fix: so the main issue is that um we need more time The main issue is that we need more time. @@ -284,6 +288,14 @@ const BUNDLE_TO_TYPE: Record = { "com.evernote.Evernote": "notes", "notion.id": "notes", "com.agiletortoise.Drafts-OSX": "notes", + "com.apple.Terminal": "terminal", + "com.googlecode.iterm2": "terminal", + "com.mitchellh.ghostty": "terminal", + "dev.warp.Warp-Stable": "terminal", + "net.kovidgoyal.kitty": "terminal", + "com.github.wez.wezterm": "terminal", + "io.alacritty": "terminal", + "co.zeit.hyper": "terminal", }; // Browser bundle identifiers diff --git a/apps/desktop/src/pipeline/providers/transcription/amical-cloud-provider.ts b/apps/desktop/src/pipeline/providers/transcription/amical-cloud-provider.ts index 5c8c4fbc..3dc94f45 100644 --- a/apps/desktop/src/pipeline/providers/transcription/amical-cloud-provider.ts +++ b/apps/desktop/src/pipeline/providers/transcription/amical-cloud-provider.ts @@ -15,6 +15,18 @@ import { type CloudErrorResponse, } from "../../../types/error"; +// Strip ANSI escape sequences and control characters from accessibility text. +// Terminal apps expose raw terminal output via the accessibility API which +// contains escape codes that degrade transcription quality. +const ANSI_REGEX = + // eslint-disable-next-line no-control-regex + /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><~]/g; + +function stripAnsiSequences(text: string | undefined | null): string | undefined { + if (!text) return undefined; + return text.replace(ANSI_REGEX, ""); +} + // Type guard to validate error codes from server const isValidErrorCode = (code: string | undefined): code is ErrorCode => code !== undefined && Object.values(ErrorCodes).includes(code as ErrorCode); @@ -283,28 +295,35 @@ export class AmicalCloudProvider implements TranscriptionProvider { enabled: enableFormatting, }, sharedContext: this.currentAccessibilityContext - ? { - selectedText: - this.currentAccessibilityContext.context?.textSelection - ?.selectedText, - beforeText: - this.currentAccessibilityContext.context?.textSelection - ?.preSelectionText, - afterText: - this.currentAccessibilityContext.context?.textSelection - ?.postSelectionText, - appType: detectApplicationType( + ? (() => { + const appType = detectApplicationType( this.currentAccessibilityContext, - ), - appBundleId: - this.currentAccessibilityContext.context?.application - ?.bundleIdentifier, - appName: - this.currentAccessibilityContext.context?.application?.name, - appUrl: - this.currentAccessibilityContext.context?.windowInfo?.url, - surroundingContext: "", // Empty for now, future enhancement - } + ); + const isTerminal = appType === "terminal"; + return { + selectedText: stripAnsiSequences( + this.currentAccessibilityContext!.context?.textSelection + ?.selectedText, + ), + beforeText: stripAnsiSequences( + this.currentAccessibilityContext!.context?.textSelection + ?.preSelectionText, + ), + afterText: stripAnsiSequences( + this.currentAccessibilityContext!.context?.textSelection + ?.postSelectionText, + ), + appType: isTerminal ? "default" : appType, + appBundleId: + this.currentAccessibilityContext!.context?.application + ?.bundleIdentifier, + appName: + this.currentAccessibilityContext!.context?.application?.name, + appUrl: + this.currentAccessibilityContext!.context?.windowInfo?.url, + surroundingContext: "", + }; + })() : undefined, }), }); diff --git a/apps/desktop/src/pipeline/providers/transcription/whisper-provider.ts b/apps/desktop/src/pipeline/providers/transcription/whisper-provider.ts index c95ebeaa..fc5acd18 100644 --- a/apps/desktop/src/pipeline/providers/transcription/whisper-provider.ts +++ b/apps/desktop/src/pipeline/providers/transcription/whisper-provider.ts @@ -277,8 +277,13 @@ export class WhisperProvider implements TranscriptionProvider { return aggregatedTranscription; } - const beforeText = + const rawBeforeText = accessibilityContext?.context?.textSelection?.preSelectionText; + // Strip ANSI escape sequences that terminal apps expose via accessibility + // context — they degrade Whisper recognition quality. + const beforeText = rawBeforeText + // eslint-disable-next-line no-control-regex + ?.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><~]/g, ""); if (beforeText && beforeText.trim().length > 0) { logger.transcription.debug( `Generated initial prompt from before text: "${beforeText}"`,