Skip to content

Commit 8600660

Browse files
authored
🤖 Fix /compact multiline parsing bug (#268)
## Problem Multiline content after the `/compact` command was being incorrectly parsed as flags, causing rejection or unexpected behavior. **Example of bug:** ``` /compact -t should be treated as message content ``` This would error with: `-t requires a positive number, got should` instead of treating the entire line as the continue message. ## Root Cause The parser was tokenizing the entire input (including content after newlines) and passing all tokens to minimist for flag parsing. This meant any text after a newline that looked like a flag (e.g., `-t`, `-c`, `--anything`) would be interpreted as a flag instead of message content. ## Solution Changed the `/compact` handler to: 1. Split `rawInput` at the first newline 2. Tokenize **only** the first line for flag parsing 3. Use remaining lines (after first newline) as multiline continue message This ensures content after newlines is never interpreted as flags. ## Testing **New test cases added:** - Multiline content starting with flag-like text (`-t`, `-c`) - Combination of flags on first line + flag-like text in multiline content **All existing tests pass** (25/25 slash command tests, 537 total tests) _Generated with `cmux`_
1 parent f95eed3 commit 8600660

File tree

2 files changed

+30
-5
lines changed

2 files changed

+30
-5
lines changed

src/utils/slashCommands/compact.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,23 @@ describe("multiline continue messages", () => {
214214
continueMessage: undefined,
215215
});
216216
});
217+
218+
it("does not parse lines after newline as flags", () => {
219+
// Bug: multiline content starting with -t or -c should not be parsed as flags
220+
const result = parseCommand("/compact\n-t should be treated as message content");
221+
expect(result).toEqual({
222+
type: "compact",
223+
maxOutputTokens: undefined,
224+
continueMessage: "-t should be treated as message content",
225+
});
226+
});
227+
228+
it("does not parse lines after newline as flags with existing flag", () => {
229+
const result = parseCommand("/compact -t 5000\n-c this is not a flag");
230+
expect(result).toEqual({
231+
type: "compact",
232+
maxOutputTokens: 5000,
233+
continueMessage: "-c this is not a flag",
234+
});
235+
});
217236
});

src/utils/slashCommands/registry.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,16 +173,22 @@ const compactCommandDefinition: SlashCommandDefinition = {
173173
key: "compact",
174174
description:
175175
"Compact conversation history using AI summarization. Use -t <tokens> to set max output tokens. Add continue message on lines after the command.",
176-
handler: ({ cleanRemainingTokens, rawInput }): ParsedCommand => {
176+
handler: ({ rawInput }): ParsedCommand => {
177177
// Split rawInput into first line (for flags) and remaining lines (for multiline continue)
178178
// rawInput format: "-t 5000\nContinue here" or "\nContinue here" (starts with newline if no flags)
179179
const hasMultilineContent = rawInput.includes("\n");
180180
const lines = rawInput.split("\n");
181-
// Note: firstLine could be empty string if rawInput starts with \n (which is fine)
181+
const firstLine = lines[0]; // First line contains flags
182182
const remainingLines = lines.slice(1).join("\n").trim();
183183

184+
// Tokenize ONLY the first line to extract flags
185+
// This prevents content after newlines from being parsed as flags
186+
const firstLineTokens = (firstLine.match(/(?:[^\s"]+|"[^"]*")+/g) ?? []).map((token) =>
187+
token.replace(/^"(.*)"$/, "$1")
188+
);
189+
184190
// Parse flags from first line using minimist
185-
const parsed = minimist(cleanRemainingTokens, {
191+
const parsed = minimist(firstLineTokens, {
186192
string: ["t", "c"],
187193
unknown: (arg: string) => {
188194
// Unknown flags starting with - are errors
@@ -193,8 +199,8 @@ const compactCommandDefinition: SlashCommandDefinition = {
193199
},
194200
});
195201

196-
// Check for unknown flags
197-
const unknownFlags = cleanRemainingTokens.filter(
202+
// Check for unknown flags (only from first line)
203+
const unknownFlags = firstLineTokens.filter(
198204
(token) => token.startsWith("-") && token !== "-t" && token !== "-c"
199205
);
200206
if (unknownFlags.length > 0) {

0 commit comments

Comments
 (0)