diff --git a/packages/cli/src/ui/commands/chatCommand.ts b/packages/cli/src/ui/commands/chatCommand.ts
index 05fd081dfb..71c590a05e 100644
--- a/packages/cli/src/ui/commands/chatCommand.ts
+++ b/packages/cli/src/ui/commands/chatCommand.ts
@@ -71,7 +71,7 @@ const getSavedChatTags = async (
};
const listCommand: SlashCommand = {
- name: 'list',
+ name: 'checkpoints',
description: 'List saved manual conversation checkpoints',
kind: CommandKind.BUILT_IN,
autoExecute: true,
diff --git a/packages/cli/src/ui/components/InputPrompt.test.tsx b/packages/cli/src/ui/components/InputPrompt.test.tsx
index 49dd08ac53..a677a1b95f 100644
--- a/packages/cli/src/ui/components/InputPrompt.test.tsx
+++ b/packages/cli/src/ui/components/InputPrompt.test.tsx
@@ -1102,6 +1102,39 @@ describe('InputPrompt', () => {
unmount();
});
+ it('should autocomplete the synthetic auto suggestion on Tab (not submit)', async () => {
+ mockedUseCommandCompletion.mockReturnValue({
+ ...mockCommandCompletion,
+ showSuggestions: true,
+ suggestions: [
+ {
+ label: 'list',
+ value: 'list',
+ insertValue: 'list',
+ },
+ ],
+ activeSuggestionIndex: 0,
+ });
+ props.buffer.setText('/chat ');
+
+ const { stdin, unmount } = await renderWithProviders(
+ ,
+ {
+ uiActions,
+ },
+ );
+
+ await act(async () => {
+ stdin.write('\t');
+ });
+
+ await waitFor(() =>
+ expect(mockCommandCompletion.handleAutocomplete).toHaveBeenCalledWith(0),
+ );
+ expect(props.onSubmit).not.toHaveBeenCalled();
+ unmount();
+ });
+
it('queues a message when Tab is pressed during generation', async () => {
props.buffer.setText('A new prompt');
props.streamingState = StreamingState.Responding;
diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx
index 4547c19d8a..7fa3f47ff8 100644
--- a/packages/cli/src/ui/components/InputPrompt.tsx
+++ b/packages/cli/src/ui/components/InputPrompt.tsx
@@ -1066,7 +1066,6 @@ export const InputPrompt: React.FC = ({
handleSubmit(suggestion.submitValue.trim());
return true;
}
-
const { isArgumentCompletion, leafCommand } =
completion.slashCompletionRange;
diff --git a/packages/cli/src/ui/components/__snapshots__/InputPrompt.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/InputPrompt.test.tsx.snap
index ab6fe9b928..f40887b3b9 100644
--- a/packages/cli/src/ui/components/__snapshots__/InputPrompt.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/InputPrompt.test.tsx.snap
@@ -1,95 +1,5 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`InputPrompt > Highlighting and Cursor Display > multi-line scenarios > should display cursor correctly 'at the beginning of a line' in a multiline block 1`] = `
-"────────────────────────────────────────────────────────────────────────────────────────────────────
-│ > first line │
-│ second line │
-────────────────────────────────────────────────────────────────────────────────────────────────────"
-`;
-
-exports[`InputPrompt > Highlighting and Cursor Display > multi-line scenarios > should display cursor correctly 'at the end of a line' in a multiline block 1`] = `
-"────────────────────────────────────────────────────────────────────────────────────────────────────
-│ > first line │
-│ second line │
-────────────────────────────────────────────────────────────────────────────────────────────────────"
-`;
-
-exports[`InputPrompt > Highlighting and Cursor Display > multi-line scenarios > should display cursor correctly 'in the middle of a line' in a multiline block 1`] = `
-"────────────────────────────────────────────────────────────────────────────────────────────────────
-│ > first line │
-│ second line │
-│ third line │
-────────────────────────────────────────────────────────────────────────────────────────────────────"
-`;
-
-exports[`InputPrompt > Highlighting and Cursor Display > multi-line scenarios > should display cursor on a blank line in a multiline block 1`] = `
-"────────────────────────────────────────────────────────────────────────────────────────────────────
-│ > first line │
-│ │
-│ third line │
-────────────────────────────────────────────────────────────────────────────────────────────────────"
-`;
-
-exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'after multi-byte unicode characters' 1`] = `
-"────────────────────────────────────────────────────────────────────────────────────────────────────
-│ > 👍A │
-────────────────────────────────────────────────────────────────────────────────────────────────────"
-`;
-
-exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'at the beginning of the line' 1`] = `
-"────────────────────────────────────────────────────────────────────────────────────────────────────
-│ > hello │
-────────────────────────────────────────────────────────────────────────────────────────────────────"
-`;
-
-exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'at the end of a line with unicode cha…' 1`] = `
-"────────────────────────────────────────────────────────────────────────────────────────────────────
-│ > hello 👍 │
-────────────────────────────────────────────────────────────────────────────────────────────────────"
-`;
-
-exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'at the end of a short line with unico…' 1`] = `
-"────────────────────────────────────────────────────────────────────────────────────────────────────
-│ > 👍 │
-────────────────────────────────────────────────────────────────────────────────────────────────────"
-`;
-
-exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'at the end of the line' 1`] = `
-"────────────────────────────────────────────────────────────────────────────────────────────────────
-│ > hello │
-────────────────────────────────────────────────────────────────────────────────────────────────────"
-`;
-
-exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'for multi-byte unicode characters' 1`] = `
-"────────────────────────────────────────────────────────────────────────────────────────────────────
-│ > hello 👍 world │
-────────────────────────────────────────────────────────────────────────────────────────────────────"
-`;
-
-exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'mid-word' 1`] = `
-"────────────────────────────────────────────────────────────────────────────────────────────────────
-│ > hello world │
-────────────────────────────────────────────────────────────────────────────────────────────────────"
-`;
-
-exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'on a highlighted token' 1`] = `
-"────────────────────────────────────────────────────────────────────────────────────────────────────
-│ > run @path/to/file │
-────────────────────────────────────────────────────────────────────────────────────────────────────"
-`;
-
-exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'on a space between words' 1`] = `
-"────────────────────────────────────────────────────────────────────────────────────────────────────
-│ > hello world │
-────────────────────────────────────────────────────────────────────────────────────────────────────"
-`;
-
-exports[`InputPrompt > Highlighting and Cursor Display > single-line scenarios > should display cursor correctly 'on an empty line' 1`] = `
-"────────────────────────────────────────────────────────────────────────────────────────────────────
-│ > Type your message or @path/to/file │
-────────────────────────────────────────────────────────────────────────────────────────────────────"
-`;
-
exports[`InputPrompt > History Navigation and Completion Suppression > should not render suggestions during history navigation 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> second message
@@ -168,18 +78,32 @@ exports[`InputPrompt > mouse interaction > should toggle paste expansion on doub
"
`;
-exports[`InputPrompt > multiline rendering > should correctly render multiline input including blank lines 1`] = `
-"────────────────────────────────────────────────────────────────────────────────────────────────────
-│ > hello │
-│ │
-│ world │
-────────────────────────────────────────────────────────────────────────────────────────────────────"
+exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 4`] = `
+"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+ > [Pasted Text: 10 lines]
+▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
+"
+`;
+
+exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 5`] = `
+"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+ > [Pasted Text: 10 lines]
+▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
+"
+`;
+
+exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 6`] = `
+"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+ > [Pasted Text: 10 lines]
+▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
+"
`;
exports[`InputPrompt > snapshots > should not show inverted cursor when shell is focused 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Type your message or @path/to/file
-▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄"
+▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
+"
`;
exports[`InputPrompt > snapshots > should render correctly in shell mode 1`] = `
diff --git a/packages/cli/src/ui/hooks/useCommandCompletion.tsx b/packages/cli/src/ui/hooks/useCommandCompletion.tsx
index 4f89d69ff1..6ebf097929 100644
--- a/packages/cli/src/ui/hooks/useCommandCompletion.tsx
+++ b/packages/cli/src/ui/hooks/useCommandCompletion.tsx
@@ -442,11 +442,18 @@ export function useCommandCompletion({
if (completionMode === CompletionMode.SLASH) {
const command =
slashCompletionRange.getCommandFromSuggestion(suggestion);
- // Don't add a space if the command has an action (can be executed)
- // and doesn't have a completion function (doesn't REQUIRE more arguments)
+ // Don't add a space if the command has an action (can be executed),
+ // doesn't have a completion function (doesn't REQUIRE more arguments),
+ // AND doesn't have subcommands (no further navigation expected).
const isExecutableCommand = !!(command && command.action);
const requiresArguments = !!(command && command.completion);
- shouldAddSpace = !isExecutableCommand || requiresArguments;
+ const hasSubCommands = !!(
+ command &&
+ command.subCommands &&
+ command.subCommands.length > 0
+ );
+ shouldAddSpace =
+ !isExecutableCommand || requiresArguments || hasSubCommands;
}
if (
diff --git a/packages/cli/src/ui/hooks/useSlashCompletion.test.ts b/packages/cli/src/ui/hooks/useSlashCompletion.test.ts
index 0bcb3863ce..67ca170caa 100644
--- a/packages/cli/src/ui/hooks/useSlashCompletion.test.ts
+++ b/packages/cli/src/ui/hooks/useSlashCompletion.test.ts
@@ -476,7 +476,7 @@ describe('useSlashCompletion', () => {
expect(chatResult.current.suggestions[0]).toMatchObject({
label: 'list',
sectionTitle: 'auto',
- submitValue: '/chat',
+ insertValue: 'list',
});
});
@@ -496,7 +496,7 @@ describe('useSlashCompletion', () => {
expect(resumeResult.current.suggestions[0]).toMatchObject({
label: 'list',
sectionTitle: 'auto',
- submitValue: '/resume',
+ insertValue: 'list',
});
});
@@ -513,6 +513,53 @@ describe('useSlashCompletion', () => {
unmountResume();
});
+ it('should prioritize matching subcommands over the synthetic auto entry when typing /chat subcommand prefixes', async () => {
+ const slashCommands = [
+ createTestCommand({
+ name: 'chat',
+ description: 'Chat command',
+ action: vi.fn(),
+ subCommands: [
+ createTestCommand({
+ name: 'list',
+ description: 'List checkpoints',
+ suggestionGroup: 'checkpoints',
+ action: vi.fn(),
+ }),
+ createTestCommand({
+ name: 'resume',
+ description: 'Resume a saved chat',
+ action: vi.fn(),
+ }),
+ ],
+ }),
+ ];
+
+ const { result, unmount } = await renderHook(() =>
+ useTestHarnessForSlashCompletion(
+ true,
+ '/chat resu',
+ slashCommands,
+ mockCommandContext,
+ ),
+ );
+
+ await resolveMatch();
+
+ await waitFor(() => {
+ expect(result.current.suggestions).toHaveLength(1);
+ expect(result.current.suggestions[0]).toMatchObject({
+ label: 'resume',
+ value: 'resume',
+ });
+ expect(
+ result.current.suggestions.some((s) => s.sectionTitle === 'auto'),
+ ).toBe(false);
+ });
+
+ unmount();
+ });
+
it('should sort exact altName matches to the top', async () => {
const slashCommands = [
createTestCommand({
diff --git a/packages/cli/src/ui/hooks/useSlashCompletion.ts b/packages/cli/src/ui/hooks/useSlashCompletion.ts
index 7b06fdc1f4..fb47235bb9 100644
--- a/packages/cli/src/ui/hooks/useSlashCompletion.ts
+++ b/packages/cli/src/ui/hooks/useSlashCompletion.ts
@@ -336,16 +336,14 @@ function useCommandSuggestions(
matchesCommand(leafCommand, commandPathParts[0])))
);
- if (isTopLevelChatOrResumeContext) {
- const canonicalParentName = leafCommand.name;
+ if (isTopLevelChatOrResumeContext && partial === '') {
const autoSectionSuggestion: Suggestion = {
label: 'list',
value: 'list',
- insertValue: canonicalParentName,
+ insertValue: 'list',
description: 'Browse auto-saved chats',
commandKind: CommandKind.BUILT_IN,
sectionTitle: 'auto',
- submitValue: `/${canonicalParentName}`,
};
setSuggestions([autoSectionSuggestion, ...finalSuggestions]);
return;