feat(cli): wire Cursor /summarize and /clear slash builtins#747
feat(cli): wire Cursor /summarize and /clear slash builtins#747heavygee wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Findings
- [Major] Cursor slash commands are parsed after queue batching —
session.queue.waitForMessagesAndGetAsString()can join multiple same-mode web messages with\nbefore the new parser runs, so a queued/clear nowfollowed by a normal prompt is consumed as one batch and the invalid-command branch drops the normal prompt too. Exact/clearand/summarizecan also stop matching once another message is batched behind them. Evidence:cli/src/cursor/cursorRemoteLauncher.ts:98, with Cursor still enqueuing every message via plainmessageQueue.push(...)atcli/src/cursor/runCursor.ts:99.
Suggested fix:import { parseCursorSpecialCommand } from './cursorSpecialCommands'; // inside session.onUserMessage, before the default push const specialCommand = parseCursorSpecialCommand(formattedText); if (specialCommand.type !== null) { messageQueue.pushIsolateAndClear(formattedText.trim(), enhancedMode, localId); return; } messageQueue.push(formattedText, enhancedMode, localId);
Summary
- Review mode: initial
- One blocking behavior risk found in the new Cursor slash-command path: command detection happens too late, after queue batching has already changed message boundaries. Residual test gap: no remote launcher/queue test covers a special command queued next to another prompt.
Testing
- Not run (automation): attempted
bun test cli/src/cursor/cursorSpecialCommands.test.ts web/src/lib/codexSlashCommands.test.ts, but this runner does not havebunonPATH.
HAPI Bot
| } | ||
|
|
||
| const { message, mode } = batch; | ||
| const specialCommand = parseCursorSpecialCommand(message); |
There was a problem hiding this comment.
[MAJOR] waitForMessagesAndGetAsString() returns a newline-joined batch, but the new parser treats that whole batch as one command. If /clear now is queued before another same-mode prompt, line 100 rejects the batch and MessageQueue2 has already emitted both local IDs as consumed, so the following prompt is lost. Exact /clear//summarize also stop matching once another message is batched behind them. Move detection to runCursor before messageQueue.push(...) and enqueue Cursor special commands with isolation.
Suggested fix:
import { parseCursorSpecialCommand } from './cursorSpecialCommands';
const specialCommand = parseCursorSpecialCommand(formattedText);
if (specialCommand.type !== null) {
messageQueue.pushIsolateAndClear(formattedText.trim(), enhancedMode, localId);
return;
}
messageQueue.push(formattedText, enhancedMode, localId);…ssions 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>
d4847bc to
81c9bdd
Compare
There was a problem hiding this comment.
Findings
- [Major] Cursor slash commands are still parsed after queue batching —
session.queue.waitForMessagesAndGetAsString()can combine multiple same-mode web messages beforeparseCursorSpecialCommand()sees them. If/clear nowis queued ahead of a normal prompt,MessageQueue2consumes and acks both local IDs as one batch, then the invalid-command branch drops the normal prompt without spawning Cursor. Exact/clearand/summarizealso stop matching when another same-mode message is batched behind them. Evidence:cli/src/cursor/cursorRemoteLauncher.ts:101,cli/src/cursor/runCursor.ts:99,cli/src/utils/MessageQueue2.ts:288.
Suggested fix:import { parseCursorSpecialCommand } from './cursorSpecialCommands'; const specialCommand = parseCursorSpecialCommand(formattedText); if (specialCommand.type !== null) { messageQueue.pushIsolateAndClear(formattedText.trim(), enhancedMode, localId); return; } messageQueue.push(formattedText, enhancedMode, localId);
Summary
- Review mode: follow-up after new commits
- One unresolved correctness issue remains in the Cursor slash-command path: command detection happens after queue batching has already changed message boundaries. Residual test gap: no
runCursoror remote launcher test covers a Cursor special command queued next to another same-mode prompt.
Testing
- Not run (automation)
HAPI Bot
| } | ||
|
|
||
| const { message, mode } = batch; | ||
| const specialCommand = parseCursorSpecialCommand(message); |
There was a problem hiding this comment.
[MAJOR] waitForMessagesAndGetAsString() returns a newline-joined batch, but the new parser treats that whole batch as one command. If /clear now is queued before another same-mode prompt, this invalid-command branch drops the whole batch after MessageQueue2 has already marked both local IDs consumed; exact /clear and /summarize also stop matching when another message is batched behind them. Move Cursor special-command detection to runCursor before messageQueue.push(...) and enqueue matches in isolation.
Suggested fix:
import { parseCursorSpecialCommand } from './cursorSpecialCommands';
const specialCommand = parseCursorSpecialCommand(formattedText);
if (specialCommand.type !== null) {
messageQueue.pushIsolateAndClear(formattedText.trim(), enhancedMode, localId);
return;
}
messageQueue.push(formattedText, enhancedMode, localId);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>
There was a problem hiding this comment.
Findings
- [Major] Isolated Cursor slash commands drop older queued prompts —
enqueueCursorUserMessage()callspushIsolateAndClear()for every/summarize,/clear, and invalid/clear ...command. That method clears the whole pending queue before adding the command, so if a user has already queued normal prompts while Cursor is busy and then sends/summarize, those earlier prompts are silently discarded and never acknowledged/processed. This PR only needs a batch boundary for the slash command, not destructive queue clearing. Evidence:cli/src/cursor/cursorUserMessageQueue.ts:17, queue semantics incli/src/utils/MessageQueue2.ts:115.
Suggested fix:// MessageQueue2.ts pushIsolated(message: string, mode: T, localId?: string): void { if (this.closed) { throw new Error('Cannot push to closed queue'); } const modeHash = this.modeHasher(mode); this.queue.push({ message, mode, modeHash, localId, isolate: true }); this.onMessageHandler?.(message, mode); if (this.waiter) { const waiter = this.waiter; this.waiter = null; waiter(true); } } // cursorUserMessageQueue.ts if (specialCommand.type !== null) { messageQueue.pushIsolated(formattedText.trim(), enhancedMode, localId); return; }
Summary
- Review mode: follow-up after new commits
- The prior batching issue is addressed for messages queued after the slash command, but the current fix introduces a data-loss path for messages queued before a Cursor slash command.
Testing
- Not run:
bunis not installed in this runner (/bin/bash: bun: command not found). Add/adjust a queue test fornormal prompt→/summarize→next promptto assert all three are processed in order.
HAPI Bot
| ): void { | ||
| const specialCommand = parseCursorSpecialCommand(formattedText); | ||
| if (specialCommand.type !== null) { | ||
| messageQueue.pushIsolateAndClear(formattedText.trim(), enhancedMode, localId); |
There was a problem hiding this comment.
[MAJOR] pushIsolateAndClear() clears every pending item before enqueueing the slash command. That means a user can queue normal prompts while Cursor is busy, then send /summarize or /clear, and the older prompts are silently removed instead of being processed. The command only needs to be isolated from batching; it should not discard earlier queued work.
Suggested fix:
// MessageQueue2.ts
pushIsolated(message: string, mode: T, localId?: string): void {
if (this.closed) {
throw new Error('Cannot push to closed queue');
}
const modeHash = this.modeHasher(mode);
this.queue.push({ message, mode, modeHash, localId, isolate: true });
this.onMessageHandler?.(message, mode);
if (this.waiter) {
const waiter = this.waiter;
this.waiter = null;
waiter(true);
}
}
// cursorUserMessageQueue.ts
if (specialCommand.type !== null) {
messageQueue.pushIsolated(formattedText.trim(), enhancedMode, localId);
return;
}
Summary
BUILTIN_SLASH_COMMANDS.cursorwithsummarizeandclearfor web autocomplete.parseCursorSpecialCommandand handle/summarize(optional trailing instructions) and strict/clearincursorRemoteLauncher.agent -punchanged (v1 integration contract); invalid/clear …is rejected without spawning.Fixes #738
Problem
Cursor supports
/summarizein the product, but HAPI exposed an empty cursor builtin list and forwarded hub messages toagent -pwith no explicit slash handling. Peer relocate and hub-driven compaction need a documented pass-through path.Approach
Mirror the Codex special-command pattern: small parser module + launcher detection + status line. No HAPI-side RPC equivalent to Codex
thread/compact/start— Cursor compaction is delegated to the agent CLI when the slash is sent as the prompt.Testing
bun typecheckbun run test(includescli/src/cursor/cursorSpecialCommands.test.ts)cd web && bun run test(codexSlashCommands.test.tscursor builtin case)/summarizecompletes a remote turn on a Cursor sessionFollow-ups (out of scope for this PR)
token-countbefore and after/summarizeheadless; issue acceptance for peer-relocate still needs this.Context summarization requestedcurrently goes to the Ink message buffer only; considersendSessionEventor a hub message row for auditability in the web thread.cursorRemoteLauncherintegration test — mock queue + assert invalid/clear foodoes not spawnagent.spawn-peer/parentContext.policy=compact— when flavor iscursor, queue/summarizewith relocate recap template (product spec §4.4.1)./summarize— only if Cursor CLI documents mixed-case behavior.docs/guide/cursor.md— document pass-through semantics and dogfood expectations.Made with Cursor