Skip to content

Commit 7f701da

Browse files
authored
fix: custom slash commands fail silently with multi-line arguments (#86)
fix: custom slash commands fail silently with multi-line arguments The regex used to parse slash command arguments was missing the 's' (dotAll) flag, causing '.' to not match newline characters. When a custom command was invoked with multi-line arguments, the regex failed silently. Additionally, active-chat.tsx was entirely missing the slash command expansion logic that new-chat-form.tsx already had, so custom commands with arguments never worked in ongoing conversations. Fixes: - Add 's' flag to regex so '.' matches newlines in arguments - Add case-insensitive command name matching - Add missing slash command expansion in active-chat.tsx
1 parent 1ed75f3 commit 7f701da

2 files changed

Lines changed: 70 additions & 9 deletions

File tree

src/renderer/features/agents/main/active-chat.tsx

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import { appStore } from "../../../lib/jotai-store"
7272
import { api } from "../../../lib/mock-api"
7373
import { trpc, trpcClient } from "../../../lib/trpc"
7474
import { cn } from "../../../lib/utils"
75+
import { BUILTIN_SLASH_COMMANDS } from "../commands"
7576
import { isDesktopApp } from "../../../lib/utils/platform"
7677
import { ChangesPanel } from "../../changes"
7778
import { DiffCenterPeekDialog } from "../../changes/components/diff-center-peek-dialog"
@@ -3287,6 +3288,36 @@ const ChatViewInner = memo(function ChatViewInner({
32873288
}
32883289

32893290
const text = inputValue.trim()
3291+
3292+
// Expand custom slash commands with arguments (e.g. "/Apex my argument")
3293+
// This mirrors the logic in new-chat-form.tsx
3294+
let finalText = text
3295+
const slashMatch = text.match(/^\/(\S+)\s*(.*)$/s)
3296+
if (slashMatch) {
3297+
const [, commandName, args] = slashMatch
3298+
const builtinNames = new Set(
3299+
BUILTIN_SLASH_COMMANDS.map((cmd) => cmd.name),
3300+
)
3301+
if (!builtinNames.has(commandName)) {
3302+
try {
3303+
const commands = await trpcClient.commands.list.query({
3304+
projectPath,
3305+
})
3306+
const cmd = commands.find(
3307+
(c) => c.name.toLowerCase() === commandName.toLowerCase(),
3308+
)
3309+
if (cmd) {
3310+
const { content } = await trpcClient.commands.getContent.query({
3311+
path: cmd.path,
3312+
})
3313+
finalText = content.replace(/\$ARGUMENTS/g, args.trim())
3314+
}
3315+
} catch (error) {
3316+
console.error("Failed to expand custom slash command:", error)
3317+
}
3318+
}
3319+
}
3320+
32903321
// Clear editor and draft from localStorage
32913322
editorRef.current?.clear()
32923323
if (parentChatId) {
@@ -3296,14 +3327,14 @@ const ChatViewInner = memo(function ChatViewInner({
32963327
// Track message sent
32973328
trackMessageSent({
32983329
workspaceId: subChatId,
3299-
messageLength: text.length,
3330+
messageLength: finalText.length,
33003331
mode: isPlanModeRef.current ? "plan" : "agent",
33013332
})
33023333

33033334
// Trigger auto-rename on first message in a new sub-chat
33043335
if (messagesLengthRef.current === 0 && !hasTriggeredRenameRef.current) {
33053336
hasTriggeredRenameRef.current = true
3306-
onAutoRename(text || "Image message", subChatId)
3337+
onAutoRename(finalText || "Image message", subChatId)
33073338
}
33083339

33093340
// Build message parts: images first, then files, then text
@@ -3361,8 +3392,8 @@ const ChatViewInner = memo(function ChatViewInner({
33613392
mentionPrefix = [...quoteMentions, ...diffMentions, ...pastedTextMentions].join(" ") + " "
33623393
}
33633394

3364-
if (text || mentionPrefix) {
3365-
parts.push({ type: "text", text: mentionPrefix + (text || "") })
3395+
if (finalText || mentionPrefix) {
3396+
parts.push({ type: "text", text: mentionPrefix + (finalText || "") })
33663397
}
33673398

33683399
clearAll()
@@ -3557,6 +3588,35 @@ const ChatViewInner = memo(function ChatViewInner({
35573588
}
35583589

35593590
const text = inputValue.trim()
3591+
3592+
// Expand custom slash commands with arguments (e.g. "/Apex my argument")
3593+
let finalText = text
3594+
const slashMatch = text.match(/^\/(\S+)\s*(.*)$/s)
3595+
if (slashMatch) {
3596+
const [, commandName, args] = slashMatch
3597+
const builtinNames = new Set(
3598+
BUILTIN_SLASH_COMMANDS.map((cmd) => cmd.name),
3599+
)
3600+
if (!builtinNames.has(commandName)) {
3601+
try {
3602+
const commands = await trpcClient.commands.list.query({
3603+
projectPath,
3604+
})
3605+
const cmd = commands.find(
3606+
(c) => c.name.toLowerCase() === commandName.toLowerCase(),
3607+
)
3608+
if (cmd) {
3609+
const { content } = await trpcClient.commands.getContent.query({
3610+
path: cmd.path,
3611+
})
3612+
finalText = content.replace(/\$ARGUMENTS/g, args.trim())
3613+
}
3614+
} catch (error) {
3615+
console.error("Failed to expand custom slash command:", error)
3616+
}
3617+
}
3618+
}
3619+
35603620
// Clear editor and draft from localStorage
35613621
editorRef.current?.clear()
35623622
if (parentChatId) {
@@ -3566,7 +3626,7 @@ const ChatViewInner = memo(function ChatViewInner({
35663626
// Track message sent
35673627
trackMessageSent({
35683628
workspaceId: subChatId,
3569-
messageLength: text.length,
3629+
messageLength: finalText.length,
35703630
mode: isPlanModeRef.current ? "plan" : "agent",
35713631
})
35723632

@@ -3596,8 +3656,8 @@ const ChatViewInner = memo(function ChatViewInner({
35963656
})),
35973657
]
35983658

3599-
if (text) {
3600-
parts.push({ type: "text", text })
3659+
if (finalText) {
3660+
parts.push({ type: "text", text: finalText })
36013661
}
36023662

36033663
// Clear attachments

src/renderer/features/agents/main/new-chat-form.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,8 @@ export function NewChatForm({
776776
}
777777

778778
// Check if message is a slash command with arguments (e.g. "/hello world")
779-
const slashMatch = message.match(/^\/(\S+)\s*(.*)$/)
779+
// Note: 's' flag makes '.' match newlines, so multi-line arguments are captured
780+
const slashMatch = message.match(/^\/(\S+)\s*(.*)$/s)
780781
if (slashMatch) {
781782
const [, commandName, args] = slashMatch
782783

@@ -790,7 +791,7 @@ export function NewChatForm({
790791
const commands = await trpcUtils.commands.list.fetch({
791792
projectPath: validatedProject?.path,
792793
})
793-
const cmd = commands.find((c) => c.name === commandName)
794+
const cmd = commands.find((c) => c.name.toLowerCase() === commandName.toLowerCase())
794795

795796
if (cmd) {
796797
const { content } = await trpcUtils.commands.getContent.fetch({

0 commit comments

Comments
 (0)