fix(prompt): persist cursor command replay context

This commit is contained in:
leokun
2026-07-30 20:28:20 +08:00
parent 4451a6df3b
commit 3349b13a2b
6 changed files with 62 additions and 261 deletions
+15 -4
View File
@@ -26,13 +26,10 @@ func BuildUserMessageReplayMessage(userMessage *agentv1.UserMessage) (Message, b
func buildUserReplayMessage(text string, selectedContext *agentv1.SelectedContext) (Message, bool) {
images := buildSelectedImageContentParts(selectedContext)
sections := make([]string, 0, 5)
sections := make([]string, 0, 4)
if text != "" {
sections = append(sections, formatMessageText(fmt.Sprintf("<user_query>\n%s\n</user_query>", text)))
}
if cursorCommands := buildSelectedCursorCommandsPromptSection(selectedContext); cursorCommands != "" {
sections = append(sections, cursorCommands)
}
if ideState := buildSelectedIDEStatePromptSection(selectedContext); ideState != "" {
sections = append(sections, ideState)
}
@@ -65,6 +62,20 @@ func buildUserReplayMessage(text string, selectedContext *agentv1.SelectedContex
}, true
}
// BuildSelectedCursorCommandsReplayMessage renders command content for new history entries.
// Keeping this separate from BuildUserMessageReplayMessage prevents old user_message entries
// from changing their model-visible meaning after a backend upgrade.
func BuildSelectedCursorCommandsReplayMessage(userMessage *agentv1.UserMessage) (Message, bool) {
if userMessage == nil {
return Message{}, false
}
content := buildSelectedCursorCommandsPromptSection(userMessage.GetSelectedContext())
if content == "" {
return Message{}, false
}
return Message{Role: "user", Content: content}, true
}
func buildSelectedCursorCommandsPromptSection(selectedContext *agentv1.SelectedContext) string {
if selectedContext == nil || len(selectedContext.GetCursorCommands()) == 0 {
return ""
@@ -2,67 +2,9 @@ package promptengine
import (
"reflect"
"strings"
"testing"
"cursor/gen/agentv1"
)
func TestBuildUserMessageReplayMessageIncludesSelectedCursorCommands(t *testing.T) {
message, ok := BuildUserMessageReplayMessage(&agentv1.UserMessage{
Text: "/init",
SelectedContext: &agentv1.SelectedContext{
CursorCommands: []*agentv1.SelectedCursorCommand{
{Name: "init", Content: "Analyze the repository and create AGENTS.md."},
{Name: `review"<&`, Content: "Review the implementation."},
},
},
})
if !ok {
t.Fatal("BuildUserMessageReplayMessage() returned ok=false")
}
want := strings.Join([]string{
"<user_query>\n/init\n</user_query>",
"<cursor_commands>\n" +
"<cursor_command name=\"init\">\nAnalyze the repository and create AGENTS.md.\n</cursor_command>\n\n" +
"<cursor_command name=\"review&quot;&lt;&amp;\">\nReview the implementation.\n</cursor_command>\n" +
"</cursor_commands>",
}, "\n\n")
if message.Role != "user" || message.Content != want {
t.Fatalf("message = %#v, want content %q", message, want)
}
}
func TestBuildUserMessageReplayMessageSkipsEmptyCursorCommandsAndKeepsOrder(t *testing.T) {
message, ok := BuildUserMessageReplayMessage(&agentv1.UserMessage{
Text: "run commands",
SelectedContext: &agentv1.SelectedContext{
CursorCommands: []*agentv1.SelectedCursorCommand{
nil,
{Name: "empty", Content: " "},
{Content: "First command."},
{Name: "second", Content: "Second command."},
},
},
})
if !ok {
t.Fatal("BuildUserMessageReplayMessage() returned ok=false")
}
first := strings.Index(message.Content, "First command.")
second := strings.Index(message.Content, "Second command.")
if first < 0 || second < 0 || first >= second {
t.Fatalf("cursor command order was not preserved: %q", message.Content)
}
if strings.Contains(message.Content, "empty") {
t.Fatalf("empty cursor command was not skipped: %q", message.Content)
}
if !strings.Contains(message.Content, "<cursor_command>\nFirst command.\n</cursor_command>") {
t.Fatalf("unnamed cursor command was not rendered safely: %q", message.Content)
}
}
func TestBuildReplayMessagesFromPendingAssistantOutputsKeepsTextAndToolCallInOneAssistantTurn(t *testing.T) {
raw := `{
"id":"1",