fix: preserve duplicate slash commands in autocomplete
Show commands with identical names from different sources Add stable command IDs so selection no longer depends on name dedupe Keep command insertion flow working across chat and prompt editors
This commit is contained in:
@@ -32,7 +32,7 @@ import { renderMagicPrompt } from '@/lib/magicPrompts';
|
|||||||
import { AttachedFilesList } from './FileAttachment';
|
import { AttachedFilesList } from './FileAttachment';
|
||||||
import { QueuedMessageChips } from './QueuedMessageChips';
|
import { QueuedMessageChips } from './QueuedMessageChips';
|
||||||
import { FileMentionAutocomplete, type FileMentionHandle } from './FileMentionAutocomplete';
|
import { FileMentionAutocomplete, type FileMentionHandle } from './FileMentionAutocomplete';
|
||||||
import { CommandAutocomplete, type CommandAutocompleteHandle } from './CommandAutocomplete';
|
import { CommandAutocomplete, type CommandAutocompleteHandle, type CommandInfo } from './CommandAutocomplete';
|
||||||
import { SkillAutocomplete, type SkillAutocompleteHandle } from './SkillAutocomplete';
|
import { SkillAutocomplete, type SkillAutocompleteHandle } from './SkillAutocomplete';
|
||||||
import { cn, formatDirectoryName, isMacOS } from '@/lib/utils';
|
import { cn, formatDirectoryName, isMacOS } from '@/lib/utils';
|
||||||
import { ModelControls } from './ModelControls';
|
import { ModelControls } from './ModelControls';
|
||||||
@@ -2470,7 +2470,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
|
|||||||
textareaRef.current?.focus();
|
textareaRef.current?.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCommandSelect = (command: { name: string; description?: string; agent?: string; model?: string }) => {
|
const handleCommandSelect = (command: CommandInfo) => {
|
||||||
|
|
||||||
setMessage(`/${command.name} `);
|
setMessage(`/${command.name} `);
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,12 @@ import { useCommandsStore } from '@/stores/useCommandsStore';
|
|||||||
import { useSkillsStore } from '@/stores/useSkillsStore';
|
import { useSkillsStore } from '@/stores/useSkillsStore';
|
||||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||||
|
|
||||||
interface CommandInfo {
|
type CommandSource = 'openchamber' | 'opencode';
|
||||||
|
|
||||||
|
export interface CommandInfo {
|
||||||
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
source: CommandSource;
|
||||||
description?: string;
|
description?: string;
|
||||||
agent?: string;
|
agent?: string;
|
||||||
model?: string;
|
model?: string;
|
||||||
@@ -89,8 +93,10 @@ export const CommandAutocomplete = React.forwardRef<CommandAutocompleteHandle, C
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const skillNames = new Set(skills.map((skill) => skill.name));
|
const skillNames = new Set(skills.map((skill) => skill.name));
|
||||||
const customCommands: CommandInfo[] = commandsWithMetadata.map(cmd => ({
|
const customCommands: CommandInfo[] = commandsWithMetadata.map((cmd, index) => ({
|
||||||
|
id: `opencode:${cmd.scope ?? 'global'}:${cmd.name}:${cmd.agent ?? ''}:${cmd.model ?? ''}:${index}`,
|
||||||
name: cmd.name,
|
name: cmd.name,
|
||||||
|
source: 'opencode',
|
||||||
description: cmd.description,
|
description: cmd.description,
|
||||||
agent: cmd.agent ?? undefined,
|
agent: cmd.agent ?? undefined,
|
||||||
model: cmd.model ?? undefined,
|
model: cmd.model ?? undefined,
|
||||||
@@ -101,30 +107,23 @@ export const CommandAutocomplete = React.forwardRef<CommandAutocompleteHandle, C
|
|||||||
|
|
||||||
const builtInCommands: CommandInfo[] = [
|
const builtInCommands: CommandInfo[] = [
|
||||||
...(hasSession && !hasMessagesInCurrentSession
|
...(hasSession && !hasMessagesInCurrentSession
|
||||||
? [{ name: 'init', description: 'Create/update AGENTS.md file', isBuiltIn: true }]
|
? [{ id: 'openchamber:init', name: 'init', source: 'openchamber' as const, description: 'Create/update AGENTS.md file', isBuiltIn: true }]
|
||||||
: []
|
: []
|
||||||
),
|
),
|
||||||
...(hasSession // Show when session exists, not when hasMessages
|
...(hasSession // Show when session exists, not when hasMessages
|
||||||
? [
|
? [
|
||||||
{ name: 'undo', description: 'Undo the last message', isBuiltIn: true },
|
{ id: 'openchamber:undo', name: 'undo', source: 'openchamber' as const, description: 'Undo the last message', isBuiltIn: true },
|
||||||
{ name: 'redo', description: 'Redo previously undone messages', isBuiltIn: true },
|
{ id: 'openchamber:redo', name: 'redo', source: 'openchamber' as const, description: 'Redo previously undone messages', isBuiltIn: true },
|
||||||
]
|
]
|
||||||
: []
|
: []
|
||||||
),
|
),
|
||||||
{ name: 'compact', description: 'Compress session history using AI to reduce context size', isBuiltIn: true },
|
{ id: 'openchamber:compact', name: 'compact', source: 'openchamber' as const, description: 'Compress session history using AI to reduce context size', isBuiltIn: true },
|
||||||
...(hasSession
|
...(hasSession
|
||||||
? [{ name: 'summary', description: 'Non-destructive session summary. Optional topic hint after the command.', isOpenChamber: true }]
|
? [{ id: 'openchamber:summary', name: 'summary', source: 'openchamber' as const, description: 'Non-destructive session summary. Optional topic hint after the command.', isOpenChamber: true }]
|
||||||
: []
|
: []
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
const allCommands = [...builtInCommands, ...customCommands];
|
||||||
const commandMap = new Map<string, CommandInfo>();
|
|
||||||
|
|
||||||
builtInCommands.forEach(cmd => commandMap.set(cmd.name, cmd));
|
|
||||||
|
|
||||||
customCommands.forEach(cmd => commandMap.set(cmd.name, cmd));
|
|
||||||
|
|
||||||
const allCommands = Array.from(commandMap.values());
|
|
||||||
|
|
||||||
const allowInitCommand = !hasMessagesInCurrentSession;
|
const allowInitCommand = !hasMessagesInCurrentSession;
|
||||||
const filtered = (searchQuery
|
const filtered = (searchQuery
|
||||||
@@ -148,19 +147,19 @@ export const CommandAutocomplete = React.forwardRef<CommandAutocompleteHandle, C
|
|||||||
const allowInitCommand = !hasMessagesInCurrentSession;
|
const allowInitCommand = !hasMessagesInCurrentSession;
|
||||||
const builtInCommands: CommandInfo[] = [
|
const builtInCommands: CommandInfo[] = [
|
||||||
...(hasSession && !hasMessagesInCurrentSession
|
...(hasSession && !hasMessagesInCurrentSession
|
||||||
? [{ name: 'init', description: 'Create/update AGENTS.md file', isBuiltIn: true }]
|
? [{ id: 'openchamber:init', name: 'init', source: 'openchamber' as const, description: 'Create/update AGENTS.md file', isBuiltIn: true }]
|
||||||
: []
|
: []
|
||||||
),
|
),
|
||||||
...(hasSession // Show when session exists, not when hasMessages
|
...(hasSession // Show when session exists, not when hasMessages
|
||||||
? [
|
? [
|
||||||
{ name: 'undo', description: 'Undo the last message', isBuiltIn: true },
|
{ id: 'openchamber:undo', name: 'undo', source: 'openchamber' as const, description: 'Undo the last message', isBuiltIn: true },
|
||||||
{ name: 'redo', description: 'Redo previously undone messages', isBuiltIn: true },
|
{ id: 'openchamber:redo', name: 'redo', source: 'openchamber' as const, description: 'Redo previously undone messages', isBuiltIn: true },
|
||||||
]
|
]
|
||||||
: []
|
: []
|
||||||
),
|
),
|
||||||
{ name: 'compact', description: 'Compress session history using AI to reduce context size', isBuiltIn: true },
|
{ id: 'openchamber:compact', name: 'compact', source: 'openchamber' as const, description: 'Compress session history using AI to reduce context size', isBuiltIn: true },
|
||||||
...(hasSession
|
...(hasSession
|
||||||
? [{ name: 'summary', description: 'Non-destructive session summary. Optional topic hint after the command.', isOpenChamber: true }]
|
? [{ id: 'openchamber:summary', name: 'summary', source: 'openchamber' as const, description: 'Non-destructive session summary. Optional topic hint after the command.', isOpenChamber: true }]
|
||||||
: []
|
: []
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@@ -307,7 +306,7 @@ export const CommandAutocomplete = React.forwardRef<CommandAutocompleteHandle, C
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={command.name}
|
key={command.id}
|
||||||
ref={(el) => { itemRefs.current[index] = el; }}
|
ref={(el) => { itemRefs.current[index] = el; }}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-start gap-2 px-3 py-2 cursor-pointer rounded-lg",
|
"flex items-start gap-2 px-3 py-2 cursor-pointer rounded-lg",
|
||||||
@@ -425,5 +424,3 @@ export const CommandAutocomplete = React.forwardRef<CommandAutocompleteHandle, C
|
|||||||
});
|
});
|
||||||
|
|
||||||
CommandAutocomplete.displayName = 'CommandAutocomplete';
|
CommandAutocomplete.displayName = 'CommandAutocomplete';
|
||||||
|
|
||||||
export type { CommandInfo };
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import type { CreateMultiRunParams, MultiRunModelSelection } from '@/types/multi
|
|||||||
import { ModelMultiSelect, generateInstanceId, type ModelSelectionWithId } from './ModelMultiSelect';
|
import { ModelMultiSelect, generateInstanceId, type ModelSelectionWithId } from './ModelMultiSelect';
|
||||||
import { BranchSelector, useBranchOptions } from './BranchSelector';
|
import { BranchSelector, useBranchOptions } from './BranchSelector';
|
||||||
import { AgentSelector } from './AgentSelector';
|
import { AgentSelector } from './AgentSelector';
|
||||||
import { CommandAutocomplete, type CommandAutocompleteHandle } from '@/components/chat/CommandAutocomplete';
|
import { CommandAutocomplete, type CommandAutocompleteHandle, type CommandInfo } from '@/components/chat/CommandAutocomplete';
|
||||||
import { FileMentionAutocomplete, type FileMentionHandle } from '@/components/chat/FileMentionAutocomplete';
|
import { FileMentionAutocomplete, type FileMentionHandle } from '@/components/chat/FileMentionAutocomplete';
|
||||||
import { isDesktopShell } from '@/lib/desktop';
|
import { isDesktopShell } from '@/lib/desktop';
|
||||||
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
||||||
@@ -505,7 +505,7 @@ export const MultiRunLauncher: React.FC<MultiRunLauncherProps> = ({
|
|||||||
});
|
});
|
||||||
}, [prompt, updateAutocompleteState]);
|
}, [prompt, updateAutocompleteState]);
|
||||||
|
|
||||||
const handleAutocompleteCommandSelect = React.useCallback((command: { name: string }) => {
|
const handleAutocompleteCommandSelect = React.useCallback((command: CommandInfo) => {
|
||||||
const nextPrompt = `/${command.name} `;
|
const nextPrompt = `/${command.name} `;
|
||||||
setPrompt(nextPrompt);
|
setPrompt(nextPrompt);
|
||||||
setShowCommandAutocomplete(false);
|
setShowCommandAutocomplete(false);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { RiAddLine, RiCloseLine, RiCalendarLine, RiArrowLeftSLine, RiArrowRightS
|
|||||||
import { ModelSelector } from '@/components/sections/agents/ModelSelector';
|
import { ModelSelector } from '@/components/sections/agents/ModelSelector';
|
||||||
import { AgentSelector } from '@/components/sections/commands/AgentSelector';
|
import { AgentSelector } from '@/components/sections/commands/AgentSelector';
|
||||||
import { isPrimaryMode } from '@/components/chat/mobileControlsUtils';
|
import { isPrimaryMode } from '@/components/chat/mobileControlsUtils';
|
||||||
import { CommandAutocomplete, type CommandAutocompleteHandle } from '@/components/chat/CommandAutocomplete';
|
import { CommandAutocomplete, type CommandAutocompleteHandle, type CommandInfo } from '@/components/chat/CommandAutocomplete';
|
||||||
import { FileMentionAutocomplete, type FileMentionHandle } from '@/components/chat/FileMentionAutocomplete';
|
import { FileMentionAutocomplete, type FileMentionHandle } from '@/components/chat/FileMentionAutocomplete';
|
||||||
import { useConfigStore } from '@/stores/useConfigStore';
|
import { useConfigStore } from '@/stores/useConfigStore';
|
||||||
import { useUIStore } from '@/stores/useUIStore';
|
import { useUIStore } from '@/stores/useUIStore';
|
||||||
@@ -907,7 +907,7 @@ export function ScheduledTaskEditorDialog(props: {
|
|||||||
});
|
});
|
||||||
}, [draft.execution.prompt, setPromptValue, updateAutocompleteState]);
|
}, [draft.execution.prompt, setPromptValue, updateAutocompleteState]);
|
||||||
|
|
||||||
const handleCommandSelect = React.useCallback((command: { name: string }) => {
|
const handleCommandSelect = React.useCallback((command: CommandInfo) => {
|
||||||
const nextPrompt = `/${command.name} `;
|
const nextPrompt = `/${command.name} `;
|
||||||
setPromptValue(nextPrompt);
|
setPromptValue(nextPrompt);
|
||||||
setShowCommandAutocomplete(false);
|
setShowCommandAutocomplete(false);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { useProjectsStore } from '@/stores/useProjectsStore';
|
|||||||
import { ModelMultiSelect, generateInstanceId, type ModelSelectionWithId } from '@/components/multirun/ModelMultiSelect';
|
import { ModelMultiSelect, generateInstanceId, type ModelSelectionWithId } from '@/components/multirun/ModelMultiSelect';
|
||||||
import { BranchSelector, useBranchOptions } from '@/components/multirun/BranchSelector';
|
import { BranchSelector, useBranchOptions } from '@/components/multirun/BranchSelector';
|
||||||
import { AgentSelector } from '@/components/multirun/AgentSelector';
|
import { AgentSelector } from '@/components/multirun/AgentSelector';
|
||||||
import { CommandAutocomplete, type CommandAutocompleteHandle } from '@/components/chat/CommandAutocomplete';
|
import { CommandAutocomplete, type CommandAutocompleteHandle, type CommandInfo } from '@/components/chat/CommandAutocomplete';
|
||||||
import { FileMentionAutocomplete, type FileMentionHandle } from '@/components/chat/FileMentionAutocomplete';
|
import { FileMentionAutocomplete, type FileMentionHandle } from '@/components/chat/FileMentionAutocomplete';
|
||||||
import { isIMECompositionEvent } from '@/lib/ime';
|
import { isIMECompositionEvent } from '@/lib/ime';
|
||||||
import { getWorktreeSetupCommands } from '@/lib/openchamberConfig';
|
import { getWorktreeSetupCommands } from '@/lib/openchamberConfig';
|
||||||
@@ -294,7 +294,7 @@ export const AgentManagerEmptyState: React.FC<AgentManagerEmptyStateProps> = ({
|
|||||||
});
|
});
|
||||||
}, [prompt, updateAutocompleteState]);
|
}, [prompt, updateAutocompleteState]);
|
||||||
|
|
||||||
const handleAutocompleteCommandSelect = React.useCallback((command: { name: string }) => {
|
const handleAutocompleteCommandSelect = React.useCallback((command: CommandInfo) => {
|
||||||
const nextPrompt = `/${command.name} `;
|
const nextPrompt = `/${command.name} `;
|
||||||
setPrompt(nextPrompt);
|
setPrompt(nextPrompt);
|
||||||
setShowCommandAutocomplete(false);
|
setShowCommandAutocomplete(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user