feat(cli): wire Cursor /summarize and /clear slash builtins (#747)

* feat(cursor): wire /summarize and /clear slash builtins for remote sessions

Seed cursor builtins for web autocomplete, parse summarize/clear in
cursorRemoteLauncher (pass-through to agent -p; reject /clear with args).

Fixes tiann/hapi#738

Co-authored-by: Cursor <cursoragent@cursor.com>

* 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 #747 review.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cursor): preserve pending messages when isolating slash commands

pushIsolateAndClear() wipes the entire queue, so a normal prompt queued
before /summarize or /clear would be silently dropped. Add pushIsolated()
- isolation without clearing - and route Cursor slash commands through
it instead. Adds queue tests covering the preserve-then-isolate path.

Addresses PR #747 review.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
HeavyGee
2026-06-01 12:07:57 +08:00
committed by GitHub
co-authored by Cursor
parent 02d4e93178
commit 449cf6af0a
10 changed files with 230 additions and 2 deletions
@@ -0,0 +1,53 @@
import { describe, expect, it } from 'vitest';
import { MessageQueue2 } from '@/utils/MessageQueue2';
import { parseCursorSpecialCommand } from './cursorSpecialCommands';
import { enqueueCursorUserMessage } from './cursorUserMessageQueue';
import type { EnhancedMode } from './loop';
const mode: EnhancedMode = { permissionMode: 'default' };
describe('enqueueCursorUserMessage', () => {
it('does not batch invalid /clear with a following prompt', async () => {
const queue = new MessageQueue2<EnhancedMode>((m) => m.permissionMode);
enqueueCursorUserMessage(queue, '/clear now', mode, 'a');
enqueueCursorUserMessage(queue, 'continue work', mode, 'b');
const first = await queue.waitForMessagesAndGetAsString();
expect(first?.message).toBe('/clear now');
expect(parseCursorSpecialCommand(first!.message).type).toBe('invalid');
const second = await queue.waitForMessagesAndGetAsString();
expect(second?.message).toBe('continue work');
});
it('isolates /summarize from a following same-mode prompt', async () => {
const queue = new MessageQueue2<EnhancedMode>((m) => m.permissionMode);
enqueueCursorUserMessage(queue, '/summarize keep recap', mode, 'a');
enqueueCursorUserMessage(queue, 'next task', mode, 'b');
const first = await queue.waitForMessagesAndGetAsString();
expect(first?.message).toBe('/summarize keep recap');
expect(parseCursorSpecialCommand(first!.message).type).toBe('summarize');
const second = await queue.waitForMessagesAndGetAsString();
expect(second?.message).toBe('next task');
});
it('preserves a normal prompt queued before a slash command', async () => {
const queue = new MessageQueue2<EnhancedMode>((m) => m.permissionMode);
enqueueCursorUserMessage(queue, 'first work', mode, 'a');
enqueueCursorUserMessage(queue, '/summarize', mode, 'b');
enqueueCursorUserMessage(queue, 'after summarize', mode, 'c');
const first = await queue.waitForMessagesAndGetAsString();
expect(first?.message).toBe('first work');
expect(first?.isolate).toBe(false);
const second = await queue.waitForMessagesAndGetAsString();
expect(second?.message).toBe('/summarize');
expect(second?.isolate).toBe(true);
const third = await queue.waitForMessagesAndGetAsString();
expect(third?.message).toBe('after summarize');
});
});