fix: keep agent slash selection editable before send (#653)

Selecting a concrete /agent entry from the composer suggestions should prepare the command and leave focus in the editor so the user can type the required prompt. The Enter send shortcut still applies after the suggestion menu is closed, and exact non-agent slash commands keep their existing direct execution path when they remain highlighted.

Constraint: The desktop composer supports configurable Enter vs Ctrl/Cmd+Enter sending.
Rejected: Special-case only /agent entries | the highlighted suggestion should consistently win while the menu is open.
Confidence: high
Scope-risk: narrow
Directive: Keep ChatInput and EmptySession slash-key handling aligned with shared send shortcut behavior.
Tested: cd desktop && bun run test -- --run src/components/chat/composerUtils.test.ts src/components/chat/ChatInput.test.tsx src/pages/EmptySession.test.tsx
Tested: bun run check:desktop
Not-tested: Live backend agent-list integration beyond mocked desktop composer coverage.
This commit is contained in:
程序员阿江(Relakkes) 2026-06-03 09:29:57 +08:00
parent 3f1312d40d
commit 959e177cdf
4 changed files with 91 additions and 2 deletions

View File

@ -1101,4 +1101,54 @@ describe('ChatInput file mentions', () => {
expect(input).toHaveValue('/agent debugger ')
})
it('selects a highlighted agent entry from /agent without sending until the configured send shortcut is used', async () => {
useSettingsStore.setState({
chatSendBehavior: 'modifierEnter',
})
mocks.listAgents.mockResolvedValue({
activeAgents: [
{
agentType: 'debugger',
description: 'Debug failures',
modelDisplay: 'OPUS',
source: 'userSettings',
isActive: true,
},
],
allAgents: [],
})
render(<ChatInput />)
await waitFor(() => {
expect(mocks.listAgents).toHaveBeenCalledWith('/repo')
})
const input = screen.getByRole('textbox') as HTMLTextAreaElement
fireEvent.change(input, {
target: { value: '/agent', selectionStart: 6 },
})
await screen.findByText('/agent debugger')
fireEvent.keyDown(input, { key: 'ArrowDown' })
fireEvent.keyDown(input, { key: 'Enter' })
expect(input).toHaveValue('/agent debugger ')
expect(mocks.wsSend).not.toHaveBeenCalled()
const prompt = '/agent debugger investigate this failure'
fireEvent.change(input, {
target: { value: prompt, selectionStart: prompt.length },
})
fireEvent.keyDown(input, { key: 'Enter' })
expect(mocks.wsSend).not.toHaveBeenCalled()
fireEvent.keyDown(input, { key: 'Enter', ctrlKey: true })
expect(mocks.wsSend).toHaveBeenCalledWith(sessionId, {
type: 'user_message',
content: prompt,
attachments: [],
})
})
})

View File

@ -737,8 +737,10 @@ export function ChatInput({ variant = 'default', compact = false }: ChatInputPro
return
}
if (event.key === 'Enter') {
const selected = filteredCommands[slashSelectedIndex]
if (
exactSlashCommand &&
selected?.name.toLowerCase() === exactSlashCommand.name.toLowerCase() &&
slashFilter.trim().toLowerCase() === exactSlashCommand.name.toLowerCase() &&
shouldSubmitOnEnter(event, chatSendBehavior)
) {
@ -747,7 +749,6 @@ export function ChatInput({ variant = 'default', compact = false }: ChatInputPro
return
}
event.preventDefault()
const selected = filteredCommands[slashSelectedIndex]
if (selected) selectSlashCommand(selected.name)
return
}

View File

@ -353,6 +353,43 @@ describe('EmptySession', () => {
expect(input).toHaveValue('/agent debugger ')
})
it('selects a highlighted agent entry from /agent without creating a session', async () => {
useSettingsStore.setState({
chatSendBehavior: 'enter',
})
mocks.listAgents.mockResolvedValue({
activeAgents: [
{
agentType: 'debugger',
description: 'Debug failures',
modelDisplay: 'OPUS',
source: 'userSettings',
isActive: true,
},
],
allAgents: [],
})
render(<EmptySession />)
await waitFor(() => {
expect(mocks.listAgents).toHaveBeenCalledWith(undefined)
})
const input = screen.getByRole('textbox') as HTMLTextAreaElement
fireEvent.change(input, {
target: { value: '/agent', selectionStart: 6 },
})
await screen.findByText('/agent debugger')
fireEvent.keyDown(input, { key: 'ArrowDown' })
fireEvent.keyDown(input, { key: 'Enter' })
expect(input).toHaveValue('/agent debugger ')
expect(mocks.createSession).not.toHaveBeenCalled()
expect(mocks.wsSend).not.toHaveBeenCalled()
})
it('integrates repository launch controls into the desktop composer panel', async () => {
render(<EmptySession />)

View File

@ -415,9 +415,11 @@ export function EmptySession() {
return
}
if (event.key === 'Enter' || event.key === 'Tab') {
const selected = filteredCommands[slashSelectedIndex]
if (
event.key === 'Enter' &&
exactSlashCommand &&
selected?.name.toLowerCase() === exactSlashCommand.name.toLowerCase() &&
slashFilter.trim().toLowerCase() === exactSlashCommand.name.toLowerCase() &&
shouldSubmitOnEnter(event, chatSendBehavior)
) {
@ -426,7 +428,6 @@ export function EmptySession() {
return
}
event.preventDefault()
const selected = filteredCommands[slashSelectedIndex]
if (selected) selectSlashCommand(selected.name)
return
}