Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions cmd/entire/cli/agent/opencode/transcript.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,48 @@ func ExtractTextFromParts(parts []Part) string {
return strings.Join(texts, "\n")
}

// Tags used by oh-my-opencode and similar orchestration tools to inject
// context into the conversation with role "user" — not actual user prompts.
const (
systemReminderOpen = "<system-reminder>"
systemReminderClose = "</system-reminder>"
)

// isSystemReminderOnly reports whether content consists entirely of
// <system-reminder>...</system-reminder> blocks (after trimming whitespace).
// Delegates to stripSystemReminders to correctly handle multiple blocks
// (HasPrefix+HasSuffix would false-positive on "<sr>a</sr>real<sr>b</sr>").
func isSystemReminderOnly(content string) bool {
if strings.TrimSpace(content) == "" {
return false
}
return stripSystemReminders(content) == ""
}

// stripSystemReminders removes all <system-reminder>...</system-reminder>
// sections from content and returns the remaining text. If nothing remains
// after stripping (or the content was system-reminder-only), it returns "".
func stripSystemReminders(content string) string {
result := content
for {
start := strings.Index(result, systemReminderOpen)
if start == -1 {
break
}
end := strings.Index(result[start:], systemReminderClose)
if end == -1 {
break
}
end += start + len(systemReminderClose)
result = result[:start] + result[end:]
}
return strings.TrimSpace(result)
}

