Skip to content

Commit 396a030

Browse files
heavygeecursoragent
andcommitted
fix(cursor): isolate slash commands before message queue batching
Parse summarize/clear at enqueue time (runCursor) with pushIsolateAndClear so waitForMessagesAndGetAsString never merges a slash with the next prompt. Adds queue policy tests for invalid /clear + following message. Addresses PR tiann#747 review. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 81c9bdd commit 396a030

3 files changed

Lines changed: 58 additions & 1 deletion

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { MessageQueue2 } from '@/utils/MessageQueue2';
3+
import { parseCursorSpecialCommand } from './cursorSpecialCommands';
4+
import { enqueueCursorUserMessage } from './cursorUserMessageQueue';
5+
import type { EnhancedMode } from './loop';
6+
7+
const mode: EnhancedMode = { permissionMode: 'default' };
8+
9+
describe('enqueueCursorUserMessage', () => {
10+
it('does not batch invalid /clear with a following prompt', async () => {
11+
const queue = new MessageQueue2<EnhancedMode>((m) => m.permissionMode);
12+
enqueueCursorUserMessage(queue, '/clear now', mode, 'a');
13+
enqueueCursorUserMessage(queue, 'continue work', mode, 'b');
14+
15+
const first = await queue.waitForMessagesAndGetAsString();
16+
expect(first?.message).toBe('/clear now');
17+
expect(parseCursorSpecialCommand(first!.message).type).toBe('invalid');
18+
19+
const second = await queue.waitForMessagesAndGetAsString();
20+
expect(second?.message).toBe('continue work');
21+
});
22+
23+
it('isolates /summarize from a following same-mode prompt', async () => {
24+
const queue = new MessageQueue2<EnhancedMode>((m) => m.permissionMode);
25+
enqueueCursorUserMessage(queue, '/summarize keep recap', mode, 'a');
26+
enqueueCursorUserMessage(queue, 'next task', mode, 'b');
27+
28+
const first = await queue.waitForMessagesAndGetAsString();
29+
expect(first?.message).toBe('/summarize keep recap');
30+
expect(parseCursorSpecialCommand(first!.message).type).toBe('summarize');
31+
32+
const second = await queue.waitForMessagesAndGetAsString();
33+
expect(second?.message).toBe('next task');
34+
});
35+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { EnhancedMode } from './loop';
2+
import { parseCursorSpecialCommand } from './cursorSpecialCommands';
3+
import type { MessageQueue2 } from '@/utils/MessageQueue2';
4+
5+
/**
6+
* Enqueue a Cursor user message. Special slash commands are isolated so they are
7+
* never newline-batched with adjacent same-mode prompts.
8+
*/
9+
export function enqueueCursorUserMessage(
10+
messageQueue: MessageQueue2<EnhancedMode>,
11+
formattedText: string,
12+
enhancedMode: EnhancedMode,
13+
localId?: string
14+
): void {
15+
const specialCommand = parseCursorSpecialCommand(formattedText);
16+
if (specialCommand.type !== null) {
17+
messageQueue.pushIsolateAndClear(formattedText.trim(), enhancedMode, localId);
18+
return;
19+
}
20+
messageQueue.push(formattedText, enhancedMode, localId);
21+
}

cli/src/cursor/runCursor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { createModeChangeHandler, createRunnerLifecycle, setControlledByUser } f
1111
import { registerSessionConfigRpc } from '@/agent/sessionConfigRpc';
1212
import { formatMessageWithAttachments } from '@/utils/attachmentFormatter';
1313
import { getInvokedCwd } from '@/utils/invokedCwd';
14+
import { enqueueCursorUserMessage } from './cursorUserMessageQueue';
1415

1516
const formatFailureReason = (message: string): string => {
1617
const maxLength = 200;
@@ -96,7 +97,7 @@ export async function runCursor(opts: {
9697
model: currentModel
9798
};
9899
const formattedText = formatMessageWithAttachments(message.content.text, message.content.attachments);
99-
messageQueue.push(formattedText, enhancedMode, localId);
100+
enqueueCursorUserMessage(messageQueue, formattedText, enhancedMode, localId);
100101
});
101102

102103
session.onCancelQueuedMessage((localId) => {

0 commit comments

Comments
 (0)