fix(tui): prefer exact slash command matches (#15813)

This commit is contained in:
Gille 2026-04-28 11:22:26 -06:00 committed by GitHub
parent b53a091b97
commit a1921c43cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 59 additions and 13 deletions

View file

@ -298,6 +298,45 @@ describe('createSlashHandler', () => {
expect(ctx.transcript.panel).toHaveBeenCalledWith(expect.any(String), expect.any(Array)) expect(ctx.transcript.panel).toHaveBeenCalledWith(expect.any(String), expect.any(Array))
}) })
it('lets exact catalog commands win over longer prefix matches', async () => {
const ctx = buildCtx({
local: {
catalog: {
canon: {
'/status': '/status',
'/statusbar': '/statusbar'
}
}
}
})
expect(createSlashHandler(ctx)('/status')).toBe(true)
await vi.waitFor(() => {
expect(ctx.gateway.gw.request).toHaveBeenCalledWith('slash.exec', {
command: 'status',
session_id: null
})
})
expect(ctx.transcript.sys).not.toHaveBeenCalledWith(expect.stringContaining('ambiguous command'))
})
it('keeps ambiguous prefix handling when there is no exact catalog match', () => {
const ctx = buildCtx({
local: {
catalog: {
canon: {
'/status': '/status',
'/statusbar': '/statusbar'
}
}
}
})
expect(createSlashHandler(ctx)('/stat')).toBe(true)
expect(ctx.transcript.sys).toHaveBeenCalledWith('ambiguous command: /status, /statusbar')
expect(ctx.gateway.gw.request).not.toHaveBeenCalled()
})
it('falls through to command.dispatch for skill commands and sends the message', async () => { it('falls through to command.dispatch for skill commands and sends the message', async () => {
const skillMessage = 'Use this skill to do X.\n\n## Steps\n1. First step' const skillMessage = 'Use this skill to do X.\n\n## Steps\n1. First step'

View file

@ -47,7 +47,13 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b
if (catalog?.canon) { if (catalog?.canon) {
const needle = `/${parsed.name}`.toLowerCase() const needle = `/${parsed.name}`.toLowerCase()
const exact = Object.entries(catalog.canon).find(([alias]) => alias.toLowerCase() === needle)?.[1]
if (exact) {
if (exact.toLowerCase() !== needle) {
return handler(`${exact}${argTail}`)
}
} else {
const matches = [ const matches = [
...new Set( ...new Set(
Object.entries(catalog.canon) Object.entries(catalog.canon)
@ -66,6 +72,7 @@ export function createSlashHandler(ctx: SlashHandlerContext): (cmd: string) => b
return true return true
} }
} }
}
gw.request<SlashExecResponse>('slash.exec', { command: cmd.slice(1), session_id: sid }) gw.request<SlashExecResponse>('slash.exec', { command: cmd.slice(1), session_id: sid })
.then(r => { .then(r => {