A candidate list was cleared about two seconds after it appeared: once typing paused, the not-typing rule dismissed the visible overlay, so a click or Ctrl+Alt+Enter found nothing to insert. The rule now only stops new requests; a visible overlay stays until it is accepted, dismissed or goes stale. Terminals were excluded from suggestions because the whole screen buffer reads as the input and the last line is usually a status bar. The input line is now extracted (Claude Code/Codex >, starship, PowerShell, cmd and POSIX prompts, wrapped continuation lines) and used as the prefix; no prompt means no suggestion. Terminals stay out of phrase learning. Accepting with nothing shown and successful inserts are now logged.
76 lines
3.1 KiB
TypeScript
76 lines
3.1 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
||
import { decideSuggestion, extractTerminalPromptPrefix, isTerminalApp } from '../src/input-intelligence'
|
||
|
||
describe('terminal prompt extraction', () => {
|
||
it('takes the Claude Code input line and ignores the status line below it', () => {
|
||
const screen = [
|
||
'● Wrote 3 files',
|
||
'',
|
||
'╭──────────────────────────────────────╮',
|
||
'│ > 오늘 회의 내용을 정리해서 │',
|
||
'╰──────────────────────────────────────╯',
|
||
' ◑ OPUS 5 · 5 medium │ CTX ▕░░▏',
|
||
].join('\n')
|
||
expect(extractTerminalPromptPrefix(screen)).toBe('오늘 회의 내용을 정리해서')
|
||
})
|
||
|
||
it('joins wrapped continuation lines of a long prompt', () => {
|
||
const screen = ['──────────', '> 긴 문장을 쓰다 보면', ' 줄이 바뀌어서 이어진다', '──────────', ' ? for shortcuts'].join('\n')
|
||
expect(extractTerminalPromptPrefix(screen)).toBe('긴 문장을 쓰다 보면 줄이 바뀌어서 이어진다')
|
||
})
|
||
|
||
it('reads shell prompts', () => {
|
||
expect(extractTerminalPromptPrefix('PS C:\\Users\\me> git commit -m "고친')).toBe('git commit -m "고친')
|
||
expect(extractTerminalPromptPrefix('C:\\work> dir')).toBe('dir')
|
||
expect(extractTerminalPromptPrefix('me@host:~/app$ npm run')).toBe('npm run')
|
||
expect(extractTerminalPromptPrefix('❯ cargo build')).toBe('cargo build')
|
||
})
|
||
|
||
it('returns null when only output is on screen, and does not mistake percentages for prompts', () => {
|
||
expect(extractTerminalPromptPrefix('Compiling...\n100% 완료\nDone in 3s')).toBeNull()
|
||
expect(extractTerminalPromptPrefix('')).toBeNull()
|
||
})
|
||
|
||
it('an empty prompt yields an empty prefix', () => {
|
||
expect(extractTerminalPromptPrefix('output\n> ')).toBe('')
|
||
})
|
||
|
||
it('recognises terminal apps', () => {
|
||
expect(isTerminalApp('WindowsTerminal.exe')).toBe(true)
|
||
expect(isTerminalApp('KakaoTalk.exe')).toBe(false)
|
||
expect(isTerminalApp(null)).toBe(false)
|
||
})
|
||
})
|
||
|
||
describe('a visible suggestion survives a typing pause', () => {
|
||
const base = {
|
||
enabled: true,
|
||
modelAvailable: true,
|
||
composing: false,
|
||
hasSelection: false,
|
||
isPassword: false,
|
||
isEditable: true,
|
||
appName: 'KakaoTalk.exe',
|
||
excludedApps: [],
|
||
editedSinceFocus: false,
|
||
typedRecently: false,
|
||
prefix: '내일 몇 시에 볼까',
|
||
idleMs: 5000,
|
||
triggerDelayMs: 100,
|
||
minPrefixChars: 4,
|
||
sinceLastRequestMs: 60_000,
|
||
minIntervalMs: 5000,
|
||
requestsThisMinute: 0,
|
||
maxRequestsPerMinute: 6,
|
||
requestsToday: 0,
|
||
dailyBudget: 500,
|
||
}
|
||
|
||
it('keeps the overlay while the user reads and picks a candidate', () => {
|
||
expect(decideSuggestion({ ...base, overlayVisible: true })).toEqual({ action: 'skip', reason: 'already-visible' })
|
||
})
|
||
|
||
it('still refuses to start a new suggestion without typing', () => {
|
||
expect(decideSuggestion({ ...base, overlayVisible: false })).toEqual({ action: 'clear', reason: 'not-typing' })
|
||
})
|
||
})
|