// ExtractAllUserPrompts extracts all user prompts from raw export JSON transcript bytes.
// This is a package-level function used by the condensation path.
// Messages that consist entirely of <system-reminder> tags (e.g. from oh-my-opencode)
// are excluded. Mixed messages have their system-reminder sections stripped.
func ExtractAllUserPrompts(data []byte) ([]string, error) {
session, err := ParseExportSession(data)
if err != nil {
Expand All @@ -226,6 +266,13 @@ func ExtractAllUserPrompts(data []byte) ([]string, error) {
continue
}
content := ExtractTextFromParts(msg.Parts)
if content == "" {
continue
}
if isSystemReminderOnly(content) {
continue
}
content = stripSystemReminders(content)
if content != "" {
prompts = append(prompts, content)
}
Expand Down
220 changes: 220 additions & 0 deletions cmd/entire/cli/agent/opencode/transcript_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,226 @@ func TestExtractModifiedFiles(t *testing.T) {
}
}

func TestExtractAllUserPrompts(t *testing.T) {
t.Parallel()

prompts, err := ExtractAllUserPrompts([]byte(testExportJSON))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(prompts) != 2 {
t.Fatalf("expected 2 prompts, got %d: %v", len(prompts), prompts)
}
if prompts[0] != "Fix the bug in main.go" {
t.Errorf("expected first prompt 'Fix the bug in main.go', got %q", prompts[0])
}
if prompts[1] != "Also fix util.go" {
t.Errorf("expected second prompt 'Also fix util.go', got %q", prompts[1])
}
}

func TestExtractAllUserPrompts_SystemReminderOnly(t *testing.T) {
t.Parallel()

session := ExportSession{
Info: SessionInfo{ID: "test-sysreminder"},
Messages: []ExportMessage{
{
Info: MessageInfo{ID: "msg-1", Role: "user"},
Parts: []Part{
{Type: "text", Text: "Fix the bug"},
},
},
{
Info: MessageInfo{ID: "msg-2", Role: "user"},
Parts: []Part{
{Type: "text", Text: "<system-reminder>\nAs you answer the user's questions, you can use the following context:\nContents of CLAUDE.md...\n</system-reminder>"},
},
},
{
Info: MessageInfo{ID: "msg-3", Role: "user"},
Parts: []Part{
{Type: "text", Text: "Now fix util.go"},
},
},
},
}
data, err := json.Marshal(session)
if err != nil {
t.Fatalf("failed to marshal: %v", err)
}

prompts, err := ExtractAllUserPrompts(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(prompts) != 2 {
t.Fatalf("expected 2 prompts (system-reminder excluded), got %d: %v", len(prompts), prompts)
}
if prompts[0] != "Fix the bug" {
t.Errorf("expected 'Fix the bug', got %q", prompts[0])
}
if prompts[1] != "Now fix util.go" {
t.Errorf("expected 'Now fix util.go', got %q", prompts[1])
}
}

func TestExtractAllUserPrompts_SystemReminderMixed(t *testing.T) {
t.Parallel()

session := ExportSession{
Info: SessionInfo{ID: "test-mixed"},
Messages: []ExportMessage{
{
Info: MessageInfo{ID: "msg-1", Role: "user"},
Parts: []Part{
{Type: "text", Text: "Fix the bug\n<system-reminder>\nCLAUDE.md contents here\n</system-reminder>"},
},
},
},
}
data, err := json.Marshal(session)
if err != nil {
t.Fatalf("failed to marshal: %v", err)
}

prompts, err := ExtractAllUserPrompts(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(prompts) != 1 {
t.Fatalf("expected 1 prompt, got %d: %v", len(prompts), prompts)
}
if prompts[0] != "Fix the bug" {
t.Errorf("expected 'Fix the bug', got %q", prompts[0])
}
}

func TestExtractAllUserPrompts_SystemReminderWithWhitespace(t *testing.T) {
t.Parallel()

session := ExportSession{
Info: SessionInfo{ID: "test-whitespace"},
Messages: []ExportMessage{
{
Info: MessageInfo{ID: "msg-1", Role: "user"},
Parts: []Part{
{Type: "text", Text: " \n<system-reminder>\nsome context\n</system-reminder>\n "},
},
},
},
}
data, err := json.Marshal(session)
if err != nil {
t.Fatalf("failed to marshal: %v", err)
}

prompts, err := ExtractAllUserPrompts(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(prompts) != 0 {
t.Fatalf("expected 0 prompts (whitespace + system-reminder), got %d: %v", len(prompts), prompts)
}
}

func TestIsSystemReminderOnly(t *testing.T) {
t.Parallel()

tests := []struct {
name string
content string
want bool
}{
{
name: "exact system-reminder",
content: "<system-reminder>context here</system-reminder>",
want: true,
},
{
name: "with surrounding whitespace",
content: " \n<system-reminder>context</system-reminder>\n ",
want: true,
},
{
name: "not system-reminder",
content: "Fix the bug",
want: false,
},
{
name: "mixed content",
content: "Fix the bug\n<system-reminder>context</system-reminder>",
want: false,
},
{
name: "empty",
content: "",
want: false,
},
{
// Multiple blocks with real content between them — starts with open tag
// and ends with close tag, but is NOT system-reminder-only.
name: "real content between multiple blocks",
content: "<system-reminder>a</system-reminder>Fix this<system-reminder>b</system-reminder>",
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := isSystemReminderOnly(tt.content); got != tt.want {
t.Errorf("isSystemReminderOnly(%q) = %v, want %v", tt.content, got, tt.want)
}
})
}
}

func TestStripSystemReminders(t *testing.T) {
t.Parallel()

tests := []struct {
name string
content string
want string
}{
{
name: "no system-reminder",
content: "Fix the bug",
want: "Fix the bug",
},
{
name: "only system-reminder",
content: "<system-reminder>context</system-reminder>",
want: "",
},
{
name: "mixed content",
content: "Fix the bug\n<system-reminder>context</system-reminder>",
want: "Fix the bug",
},
{
name: "system-reminder in middle",
content: "First part\n<system-reminder>context</system-reminder>\nSecond part",
want: "First part\n\nSecond part",
},
{
name: "multiple system-reminders",
content: "<system-reminder>a</system-reminder>Fix this<system-reminder>b</system-reminder>",
want: "Fix this",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := stripSystemReminders(tt.content); got != tt.want {
t.Errorf("stripSystemReminders(%q) = %q, want %q", tt.content, got, tt.want)
}
})
}
}

// Compile-time interface checks are in transcript.go.
// Verify the unused import guard by referencing the agent package.
var _ = agent.AgentNameOpenCode
Loading