diff --git a/internal/backend/agent/prompt/replay.go b/internal/backend/agent/prompt/replay.go index baf86f6..68560b7 100644 --- a/internal/backend/agent/prompt/replay.go +++ b/internal/backend/agent/prompt/replay.go @@ -26,10 +26,13 @@ func BuildUserMessageReplayMessage(userMessage *agentv1.UserMessage) (Message, b func buildUserReplayMessage(text string, selectedContext *agentv1.SelectedContext) (Message, bool) { images := buildSelectedImageContentParts(selectedContext) - sections := make([]string, 0, 4) + sections := make([]string, 0, 5) if text != "" { sections = append(sections, formatMessageText(fmt.Sprintf("\n%s\n", text))) } + if cursorCommands := buildSelectedCursorCommandsPromptSection(selectedContext); cursorCommands != "" { + sections = append(sections, cursorCommands) + } if ideState := buildSelectedIDEStatePromptSection(selectedContext); ideState != "" { sections = append(sections, ideState) } @@ -62,6 +65,32 @@ func buildUserReplayMessage(text string, selectedContext *agentv1.SelectedContex }, true } +func buildSelectedCursorCommandsPromptSection(selectedContext *agentv1.SelectedContext) string { + if selectedContext == nil || len(selectedContext.GetCursorCommands()) == 0 { + return "" + } + entries := make([]string, 0, len(selectedContext.GetCursorCommands())) + for _, command := range selectedContext.GetCursorCommands() { + if command == nil { + continue + } + content := strings.TrimSpace(command.GetContent()) + if content == "" { + continue + } + name := strings.TrimSpace(command.GetName()) + if name == "" { + entries = append(entries, "\n"+content+"\n") + continue + } + entries = append(entries, fmt.Sprintf("\n%s\n", escapePromptXML(name), content)) + } + if len(entries) == 0 { + return "" + } + return "\n" + strings.Join(entries, "\n\n") + "\n" +} + func buildSelectedIDEStatePromptSection(selectedContext *agentv1.SelectedContext) string { if selectedContext == nil || selectedContext.GetInvocationContext() == nil { return "" diff --git a/internal/backend/agent/prompt/replay_test.go b/internal/backend/agent/prompt/replay_test.go index 04abd85..fc91b90 100644 --- a/internal/backend/agent/prompt/replay_test.go +++ b/internal/backend/agent/prompt/replay_test.go @@ -2,9 +2,67 @@ 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{ + "\n/init\n", + "\n" + + "\nAnalyze the repository and create AGENTS.md.\n\n\n" + + "\nReview the implementation.\n\n" + + "", + }, "\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, "\nFirst command.\n") { + t.Fatalf("unnamed cursor command was not rendered safely: %q", message.Content) + } +} + func TestBuildReplayMessagesFromPendingAssistantOutputsKeepsTextAndToolCallInOneAssistantTurn(t *testing.T) { raw := `{ "id":"1",