Skip to content

Commit 81c9bdd

Browse files
heavygeecursoragent
andcommitted
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#738 Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 994a820 commit 81c9bdd

5 files changed

Lines changed: 95 additions & 1 deletion

File tree

cli/src/cursor/cursorRemoteLauncher.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import type { CursorSession } from './session';
1414
import type { CursorStreamEvent } from './utils/cursorEventConverter';
1515
import { parseCursorEvent, convertCursorEventToAgentMessage } from './utils/cursorEventConverter';
16+
import { parseCursorSpecialCommand } from './cursorSpecialCommands';
1617

1718
function buildAgentArgs(opts: {
1819
message: string;
@@ -97,10 +98,29 @@ class CursorRemoteLauncher extends RemoteLauncherBase {
9798
}
9899

99100
const { message, mode } = batch;
101+
const specialCommand = parseCursorSpecialCommand(message);
102+
103+
if (specialCommand.type === 'invalid') {
104+
session.sendSessionEvent({ type: 'message', message: specialCommand.message });
105+
messageBuffer.addMessage(specialCommand.message, 'status');
106+
if (session.queue.size() === 0 && !this.shouldExit) {
107+
sendReady();
108+
}
109+
continue;
110+
}
111+
100112
const { mode: agentMode, yolo } = permissionModeToAgentArgs(mode.permissionMode as string);
101113
this.applyDisplayMode(mode.permissionMode as string);
102114
messageBuffer.addMessage(message, 'user');
103115

116+
if (specialCommand.type === 'summarize') {
117+
logger.debug('[cursor-remote] /summarize — pass-through to agent -p');
118+
messageBuffer.addMessage('Context summarization requested', 'status');
119+
} else if (specialCommand.type === 'clear') {
120+
logger.debug('[cursor-remote] /clear — pass-through to agent -p');
121+
messageBuffer.addMessage('Context clear requested', 'status');
122+
}
123+
104124
const args = buildAgentArgs({
105125
message,
106126
cwd: session.path,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { parseCursorSpecialCommand } from './cursorSpecialCommands';
3+
4+
describe('parseCursorSpecialCommand', () => {
5+
it('accepts /summarize with optional instructions', () => {
6+
expect(parseCursorSpecialCommand('/summarize')).toEqual({
7+
type: 'summarize',
8+
message: '/summarize'
9+
});
10+
expect(parseCursorSpecialCommand(' /summarize keep peer relocate recap ')).toEqual({
11+
type: 'summarize',
12+
message: '/summarize keep peer relocate recap'
13+
});
14+
});
15+
16+
it('accepts exact /clear', () => {
17+
expect(parseCursorSpecialCommand(' /clear ')).toEqual({ type: 'clear' });
18+
});
19+
20+
it('rejects /clear with arguments', () => {
21+
expect(parseCursorSpecialCommand('/clear now')).toEqual({
22+
type: 'invalid',
23+
command: 'clear',
24+
message: '/clear does not accept arguments'
25+
});
26+
});
27+
28+
it('ignores regular slash-like messages', () => {
29+
expect(parseCursorSpecialCommand('/summarizer')).toEqual({ type: null });
30+
expect(parseCursorSpecialCommand('please /summarize')).toEqual({ type: null });
31+
expect(parseCursorSpecialCommand('/clearing')).toEqual({ type: null });
32+
});
33+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export type CursorSpecialCommand =
2+
| { type: 'summarize'; message: string }
3+
| { type: 'clear' }
4+
| { type: 'invalid'; command: 'clear'; message: string }
5+
| { type: null };
6+
7+
/**
8+
* Parse Cursor-specific slash commands for remote sessions.
9+
* Summarize accepts optional trailing instructions after the command.
10+
* Messages are still passed verbatim to `agent -p` — this parser is for detection and UI contract only.
11+
*/
12+
export function parseCursorSpecialCommand(message: string): CursorSpecialCommand {
13+
const trimmed = message.trim();
14+
15+
if (trimmed === '/summarize' || trimmed.startsWith('/summarize ')) {
16+
return { type: 'summarize', message: trimmed };
17+
}
18+
19+
if (trimmed === '/clear') {
20+
return { type: 'clear' };
21+
}
22+
23+
if (trimmed.startsWith('/clear ')) {
24+
return {
25+
type: 'invalid',
26+
command: 'clear',
27+
message: '/clear does not accept arguments'
28+
};
29+
}
30+
31+
return { type: null };
32+
}

shared/src/slashCommands.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ export const BUILTIN_SLASH_COMMANDS = {
3939
{ name: 'default', description: 'Return OpenCode permission mode to default', source: 'builtin' },
4040
{ name: 'init', description: 'Generate or refresh AGENTS.md for this project', source: 'builtin' },
4141
],
42-
cursor: [],
42+
cursor: [
43+
{ name: 'summarize', description: 'Summarize conversation context to free window space (pass-through to Cursor agent)', source: 'builtin' },
44+
{ name: 'clear', description: 'Clear conversation context if supported by Cursor agent', source: 'builtin' },
45+
],
4346
} as const satisfies Record<string, readonly SlashCommand[]>
4447

4548
export function getBuiltinSlashCommands(agent: string): SlashCommand[] {

web/src/lib/codexSlashCommands.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ describe('getBuiltinSlashCommands', () => {
1919
'permission',
2020
]))
2121
})
22+
23+
it('exposes Cursor summarize and clear builtins', () => {
24+
expect(getBuiltinSlashCommands('cursor').map((command) => command.name)).toEqual(
25+
expect.arrayContaining(['summarize', 'clear'])
26+
)
27+
})
2228
})
2329

2430
describe('mergeSlashCommands', () => {

0 commit comments

Comments
 (0)