diff --git a/ui/server/routes/commands.js b/ui/server/routes/commands.js index ca5b0857..c46687bb 100644 --- a/ui/server/routes/commands.js +++ b/ui/server/routes/commands.js @@ -979,7 +979,7 @@ router.post('/load', async (req, res) => { */ router.post('/execute', async (req, res) => { try { - const { commandName, commandPath, args = [], context = {} } = req.body; + const { commandName, commandPath, args = [], rawArgs, rawInput, context = {} } = req.body; if (!commandName) { return res.status(400).json({ @@ -1014,11 +1014,18 @@ router.post('/execute', async (req, res) => { const isBundledStub = BUNDLED_SKILL_STUBS.some( (stub) => stub.name === commandName, ); + const buildPassthroughContent = () => { + if (typeof rawInput === 'string' && rawInput.trimStart().startsWith(commandName)) { + return rawInput.trim(); + } + const argsString = typeof rawArgs === 'string' + ? rawArgs.trimStart() + : args.join(' ').trim(); + return argsString ? `${commandName} ${argsString}` : commandName; + }; + if (isBundledStub) { - const argsString = args.join(' ').trim(); - const passthroughContent = argsString - ? `${commandName} ${argsString}` - : commandName; + const passthroughContent = buildPassthroughContent(); return res.json({ type: 'custom', command: commandName, @@ -1033,10 +1040,7 @@ router.post('/execute', async (req, res) => { // SKILL.md body into chat. Instead, passthrough the slash text so the // proxy's slash parser invokes SkillTool with the procedural body. if (commandPath && /\/\.pilotdeck\/skills\/[^/]+\/SKILL\.md$/i.test(commandPath)) { - const argsString = args.join(' ').trim(); - const passthroughContent = argsString - ? `${commandName} ${argsString}` - : commandName; + const passthroughContent = buildPassthroughContent(); return res.json({ type: 'custom', command: commandName, diff --git a/ui/src/components/chat/hooks/useChatComposerState.ts b/ui/src/components/chat/hooks/useChatComposerState.ts index 13acbf3d..bfb688c4 100644 --- a/ui/src/components/chat/hooks/useChatComposerState.ts +++ b/ui/src/components/chat/hooks/useChatComposerState.ts @@ -434,9 +434,10 @@ export function useChatComposerState({ try { const effectiveInput = rawInput ?? input; - const commandMatch = effectiveInput.match(new RegExp(`${escapeRegExp(command.name)}\\s*(.*)`)); - const args = - commandMatch && commandMatch[1] ? commandMatch[1].trim().split(/\s+/) : []; + const rawArgs = effectiveInput.startsWith(command.name) + ? effectiveInput.slice(command.name.length).trimStart() + : ''; + const args = rawArgs.trim() ? rawArgs.trim().split(/\s+/) : []; const context = { projectPath: selectedProject.fullPath || selectedProject.path, @@ -455,6 +456,8 @@ export function useChatComposerState({ commandName: command.name, commandPath: command.path, args, + rawArgs, + rawInput: effectiveInput, context, }), }); @@ -635,8 +638,7 @@ export function useChatComposerState({ if (skipSlashDetectionOnceRef.current) { skipSlashDetectionOnceRef.current = false; } else if (trimmedInput.startsWith('/')) { - const firstSpace = trimmedInput.indexOf(' '); - const commandName = firstSpace > 0 ? trimmedInput.slice(0, firstSpace) : trimmedInput; + const commandName = trimmedInput.match(/^(\S+)/)?.[1] ?? trimmedInput; const matchedCommand = slashCommands.find((cmd: SlashCommand) => cmd.name === commandName); if (matchedCommand) { executeCommand(matchedCommand, trimmedInput);