From fe67deb3e220c3714a779d1ed1511651b06bd3cf Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 24 Jan 2026 17:21:06 +0800 Subject: [PATCH] Refactor Goal Name Extraction and Add Markdown Formatting Stripping - Enhanced the `extractGoalName` function to skip empty lines, markdown headers, and horizontal rules while extracting the first meaningful line as the goal name. - Implemented a new `stripMarkdownFormatting` function to remove various markdown formatting elements, including bold, italic, inline code, and link syntax. - Added comprehensive unit tests for both `extractGoalName` and `stripMarkdownFormatting` to ensure correct functionality across various markdown scenarios. --- agent/robot/executor/standard/executor.go | 96 ++++++++++--- .../robot/executor/standard/ui_fields_test.go | 131 ++++++++++++++++++ 2 files changed, 209 insertions(+), 18 deletions(-) diff --git a/agent/robot/executor/standard/executor.go b/agent/robot/executor/standard/executor.go index 685d5211..85144069 100644 --- a/agent/robot/executor/standard/executor.go +++ b/agent/robot/executor/standard/executor.go @@ -2,6 +2,7 @@ package standard import ( "fmt" + "strings" "sync/atomic" "time" @@ -523,29 +524,88 @@ func extractGoalName(goals *robottypes.Goals) string { return "" } - // Extract first line or first sentence as the goal name + // Extract first non-empty, non-markdown-header line as the goal name content := goals.Content - // Find first newline - if idx := indexAny(content, "\n\r"); idx > 0 { - content = content[:idx] - } - // Limit length - if len(content) > 150 { - content = content[:150] + "..." - } - return content -} + lines := strings.Split(content, "\n") -// indexAny returns the index of the first occurrence of any char in chars -func indexAny(s string, chars string) int { - for i, c := range s { - for _, ch := range chars { - if c == ch { - return i + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + // Skip markdown headers (# ## ### etc.) + if strings.HasPrefix(line, "#") { + continue + } + // Skip markdown horizontal rules (--- or ***) + if strings.HasPrefix(line, "---") || strings.HasPrefix(line, "***") { + continue + } + // Found a content line - strip markdown formatting + line = stripMarkdownFormatting(line) + // Limit length + if len(line) > 150 { + line = line[:150] + "..." + } + return line + } + + // Fallback: if all lines are headers, use first header without # prefix + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + // Strip leading # symbols + line = strings.TrimLeft(line, "#") + line = strings.TrimSpace(line) + line = stripMarkdownFormatting(line) + if line != "" { + if len(line) > 150 { + line = line[:150] + "..." } + return line } } - return -1 + + return "" +} + +// stripMarkdownFormatting removes common markdown formatting from text +func stripMarkdownFormatting(s string) string { + // Remove bold/italic markers + s = strings.ReplaceAll(s, "**", "") + s = strings.ReplaceAll(s, "__", "") + s = strings.ReplaceAll(s, "*", "") + s = strings.ReplaceAll(s, "_", "") + // Remove inline code + s = strings.ReplaceAll(s, "`", "") + // Remove link syntax [text](url) -> text + // Simple approach: just remove brackets and parentheses content + for { + start := strings.Index(s, "[") + if start == -1 { + break + } + end := strings.Index(s[start:], "]") + if end == -1 { + break + } + linkEnd := start + end + // Check if followed by (url) + if linkEnd+1 < len(s) && s[linkEnd+1] == '(' { + parenEnd := strings.Index(s[linkEnd+1:], ")") + if parenEnd != -1 { + // Extract just the link text + linkText := s[start+1 : linkEnd] + s = s[:start] + linkText + s[linkEnd+1+parenEnd+1:] + continue + } + } + // Just remove brackets + s = s[:start] + s[start+1:linkEnd] + s[linkEnd+1:] + } + return strings.TrimSpace(s) } // Verify Executor implements types.Executor diff --git a/agent/robot/executor/standard/ui_fields_test.go b/agent/robot/executor/standard/ui_fields_test.go index 0ef74ed2..6a1eb7de 100644 --- a/agent/robot/executor/standard/ui_fields_test.go +++ b/agent/robot/executor/standard/ui_fields_test.go @@ -307,6 +307,137 @@ func TestExtractGoalName(t *testing.T) { name := extractGoalName(goals) assert.Equal(t, "First goal", name) }) + + t.Run("skips_markdown_h1_header", func(t *testing.T) { + goals := &robottypes.Goals{ + Content: "# Goals\nSystem optimization and monitoring", + } + + name := extractGoalName(goals) + assert.Equal(t, "System optimization and monitoring", name) + }) + + t.Run("skips_markdown_h2_header", func(t *testing.T) { + goals := &robottypes.Goals{ + Content: "## Goals\n\nPerform system maintenance tasks", + } + + name := extractGoalName(goals) + assert.Equal(t, "Perform system maintenance tasks", name) + }) + + t.Run("skips_multiple_markdown_headers", func(t *testing.T) { + goals := &robottypes.Goals{ + Content: "## Goals\n### 1. [High] First Goal\nActual description here", + } + + name := extractGoalName(goals) + assert.Equal(t, "Actual description here", name) + }) + + t.Run("strips_bold_formatting", func(t *testing.T) { + goals := &robottypes.Goals{ + Content: "**Important** task to complete", + } + + name := extractGoalName(goals) + assert.Equal(t, "Important task to complete", name) + }) + + t.Run("strips_italic_formatting", func(t *testing.T) { + goals := &robottypes.Goals{ + Content: "*Urgent* system update needed", + } + + name := extractGoalName(goals) + assert.Equal(t, "Urgent system update needed", name) + }) + + t.Run("strips_inline_code", func(t *testing.T) { + goals := &robottypes.Goals{ + Content: "Run `npm install` command", + } + + name := extractGoalName(goals) + assert.Equal(t, "Run npm install command", name) + }) + + t.Run("skips_empty_lines", func(t *testing.T) { + goals := &robottypes.Goals{ + Content: "\n\n\nFirst real content\nSecond line", + } + + name := extractGoalName(goals) + assert.Equal(t, "First real content", name) + }) + + t.Run("fallback_to_header_content_if_only_headers", func(t *testing.T) { + goals := &robottypes.Goals{ + Content: "## Goals\n### Tasks", + } + + name := extractGoalName(goals) + assert.Equal(t, "Goals", name) + }) + + t.Run("skips_horizontal_rules", func(t *testing.T) { + goals := &robottypes.Goals{ + Content: "---\nActual content here", + } + + name := extractGoalName(goals) + assert.Equal(t, "Actual content here", name) + }) + + t.Run("handles_complex_markdown_content", func(t *testing.T) { + goals := &robottypes.Goals{ + Content: "## Goals\n\n### 1. [High] System Maintenance\n**Description**: Perform system optimization based on diagnostic results\n**Reason**: Time-sensitive maintenance", + } + + name := extractGoalName(goals) + assert.Equal(t, "Description: Perform system optimization based on diagnostic results", name) + }) +} + +// ============================================================================ +// stripMarkdownFormatting Tests +// ============================================================================ + +func TestStripMarkdownFormatting(t *testing.T) { + t.Run("strips_bold", func(t *testing.T) { + result := stripMarkdownFormatting("**bold text**") + assert.Equal(t, "bold text", result) + }) + + t.Run("strips_italic_asterisk", func(t *testing.T) { + result := stripMarkdownFormatting("*italic text*") + assert.Equal(t, "italic text", result) + }) + + t.Run("strips_italic_underscore", func(t *testing.T) { + result := stripMarkdownFormatting("_italic text_") + assert.Equal(t, "italic text", result) + }) + + t.Run("strips_inline_code", func(t *testing.T) { + result := stripMarkdownFormatting("`code`") + assert.Equal(t, "code", result) + }) + + t.Run("strips_link_syntax", func(t *testing.T) { + result := stripMarkdownFormatting("[link text](https://example.com)") + assert.Equal(t, "link text", result) + }) + + t.Run("preserves_plain_text", func(t *testing.T) { + result := stripMarkdownFormatting("plain text without formatting") + assert.Equal(t, "plain text without formatting", result) + }) + + t.Run("handles_mixed_formatting", func(t *testing.T) { + result := stripMarkdownFormatting("**bold** and *italic* and `code`") + assert.Equal(t, "bold and italic and code", result) + }) } // ============================================================================