From ae8cab5df748cf26826a3b9ddcef9a1732687961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E8=BE=BE=E5=A5=87?= <> Date: Fri, 26 Jun 2026 16:43:09 +0800 Subject: [PATCH] fix(ui): preserve multiline slash command input --- ui/server/routes/commands.js | 22 +++++++++++-------- .../chat/hooks/useChatComposerState.ts | 12 +++++----- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/ui/server/routes/commands.js b/ui/server/routes/commands.js index ca5b0857f..c46687bb5 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 13acbf3d5..bfb688c4c 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);