057c2c85fc
web/src/lib/slashExec.ts and web/src/components/SlashPopover.tsx had zero
importers since the React composer was replaced by the PTY-embedded TUI
(f49afd3122) — exactly what web/AGENTS.md forbids, now orphaned. Their
parseSlash still carried the `(.*)` newline bug and lacked the `prefill`
variant. Desktop and the TUI each hand-rolled the same slash split and the
same command.dispatch narrowing; the multi-line fix (#41323, #55510) had to
be applied to each copy separately.
Sites:
web/src/lib/slashExec.ts::executeSlash/parseSlash/parseCommandDispatch -> deleted
web/src/components/SlashPopover.tsx::SlashPopover -> deleted
apps/desktop/src/lib/chat-runtime.ts::parseSlashCommand -> apps/shared/src/slash.ts::parseSlashCommand
apps/desktop/src/lib/chat-runtime.ts::parseCommandDispatch -> apps/shared/src/slash.ts::parseCommandDispatch
apps/desktop/src/lib/chat-runtime.ts::SLASH_COMMAND_RE -> apps/shared/src/slash.ts::SLASH_COMMAND_RE
apps/desktop/src/app/types.ts::*CommandDispatchResponse (5 interfaces) -> apps/shared/src/slash.ts
ui-tui/src/domain/slash.ts::parseSlashCommand/looksLikeSlashCommand -> apps/shared/src/slash.ts
ui-tui/src/lib/rpc.ts::asCommandDispatch -> apps/shared/src/slash.ts::parseCommandDispatch
ui-tui/src/gatewayTypes.ts::CommandDispatchResponse -> apps/shared/src/slash.ts
9 desktop importers + 3 TUI importers repointed.
Behavior change: desktop `parseSlashCommand` now lower-cases the command
name like the TUI, backend `resolve_command` and `slash.exec` already do
(`/Help` resolved before via the case-insensitive backend; local desktop
action lookups were case-sensitive). TUI's parsed result no longer carries
the redundant `cmd` echo (no consumer read it).
Tests: apps/shared/src/slash.test.ts (parseSlashCommand multi-line /
newline-boundary / degenerate cases; parseCommandDispatch every variant +
malformed rejection). Sabotage: restoring `(.*)` in SLASH_PARTS_RE fails
2 tests; restored -> 7 pass. Desktop chat-runtime.test.ts and TUI
asCommandDispatch.test.ts cases moved here; slashParity.test.ts repointed.
73 lines
3.3 KiB
TypeScript
73 lines
3.3 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import { looksLikeSlashCommand, parseCommandDispatch, parseSlashCommand } from './slash'
|
|
|
|
describe('parseSlashCommand', () => {
|
|
it('splits the name off a single separator and lower-cases it', () => {
|
|
expect(parseSlashCommand('/cron add daily')).toEqual({ arg: 'add daily', name: 'cron' })
|
|
expect(parseSlashCommand('/Exit ')).toEqual({ arg: '', name: 'exit' })
|
|
})
|
|
|
|
it('keeps a multi-line argument byte-for-byte (#41323, #55510)', () => {
|
|
const arg = 'first line\nsecond line\n\n indented tail'
|
|
|
|
expect(parseSlashCommand(`/pr-triage ${arg}`)).toEqual({ arg, name: 'pr-triage' })
|
|
expect(parseSlashCommand('/goal ship it').arg).toBe('ship it')
|
|
})
|
|
|
|
it('takes the name across a newline boundary like the CLI and gateway (split on any whitespace)', () => {
|
|
expect(parseSlashCommand('/goal\npasted block')).toEqual({ arg: 'pasted block', name: 'goal' })
|
|
})
|
|
|
|
it('treats a bare or space-separated slash as no command (CLI parity)', () => {
|
|
expect(parseSlashCommand('/')).toEqual({ arg: '', name: '' })
|
|
expect(parseSlashCommand('/ ')).toEqual({ arg: '', name: '' })
|
|
expect(parseSlashCommand('/ some words')).toEqual({ arg: '', name: '' })
|
|
})
|
|
|
|
it('recognises a command only at position 0 with a single bare segment', () => {
|
|
expect(looksLikeSlashCommand('/help')).toBe(true)
|
|
expect(looksLikeSlashCommand('/goal do it')).toBe(true)
|
|
expect(looksLikeSlashCommand('/usr/local/bin')).toBe(false)
|
|
expect(looksLikeSlashCommand('run /clean')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('parseCommandDispatch', () => {
|
|
it('parses every variant of the command.dispatch union and keeps notice/display', () => {
|
|
expect(parseCommandDispatch({ type: 'exec', output: 'hi' })).toEqual({ type: 'exec', output: 'hi' })
|
|
expect(parseCommandDispatch({ type: 'plugin' })).toEqual({ type: 'plugin', output: undefined })
|
|
expect(parseCommandDispatch({ type: 'alias', target: 'help' })).toEqual({ type: 'alias', target: 'help' })
|
|
expect(parseCommandDispatch({ type: 'skill', name: 'x', message: 'do', display: '/x' })).toEqual({
|
|
type: 'skill',
|
|
name: 'x',
|
|
message: 'do',
|
|
display: '/x'
|
|
})
|
|
// /goal set answers {type:send, notice, message}; dropping the notice once
|
|
// made /goal look like it did nothing on the desktop.
|
|
expect(parseCommandDispatch({ type: 'send', notice: '⊙ Goal set', message: 'do the thing' })).toEqual({
|
|
type: 'send',
|
|
message: 'do the thing',
|
|
notice: '⊙ Goal set',
|
|
display: undefined
|
|
})
|
|
expect(parseCommandDispatch({ type: 'prefill', message: 'edit me', notice: '↶ rewound' })).toEqual({
|
|
type: 'prefill',
|
|
message: 'edit me',
|
|
notice: '↶ rewound'
|
|
})
|
|
})
|
|
|
|
it('rejects malformed payloads', () => {
|
|
expect(parseCommandDispatch(null)).toBeNull()
|
|
expect(parseCommandDispatch([{ type: 'exec' }])).toBeNull()
|
|
expect(parseCommandDispatch({ type: 'alias' })).toBeNull()
|
|
expect(parseCommandDispatch({ type: 'skill', name: 1 })).toBeNull()
|
|
expect(parseCommandDispatch({ type: 'send' })).toBeNull()
|
|
expect(parseCommandDispatch({ type: 'send', message: 42 })).toBeNull()
|
|
expect(parseCommandDispatch({ type: 'prefill', notice: 'x' })).toBeNull()
|
|
expect(parseCommandDispatch({ type: 'nope' })).toBeNull()
|
|
})
|
|
})
|