Skip to content

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

Open
heavygee wants to merge 2 commits into
tiann:mainfrom
heavygee:feat/cursor-summarize-738
Open

feat(cli): wire Cursor /summarize and /clear slash builtins#747
heavygee wants to merge 2 commits into
tiann:mainfrom
heavygee:feat/cursor-summarize-738

Conversation

@heavygee
Copy link
Copy Markdown
Contributor

Summary

  • Seed BUILTIN_SLASH_COMMANDS.cursor with summarize and clear for web autocomplete.
  • Add parseCursorSpecialCommand and handle /summarize (optional trailing instructions) and strict /clear in cursorRemoteLauncher.
  • Pass slash commands through to agent -p unchanged (v1 integration contract); invalid /clear … is rejected without spawning.

Fixes #738

Problem

Cursor supports /summarize in the product, but HAPI exposed an empty cursor builtin list and forwarded hub messages to agent -p with 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 typecheck
  • bun run test (includes cli/src/cursor/cursorSpecialCommands.test.ts)
  • cd web && bun run test (codexSlashCommands.test.ts cursor builtin case)
  • Operator dogfood on daily driver soup: slash autocomplete works; /summarize completes a remote turn on a Cursor session

Follow-ups (out of scope for this PR)

  1. Prove native compaction — measure Cursor context usage / token-count before and after /summarize headless; issue acceptance for peer-relocate still needs this.
  2. Persist status to hubContext summarization requested currently goes to the Ink message buffer only; consider sendSessionEvent or a hub message row for auditability in the web thread.
  3. cursorRemoteLauncher integration test — mock queue + assert invalid /clear foo does not spawn agent.
  4. spawn-peer / parentContext.policy=compact — when flavor is cursor, queue /summarize with relocate recap template (product spec §4.4.1).
  5. Case-insensitive /summarize — only if Cursor CLI documents mixed-case behavior.
  6. docs/guide/cursor.md — document pass-through semantics and dogfood expectations.

Made with Cursor

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Major] Cursor slash commands are parsed after queue batching — session.queue.waitForMessagesAndGetAsString() can join multiple same-mode web messages with \n before the new parser runs, so a queued /clear now followed by a normal prompt is consumed as one batch and the invalid-command branch drops the normal prompt too. Exact /clear and /summarize can also stop matching once another message is batched behind them. Evidence: cli/src/cursor/cursorRemoteLauncher.ts:98, with Cursor still enqueuing every message via plain messageQueue.push(...) at cli/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 have bun on PATH.

HAPI Bot

}

const { message, mode } = batch;
const specialCommand = parseCursorSpecialCommand(message);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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>
@heavygee heavygee force-pushed the feat/cursor-summarize-738 branch from d4847bc to 81c9bdd Compare May 31, 2026 12:15
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Major] Cursor slash commands are still parsed after queue batching — session.queue.waitForMessagesAndGetAsString() can combine multiple same-mode web messages before parseCursorSpecialCommand() sees them. If /clear now is queued ahead of a normal prompt, MessageQueue2 consumes and acks both local IDs as one batch, then the invalid-command branch drops the normal prompt without spawning Cursor. Exact /clear and /summarize also 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 runCursor or 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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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>
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • [Major] Isolated Cursor slash commands drop older queued prompts — enqueueCursorUserMessage() calls pushIsolateAndClear() 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 in cli/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: bun is not installed in this runner (/bin/bash: bun: command not found). Add/adjust a queue test for normal prompt/summarizenext prompt to assert all three are processed in order.

HAPI Bot

): void {
const specialCommand = parseCursorSpecialCommand(formattedText);
if (specialCommand.type !== null) {
messageQueue.pushIsolateAndClear(formattedText.trim(), enhancedMode, localId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(cli): wire Cursor /summarize for hub-driven context compaction

1 participant