on main help sign in
feat(prompts): remove long prompt option
efb3b03Kieran Klukas
internal/agent/agent_tool.go+2−2
@@ -13,7 +13,7 @@ import (
13)
13)
14
14
15//go:embed templates/agent_tool.md
15//go:embed templates/agent_tool.md
16−var agentToolDescription []byte
16+var agentToolDescription string
17
17
18type AgentParams struct {
18type AgentParams struct {
19 Prompt string `json:"prompt" description:"The task for the agent to perform"`
19 Prompt string `json:"prompt" description:"The task for the agent to perform"`
@@ -39,7 +39,7 @@ func (c *coordinator) agentTool(ctx context.Context) (fantasy.AgentTool, error)
39 }
39 }
40 return fantasy.NewParallelAgentTool(
40 return fantasy.NewParallelAgentTool(
41 AgentToolName,
41 AgentToolName,
42− tools.FirstLineDescription(agentToolDescription),
42+ agentToolDescription,
43 func(ctx context.Context, params AgentParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
43 func(ctx context.Context, params AgentParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
44 if params.Prompt == "" {
44 if params.Prompt == "" {
45 return fantasy.NewTextErrorResponse("prompt is required"), nil
45 return fantasy.NewTextErrorResponse("prompt is required"), nil
internal/agent/agentic_fetch_tool.go+2−2
@@ -17,7 +17,7 @@ import (
17)
17)
18
18
19//go:embed templates/agentic_fetch.md
19//go:embed templates/agentic_fetch.md
20−var agenticFetchToolDescription []byte
20+var agenticFetchToolDescription string
21
21
22// agenticFetchValidationResult holds the validated parameters from the tool call context.
22// agenticFetchValidationResult holds the validated parameters from the tool call context.
23type agenticFetchValidationResult struct {
23type agenticFetchValidationResult struct {
@@ -65,7 +65,7 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
65
65
66 return fantasy.NewParallelAgentTool(
66 return fantasy.NewParallelAgentTool(
67 tools.AgenticFetchToolName,
67 tools.AgenticFetchToolName,
68− tools.FirstLineDescription(agenticFetchToolDescription),
68+ agenticFetchToolDescription,
69 func(ctx context.Context, params tools.AgenticFetchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
69 func(ctx context.Context, params tools.AgenticFetchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
70 validationResult, err := validateAgenticFetchParams(ctx, params)
70 validationResult, err := validateAgenticFetchParams(ctx, params)
71 if err != nil {
71 if err != nil {
internal/agent/templates/agent_tool.md−14
@@ -1,15 +1 @@
1Launch a new agent that has access to the following tools: glob, grep, ls, view. When you are searching for a keyword or file and are not confident that you will find the right match on the first try, use the agent tool to perform the search for you.
1Launch a new agent that has access to the following tools: glob, grep, ls, view. When you are searching for a keyword or file and are not confident that you will find the right match on the first try, use the agent tool to perform the search for you.
2−
3−<usage>
4−- If you are searching for a keyword like "config" or "logger", or for questions like "which file does X?", the Agent tool is strongly recommended
5−- If you want to read a specific file path, use the View or GlobTool tool instead of the Agent tool, to find the match more quickly
6−- If you are searching for a specific class definition like "class Foo", use the GlobTool tool instead, to find the match more quickly
7−</usage>
8−
9−<usage_notes>
10−1. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
11−2. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.
12−3. Each agent invocation is stateless. You will not be able to send additional messages to the agent, nor will the agent be able to communicate with you outside of its final report. Therefore, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.
13−4. The agent's outputs should generally be trusted
14−5. IMPORTANT: The agent can not use Bash, Replace, Edit, so can not modify files. If you want to use these tools, use them directly instead of going through the agent.
15−</usage_notes>
internal/agent/templates/agentic_fetch.md−63
@@ -1,64 +1 @@
1Fetch a URL or search the web using an AI sub-agent that can extract, summarize, and answer questions. Slower and costlier than fetch; use fetch for raw content or API responses.
1Fetch a URL or search the web using an AI sub-agent that can extract, summarize, and answer questions. Slower and costlier than fetch; use fetch for raw content or API responses.
2−
3−<when_to_use>
4−Use this tool when you need to:
5−- Search the web for information (omit the url parameter)
6−- Extract specific information from a webpage (provide a url)
7−- Answer questions about web content
8−- Summarize or analyze web pages
9−- Research topics by searching and following links
10−
11−DO NOT use this tool when:
12−- You just need raw content without analysis (use fetch instead - faster and cheaper)
13−- You want direct access to API responses or JSON (use fetch instead)
14−- You don't need the content processed or interpreted (use fetch instead)
15−</when_to_use>
16−
17−<usage>
18−- Provide a prompt describing what information you want to find or extract (required)
19−- Optionally provide a URL to fetch and analyze specific content
20−- If no URL is provided, the agent will search the web to find relevant information
21−- The tool spawns a sub-agent with web_search, web_fetch, and analysis tools
22−- Returns the agent's response about the content
23−</usage>
24−
25−<parameters>
26−- prompt: What information you want to find or extract (required)
27−- url: The URL to fetch content from (optional - if not provided, agent will search the web)
28−</parameters>
29−
30−<usage_notes>
31−- IMPORTANT: If an MCP-provided web fetch tool is available, prefer using that tool instead of this one, as it may have fewer restrictions. All MCP-provided tools start with "mcp_".
32−- When using URL mode: The URL must be a fully-formed valid URL. HTTP URLs will be automatically upgraded to HTTPS.
33−- When searching: Just provide the prompt describing what you want to find - the agent will search and fetch relevant pages.
34−- The sub-agent can perform multiple searches and fetch multiple pages to gather comprehensive information.
35−- This tool is read-only and does not modify any files.
36−- Results will be summarized if the content is very large.
37−- This tool uses AI processing and costs more tokens than the simple fetch tool.
38−</usage_notes>
39−
40−<limitations>
41−- Max response size: 5MB per page
42−- Only supports HTTP and HTTPS protocols
43−- Cannot handle authentication or cookies
44−- Some websites may block automated requests
45−- Uses additional tokens for AI processing
46−- Search results depend on DuckDuckGo availability
47−</limitations>
48−
49−<tips>
50−- Be specific in your prompt about what information you want
51−- For research tasks, omit the URL and let the agent search and follow relevant links
52−- For complex pages, ask the agent to focus on specific sections
53−- The agent has access to web_search, web_fetch, grep, and view tools
54−- If you just need raw content, use the fetch tool instead to save tokens
55−</tips>
56−
57−<examples>
58−Search for information:
59−- prompt: "What are the main new features in the latest Python release?"
60−
61−Fetch and analyze a URL:
62−- url: "https://docs.python.org/3/whatsnew/3.12.html"
63−- prompt: "Summarize the key changes in Python 3.12"
64−</examples>
internal/agent/tools/crush_info.go+2−2
@@ -17,7 +17,7 @@ import (
17const CrushInfoToolName = "crush_info"
17const CrushInfoToolName = "crush_info"
18
18
19//go:embed crush_info.md
19//go:embed crush_info.md
20−var crushInfoDescription []byte
20+var crushInfoDescription string
21
21
22type CrushInfoParams struct{}
22type CrushInfoParams struct{}
23
23
@@ -30,7 +30,7 @@ func NewCrushInfoTool(
30) fantasy.AgentTool {
30) fantasy.AgentTool {
31 return fantasy.NewAgentTool(
31 return fantasy.NewAgentTool(
32 CrushInfoToolName,
32 CrushInfoToolName,
33− string(crushInfoDescription),
33+ crushInfoDescription,
34 func(ctx context.Context, _ CrushInfoParams, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
34 func(ctx context.Context, _ CrushInfoParams, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
35 return fantasy.NewTextResponse(buildCrushInfo(cfg, lspManager, allSkills, activeSkills, skillTracker)), nil
35 return fantasy.NewTextResponse(buildCrushInfo(cfg, lspManager, allSkills, activeSkills, skillTracker)), nil
36 })
36 })
internal/agent/tools/crush_logs.go+2−2
@@ -19,7 +19,7 @@ import (
19const CrushLogsToolName = "crush_logs"
19const CrushLogsToolName = "crush_logs"
20
20
21//go:embed crush_logs.md
21//go:embed crush_logs.md
22−var crushLogsDescription []byte
22+var crushLogsDescription string
23
23
24// Max line size to prevent memory issues with very long log lines (1 MB).
24// Max line size to prevent memory issues with very long log lines (1 MB).
25const maxLogLineSize = 1024 * 1024
25const maxLogLineSize = 1024 * 1024
@@ -58,7 +58,7 @@ type CrushLogsParams struct {
58func NewCrushLogsTool(logFile string) fantasy.AgentTool {
58func NewCrushLogsTool(logFile string) fantasy.AgentTool {
59 return fantasy.NewAgentTool(
59 return fantasy.NewAgentTool(
60 CrushLogsToolName,
60 CrushLogsToolName,
61− string(crushLogsDescription),
61+ crushLogsDescription,
62 func(ctx context.Context, params CrushLogsParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
62 func(ctx context.Context, params CrushLogsParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
63 result := runCrushLogs(logFile, params)
63 result := runCrushLogs(logFile, params)
64 return fantasy.NewTextResponse(result), nil
64 return fantasy.NewTextResponse(result), nil
internal/agent/tools/diagnostics.go+2−2
@@ -22,12 +22,12 @@ type DiagnosticsParams struct {
22const DiagnosticsToolName = "lsp_diagnostics"
22const DiagnosticsToolName = "lsp_diagnostics"
23
23
24//go:embed diagnostics.md
24//go:embed diagnostics.md
25−var diagnosticsDescription []byte
25+var diagnosticsDescription string
26
26
27func NewDiagnosticsTool(lspManager *lsp.Manager) fantasy.AgentTool {
27func NewDiagnosticsTool(lspManager *lsp.Manager) fantasy.AgentTool {
28 return fantasy.NewAgentTool(
28 return fantasy.NewAgentTool(
29 DiagnosticsToolName,
29 DiagnosticsToolName,
30− FirstLineDescription(diagnosticsDescription),
30+ diagnosticsDescription,
31 func(ctx context.Context, params DiagnosticsParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
31 func(ctx context.Context, params DiagnosticsParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
32 if lspManager.Clients().Len() == 0 {
32 if lspManager.Clients().Len() == 0 {
33 return fantasy.NewTextErrorResponse("no LSP clients available"), nil
33 return fantasy.NewTextErrorResponse("no LSP clients available"), nil
internal/agent/tools/diagnostics.md+1−24
@@ -1,24 +1 @@
1−Get LSP errors, warnings, and hints for a file or the whole project.
1+Get LSP errors, warnings, and hints for a file or the whole project.
2−
3−<usage>
4−- Provide file path to get diagnostics for that file
5−- Leave path empty to get diagnostics for entire project
6−- Results displayed in structured format with severity levels
7−</usage>
8−
9−<features>
10−- Displays errors, warnings, and hints
11−- Groups diagnostics by severity
12−- Provides detailed information about each diagnostic
13−</features>
14−
15−<limitations>
16−- Results limited to diagnostics provided by LSP clients
17−- May not cover all possible code issues
18−- Does not provide suggestions for fixing issues
19−</limitations>
20−
21−<tips>
22−- Use with other tools for comprehensive code review
23−- Combine with LSP client for real-time diagnostics
24−</tips>
internal/agent/tools/download.go+2−2
@@ -32,7 +32,7 @@ type DownloadPermissionsParams struct {
32const DownloadToolName = "download"
32const DownloadToolName = "download"
33
33
34//go:embed download.md
34//go:embed download.md
35−var downloadDescription []byte
35+var downloadDescription string
36
36
37func NewDownloadTool(permissions permission.Service, workingDir string, client *http.Client) fantasy.AgentTool {
37func NewDownloadTool(permissions permission.Service, workingDir string, client *http.Client) fantasy.AgentTool {
38 if client == nil {
38 if client == nil {
@@ -48,7 +48,7 @@ func NewDownloadTool(permissions permission.Service, workingDir string, client *
48 }
48 }
49 return fantasy.NewParallelAgentTool(
49 return fantasy.NewParallelAgentTool(
50 DownloadToolName,
50 DownloadToolName,
51− FirstLineDescription(downloadDescription),
51+ downloadDescription,
52 func(ctx context.Context, params DownloadParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
52 func(ctx context.Context, params DownloadParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
53 if params.URL == "" {
53 if params.URL == "" {
54 return fantasy.NewTextErrorResponse("URL parameter is required"), nil
54 return fantasy.NewTextErrorResponse("URL parameter is required"), nil
internal/agent/tools/download.md+1−28
@@ -1,28 +1 @@
1−Download a URL directly to a local file (binary-safe, streaming, max 100MB); overwrites without warning. For reading content into context use fetch.
1+Download a URL directly to a local file (binary-safe, streaming, max 100MB); overwrites without warning. For reading content into context use fetch.
2−
3−<usage>
4−- Provide URL to download from
5−- Specify local file path where content should be saved
6−- Optional timeout for request
7−</usage>
8−
9−<features>
10−- Downloads any file type (binary or text)
11−- Auto-creates parent directories if missing
12−- Handles large files efficiently with streaming
13−- Sets reasonable timeouts to prevent hanging
14−- Validates input parameters before requests
15−</features>
16−
17−<limitations>
18−- Max file size: 100MB
19−- Only supports HTTP and HTTPS protocols
20−- Cannot handle authentication or cookies
21−- Some websites may block automated requests
22−- Will overwrite existing files without warning
23−</limitations>
24−
25−<tips>
26−- Use absolute paths or paths relative to working directory
27−- Set appropriate timeouts for large files or slow connections
28−</tips>
internal/agent/tools/edit.go+2−2
@@ -49,7 +49,7 @@ var (
49)
49)
50
50
51//go:embed edit.md
51//go:embed edit.md
52−var editDescription []byte
52+var editDescription string
53
53
54type editContext struct {
54type editContext struct {
55 ctx context.Context
55 ctx context.Context
@@ -68,7 +68,7 @@ func NewEditTool(
68) fantasy.AgentTool {
68) fantasy.AgentTool {
69 return fantasy.NewAgentTool(
69 return fantasy.NewAgentTool(
70 EditToolName,
70 EditToolName,
71− FirstLineDescription(editDescription),
71+ editDescription,
72 func(ctx context.Context, params EditParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
72 func(ctx context.Context, params EditParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
73 if params.FilePath == "" {
73 if params.FilePath == "" {
74 return fantasy.NewTextErrorResponse("file_path is required"), nil
74 return fantasy.NewTextErrorResponse("file_path is required"), nil
internal/agent/tools/edit.md+1−147
@@ -1,147 +1 @@
1−Edit a file by exact find-and-replace; can also create or delete content. For renames/moves use bash. For large edits use write.
1+Edit a file by exact find-and-replace; can also create or delete content. For renames/moves use bash. For large edits use write.
2−
3−<prerequisites>
4−1. Use View tool to understand file contents and context
5−2. For new files: Use LS tool to verify parent directory exists
6−3. **CRITICAL**: Note exact whitespace, indentation, and formatting from View output
7−</prerequisites>
8−
9−<parameters>
10−1. file_path: Absolute path to file (required)
11−2. old_string: Text to replace (must match exactly including whitespace/indentation)
12−3. new_string: Replacement text
13−4. replace_all: Replace all occurrences (default false)
14−</parameters>
15−
16−<special_cases>
17−
18−- Create file: provide file_path + new_string, leave old_string empty
19−- Delete content: provide file_path + old_string, leave new_string empty
20− </special_cases>
21−
22−<critical_requirements>
23−EXACT MATCHING: The tool is extremely literal. Text must match **EXACTLY**
24−
25−- Every space and tab character
26−- Every blank line
27−- Every newline character
28−- Indentation level (count the spaces/tabs)
29−- Comment spacing (`// comment` vs `//comment`)
30−- Brace positioning (`func() {` vs `func(){`)
31−
32−Common failures:
33−
34−```
35−Expected: " func foo() {" (4 spaces)
36−Provided: " func foo() {" (2 spaces) ❌ FAILS
37−
38−Expected: "}\n\nfunc bar() {" (2 newlines)
39−Provided: "}\nfunc bar() {" (1 newline) ❌ FAILS
40−
41−Expected: "// Comment" (space after //)
42−Provided: "//Comment" (no space) ❌ FAILS
43−```
44−
45−UNIQUENESS (when replace_all=false): old_string MUST uniquely identify target instance
46−
47−- Include 3-5 lines context BEFORE and AFTER change point
48−- Include exact whitespace, indentation, surrounding code
49−- If text appears multiple times, add more context to make it unique
50−
51−SINGLE INSTANCE: Tool changes ONE instance when replace_all=false
52−
53−- For multiple instances: set replace_all=true OR make separate calls with unique context
54−- Plan calls carefully to avoid conflicts
55−
56−VERIFICATION BEFORE USING: Before every edit
57−
58−1. View the file and locate exact target location
59−2. Check how many instances of target text exist
60−3. Copy the EXACT text including all whitespace
61−4. Verify you have enough context for unique identification
62−5. Double-check indentation matches (count spaces/tabs)
63−6. Plan separate calls or use replace_all for multiple changes
64− </critical_requirements>
65−
66−<warnings>
67−Tool fails if:
68−- old_string matches multiple locations and replace_all=false
69−- old_string doesn't match exactly (including whitespace)
70−- Insufficient context causes wrong instance change
71−- Indentation is off by even one space
72−- Missing or extra blank lines
73−- Wrong tabs vs spaces
74−</warnings>
75−
76−<recovery_steps>
77−If you get "old_string not found in file":
78−
79−1. **View the file again** at the specific location
80−2. **Copy more context** - include entire function if needed
81−3. **Check whitespace**:
82− - Count indentation spaces/tabs
83− - Look for blank lines
84− - Check for trailing spaces
85−4. **Verify character-by-character** that your old_string matches
86−5. **Never guess** - always View the file to get exact text
87− </recovery_steps>
88−
89−<best_practices>
90−
91−- Ensure edits result in correct, idiomatic code
92−- Don't leave code in broken state
93−- Use absolute file paths (starting with /)
94−- Use forward slashes (/) for cross-platform compatibility
95−- Multiple edits to same file: send all in single message with multiple tool calls
96−- **When in doubt, include MORE context rather than less**
97−- Match the existing code style exactly (spaces, tabs, blank lines)
98− </best_practices>
99−
100−<whitespace_checklist>
101−Before submitting an edit, verify:
102−
103−- [ ] Viewed the file first
104−- [ ] Counted indentation spaces/tabs
105−- [ ] Included blank lines if they exist
106−- [ ] Matched brace/bracket positioning
107−- [ ] Included 3-5 lines of surrounding context
108−- [ ] Verified text appears exactly once (or using replace_all)
109−- [ ] Copied text character-for-character, not approximated
110− </whitespace_checklist>
111−
112−<examples>
113−✅ Correct: Exact match with context
114−
115−```
116−old_string: "func ProcessData(input string) error {\n if input == \"\" {\n return errors.New(\"empty input\")\n }\n return nil\n}"
117−
118−new_string: "func ProcessData(input string) error {\n if input == \"\" {\n return errors.New(\"empty input\")\n }\n // New validation\n if len(input) > 1000 {\n return errors.New(\"input too long\")\n }\n return nil\n}"
119−```
120−
121−❌ Incorrect: Not enough context
122−
123−```
124−old_string: "return nil" // Appears many times!
125−```
126−
127−❌ Incorrect: Wrong indentation
128−
129−```
130−old_string: " if input == \"\" {" // 2 spaces
131−// But file actually has: " if input == \"\" {" // 4 spaces
132−```
133−
134−✅ Correct: Including context to make unique
135−
136−```
137−old_string: "func ProcessData(input string) error {\n if input == \"\" {\n return errors.New(\"empty input\")\n }\n return nil"
138−```
139−
140−</examples>
141−
142−<windows_notes>
143−
144−- Forward slashes work throughout (C:/path/file)
145−- File permissions handled automatically
146−- Line endings converted automatically (\n ↔ \r\n)
147− </windows_notes>
internal/agent/tools/fetch.go+2−2
@@ -22,7 +22,7 @@ const (
22)
22)
23
23
24//go:embed fetch.md
24//go:embed fetch.md
25−var fetchDescription []byte
25+var fetchDescription string
26
26
27func NewFetchTool(permissions permission.Service, workingDir string, client *http.Client) fantasy.AgentTool {
27func NewFetchTool(permissions permission.Service, workingDir string, client *http.Client) fantasy.AgentTool {
28 if client == nil {
28 if client == nil {
@@ -39,7 +39,7 @@ func NewFetchTool(permissions permission.Service, workingDir string, client *htt
39
39
40 return fantasy.NewParallelAgentTool(
40 return fantasy.NewParallelAgentTool(
41 FetchToolName,
41 FetchToolName,
42− FirstLineDescription(fetchDescription),
42+ fetchDescription,
43 func(ctx context.Context, params FetchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
43 func(ctx context.Context, params FetchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
44 if params.URL == "" {
44 if params.URL == "" {
45 return fantasy.NewTextErrorResponse("URL parameter is required"), nil
45 return fantasy.NewTextErrorResponse("URL parameter is required"), nil
internal/agent/tools/fetch.md+1−45
@@ -1,45 +1 @@
1−Fetch raw content from a URL as text, markdown, or html (max 100KB); no AI processing. For analysis or extraction use agentic_fetch.
1+Fetch raw content from a URL as text, markdown, or html (max 100KB); no AI processing. For analysis or extraction use agentic_fetch.
2−
3−<when_to_use>
4−Use this tool when you need:
5−- Raw, unprocessed content from a URL
6−- Direct access to API responses or JSON data
7−- HTML/text/markdown content without interpretation
8−- Simple, fast content retrieval without analysis
9−- To save tokens by avoiding AI processing
10−
11−DO NOT use this tool when you need to:
12−- Extract specific information from a webpage (use agentic_fetch instead)
13−- Answer questions about web content (use agentic_fetch instead)
14−- Analyze or summarize web pages (use agentic_fetch instead)
15−</when_to_use>
16−
17−<usage>
18−- Provide URL to fetch content from
19−- Specify desired output format (text, markdown, or html)
20−- Optional timeout for request
21−</usage>
22−
23−<features>
24−- Supports three output formats: text, markdown, html
25−- Auto-handles HTTP redirects
26−- Fast and lightweight - no AI processing
27−- Sets reasonable timeouts to prevent hanging
28−- Validates input parameters before requests
29−</features>
30−
31−<limitations>
32−- Max response size: 100KB
33−- Only supports HTTP and HTTPS protocols
34−- Cannot handle authentication or cookies
35−- Some websites may block automated requests
36−- Returns raw content only - no analysis or extraction
37−</limitations>
38−
39−<tips>
40−- Use text format for plain text content or simple API responses
41−- Use markdown format for content that should be rendered with formatting
42−- Use html format when you need raw HTML structure
43−- Set appropriate timeouts for potentially slow websites
44−- If the user asks to analyze or extract from a page, use agentic_fetch instead
45−</tips>
internal/agent/tools/glob.go+2−2
@@ -20,7 +20,7 @@ import (
20const GlobToolName = "glob"
20const GlobToolName = "glob"
21
21
22//go:embed glob.md
22//go:embed glob.md
23−var globDescription []byte
23+var globDescription string
24
24
25type GlobParams struct {
25type GlobParams struct {
26 Pattern string `json:"pattern" description:"The glob pattern to match files against"`
26 Pattern string `json:"pattern" description:"The glob pattern to match files against"`
@@ -35,7 +35,7 @@ type GlobResponseMetadata struct {
35func NewGlobTool(workingDir string) fantasy.AgentTool {
35func NewGlobTool(workingDir string) fantasy.AgentTool {
36 return fantasy.NewAgentTool(
36 return fantasy.NewAgentTool(
37 GlobToolName,
37 GlobToolName,
38− FirstLineDescription(globDescription),
38+ globDescription,
39 func(ctx context.Context, params GlobParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
39 func(ctx context.Context, params GlobParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
40 if params.Pattern == "" {
40 if params.Pattern == "" {
41 return fantasy.NewTextErrorResponse("pattern is required"), nil
41 return fantasy.NewTextErrorResponse("pattern is required"), nil
internal/agent/tools/glob.md+1−40
@@ -1,40 +1 @@
1−Find files by name/pattern (glob syntax), sorted by modification time; max 100 results; skips hidden files. Use grep to search file contents.
1+Find files by name/pattern (glob syntax), sorted by modification time; max 100 results; skips hidden files. Use grep to search file contents.
2−
3−<usage>
4−- Provide glob pattern to match against file paths
5−- Optional starting directory (defaults to current working directory)
6−- Results sorted with most recently modified files first
7−</usage>
8−
9−<pattern_syntax>
10−- '\*' matches any sequence of non-separator characters
11−- '\*\*' matches any sequence including separators
12−- '?' matches any single non-separator character
13−- '[...]' matches any character in brackets
14−- '[!...]' matches any character not in brackets
15−</pattern_syntax>
16−
17−<examples>
18−- '*.js' - JavaScript files in current directory
19−- '**/*.js' - JavaScript files in any subdirectory
20−- 'src/**/*.{ts,tsx}' - TypeScript files in src directory
21−- '*.{html,css,js}' - HTML, CSS, and JS files
22−</examples>
23−
24−<limitations>
25−- Results limited to 100 files (newest first)
26−- Does not search file contents (use Grep for that)
27−- Hidden files (starting with '.') skipped
28−</limitations>
29−
30−<cross_platform>
31−- Path separators handled automatically (/ and \ work)
32−- Uses ripgrep (rg) if available, otherwise Go implementation
33−- Patterns should use forward slashes (/) for compatibility
34−</cross_platform>
35−
36−<tips>
37−- Combine with Grep: find files with Glob, search contents with Grep
38−- For iterative exploration requiring multiple searches, consider Agent tool
39−- Check if results truncated and refine pattern if needed
40−</tips>
internal/agent/tools/grep.go+2−2
@@ -90,7 +90,7 @@ const (
90)
90)
91
91
92//go:embed grep.md
92//go:embed grep.md
93−var grepDescription []byte
93+var grepDescription string
94
94
95// escapeRegexPattern escapes special regex characters so they're treated as literal characters
95// escapeRegexPattern escapes special regex characters so they're treated as literal characters
96func escapeRegexPattern(pattern string) string {
96func escapeRegexPattern(pattern string) string {
@@ -107,7 +107,7 @@ func escapeRegexPattern(pattern string) string {
107func NewGrepTool(workingDir string, config config.ToolGrep) fantasy.AgentTool {
107func NewGrepTool(workingDir string, config config.ToolGrep) fantasy.AgentTool {
108 return fantasy.NewAgentTool(
108 return fantasy.NewAgentTool(
109 GrepToolName,
109 GrepToolName,
110− FirstLineDescription(grepDescription),
110+ grepDescription,
111 func(ctx context.Context, params GrepParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
111 func(ctx context.Context, params GrepParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
112 if params.Pattern == "" {
112 if params.Pattern == "" {
113 return fantasy.NewTextErrorResponse("pattern is required"), nil
113 return fantasy.NewTextErrorResponse("pattern is required"), nil
internal/agent/tools/grep.md+1−49
@@ -1,49 +1 @@
1−Search file contents by regex or literal text; returns matching file paths sorted by modification time (max 100); respects .gitignore. Use glob to filter by filename, not contents.
1+Search file contents by regex or literal text; returns matching file paths sorted by modification time (max 100); respects .gitignore. Use glob to filter by filename, not contents.
2−
3−<usage>
4−- Provide regex pattern to search within file contents
5−- Set literal_text=true for exact text with special characters (recommended for non-regex users)
6−- Optional starting directory (defaults to current working directory)
7−- Optional include pattern to filter which files to search
8−- Results sorted with most recently modified files first
9−</usage>
10−
11−<regex_syntax>
12−When literal_text=false (supports standard regex):
13−
14−- 'function' searches for literal text "function"
15−- 'log\..\*Error' finds text starting with "log." and ending with "Error"
16−- 'import\s+.\*\s+from' finds import statements in JavaScript/TypeScript
17−</regex_syntax>
18−
19−<include_patterns>
20−- '\*.js' - Only search JavaScript files
21−- '\*.{ts,tsx}' - Only search TypeScript files
22−- '\*.go' - Only search Go files
23−</include_patterns>
24−
25−<limitations>
26−- Results limited to 100 files (newest first)
27−- Performance depends on number of files searched
28−- Very large binary files may be skipped
29−- Hidden files (starting with '.') skipped
30−</limitations>
31−
32−<ignore_support>
33−- Respects .gitignore patterns to skip ignored files/directories
34−- Respects .crushignore patterns for additional ignore rules
35−- Both ignore files auto-detected in search root directory
36−</ignore_support>
37−
38−<cross_platform>
39−- Uses ripgrep (rg) if available for better performance
40−- Falls back to Go implementation if ripgrep unavailable
41−- File paths normalized automatically for compatibility
42−</cross_platform>
43−
44−<tips>
45−- For faster searches: use Glob to find relevant files first, then Grep
46−- For iterative exploration requiring multiple searches, consider Agent tool
47−- Check if results truncated and refine search pattern if needed
48−- Use literal_text=true for exact text with special characters (dots, parentheses, etc.)
49−</tips>
internal/agent/tools/job_kill.go+2−2
@@ -14,7 +14,7 @@ const (
14)
14)
15
15
16//go:embed job_kill.md
16//go:embed job_kill.md
17−var jobKillDescription []byte
17+var jobKillDescription string
18
18
19type JobKillParams struct {
19type JobKillParams struct {
20 ShellID string `json:"shell_id" description:"The ID of the background shell to terminate"`
20 ShellID string `json:"shell_id" description:"The ID of the background shell to terminate"`
@@ -29,7 +29,7 @@ type JobKillResponseMetadata struct {
29func NewJobKillTool() fantasy.AgentTool {
29func NewJobKillTool() fantasy.AgentTool {
30 return fantasy.NewAgentTool(
30 return fantasy.NewAgentTool(
31 JobKillToolName,
31 JobKillToolName,
32− string(jobKillDescription),
32+ jobKillDescription,
33 func(ctx context.Context, params JobKillParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
33 func(ctx context.Context, params JobKillParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
34 if params.ShellID == "" {
34 if params.ShellID == "" {
35 return fantasy.NewTextErrorResponse("missing shell_id"), nil
35 return fantasy.NewTextErrorResponse("missing shell_id"), nil
internal/agent/tools/job_output.go+2−2
@@ -15,7 +15,7 @@ const (
15)
15)
16
16
17//go:embed job_output.md
17//go:embed job_output.md
18−var jobOutputDescription []byte
18+var jobOutputDescription string
19
19
20type JobOutputParams struct {
20type JobOutputParams struct {
21 ShellID string `json:"shell_id" description:"The ID of the background shell to retrieve output from"`
21 ShellID string `json:"shell_id" description:"The ID of the background shell to retrieve output from"`
@@ -33,7 +33,7 @@ type JobOutputResponseMetadata struct {
33func NewJobOutputTool() fantasy.AgentTool {
33func NewJobOutputTool() fantasy.AgentTool {
34 return fantasy.NewAgentTool(
34 return fantasy.NewAgentTool(
35 JobOutputToolName,
35 JobOutputToolName,
36− FirstLineDescription(jobOutputDescription),
36+ jobOutputDescription,
37 func(ctx context.Context, params JobOutputParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
37 func(ctx context.Context, params JobOutputParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
38 if params.ShellID == "" {
38 if params.ShellID == "" {
39 return fantasy.NewTextErrorResponse("missing shell_id"), nil
39 return fantasy.NewTextErrorResponse("missing shell_id"), nil
internal/agent/tools/job_output.md+1−22
@@ -1,22 +1 @@
1−Get stdout/stderr from a background shell by ID; set wait=true to block until completion.
1+Get stdout/stderr from a background shell by ID; set wait=true to block until completion.
2−
3−<usage>
4−- Provide the shell ID returned from a background bash execution
5−- Returns the current stdout and stderr output
6−- Indicates whether the shell has completed execution
7−- Set wait=true to block until the shell completes or the request context is done
8−</usage>
9−
10−<features>
11−- View output from running background processes
12−- Check if background process has completed
13−- Get cumulative output from process start
14−- Optionally wait for process completion (returns early on context cancel)
15−</features>
16−
17−<tips>
18−- Use this to monitor long-running processes
19−- Check the 'done' status to see if process completed
20−- Can be called multiple times to view incremental output
21−- Use wait=true when you need the final output and exit status (or current output if the request cancels)
22−</tips>
internal/agent/tools/list_mcp_resources.go+2−2
@@ -26,12 +26,12 @@ type ListMCPResourcesPermissionsParams struct {
26const ListMCPResourcesToolName = "list_mcp_resources"
26const ListMCPResourcesToolName = "list_mcp_resources"
27
27
28//go:embed list_mcp_resources.md
28//go:embed list_mcp_resources.md
29−var listMCPResourcesDescription []byte
29+var listMCPResourcesDescription string
30
30
31func NewListMCPResourcesTool(cfg *config.ConfigStore, permissions permission.Service) fantasy.AgentTool {
31func NewListMCPResourcesTool(cfg *config.ConfigStore, permissions permission.Service) fantasy.AgentTool {
32 return fantasy.NewParallelAgentTool(
32 return fantasy.NewParallelAgentTool(
33 ListMCPResourcesToolName,
33 ListMCPResourcesToolName,
34− FirstLineDescription(listMCPResourcesDescription),
34+ listMCPResourcesDescription,
35 func(ctx context.Context, params ListMCPResourcesParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
35 func(ctx context.Context, params ListMCPResourcesParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
36 params.MCPName = strings.TrimSpace(params.MCPName)
36 params.MCPName = strings.TrimSpace(params.MCPName)
37 if params.MCPName == "" {
37 if params.MCPName == "" {
internal/agent/tools/list_mcp_resources.md+1−18
@@ -1,18 +1 @@
1−List available resource URIs from an MCP server by name; use before read_mcp_resource.
1+List available resource URIs from an MCP server by name; use before read_mcp_resource.
2−
3−<when_to_use>
4−Use this tool to discover which resources are available before reading them.
5−</when_to_use>
6−
7−<usage>
8−- Provide MCP server name
9−- Returns resource titles and URIs
10−</usage>
11−
12−<parameters>
13−- mcp_name: The MCP server name
14−</parameters>
15−
16−<notes>
17−- Results include resource titles, URIs, and metadata when available
18−</notes>
internal/agent/tools/ls.go+2−2
@@ -53,12 +53,12 @@ const (
53)
53)
54
54
55//go:embed ls.md
55//go:embed ls.md
56−var lsDescription []byte
56+var lsDescription string
57
57
58func NewLsTool(permissions permission.Service, workingDir string, lsConfig config.ToolLs) fantasy.AgentTool {
58func NewLsTool(permissions permission.Service, workingDir string, lsConfig config.ToolLs) fantasy.AgentTool {
59 return fantasy.NewAgentTool(
59 return fantasy.NewAgentTool(
60 LSToolName,
60 LSToolName,
61− FirstLineDescription(lsDescription),
61+ lsDescription,
62 func(ctx context.Context, params LSParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
62 func(ctx context.Context, params LSParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
63 searchPath, err := fsext.Expand(cmp.Or(params.Path, workingDir))
63 searchPath, err := fsext.Expand(cmp.Or(params.Path, workingDir))
64 if err != nil {
64 if err != nil {
internal/agent/tools/ls.md+1−34
@@ -1,34 +1 @@
1−List files and directories as a tree; skips hidden files and common system dirs; max 1000 files. Use glob to find files by pattern, grep to search contents.
1+List files and directories as a tree; skips hidden files and common system dirs; max 1000 files. Use glob to find files by pattern, grep to search contents.
2−
3−<usage>
4−- Provide path to list (defaults to current working directory)
5−- Optional glob patterns to ignore
6−- Results displayed in tree structure
7−</usage>
8−
9−<features>
10−- Hierarchical view of files and directories
11−- Auto-skips hidden files/directories (starting with '.')
12−- Skips common system directories like __pycache__
13−- Can filter files matching specific patterns
14−</features>
15−
16−<limitations>
17−- Results limited to 1000 files
18−- Large directories truncated
19−- No file sizes or permissions shown
20−- Cannot recursively list all directories in large projects
21−</limitations>
22−
23−<cross_platform>
24−- Hidden file detection uses Unix convention (files starting with '.')
25−- Windows hidden files (with hidden attribute) not auto-skipped
26−- Common Windows directories (System32, Program Files) not in default ignore
27−- Path separators handled automatically (/ and \ work)
28−</cross_platform>
29−
30−<tips>
31−- Use Glob for finding files by name patterns instead of browsing
32−- Use Grep for searching file contents
33−- Combine with other tools for effective exploration
34−</tips>
internal/agent/tools/lsp_restart.go+2−2
@@ -16,7 +16,7 @@ import (
16const LSPRestartToolName = "lsp_restart"
16const LSPRestartToolName = "lsp_restart"
17
17
18//go:embed lsp_restart.md
18//go:embed lsp_restart.md
19−var lspRestartDescription []byte
19+var lspRestartDescription string
20
20
21type LSPRestartParams struct {
21type LSPRestartParams struct {
22 // Name is the optional name of a specific LSP client to restart.
22 // Name is the optional name of a specific LSP client to restart.
@@ -27,7 +27,7 @@ type LSPRestartParams struct {
27func NewLSPRestartTool(lspManager *lsp.Manager) fantasy.AgentTool {
27func NewLSPRestartTool(lspManager *lsp.Manager) fantasy.AgentTool {
28 return fantasy.NewAgentTool(
28 return fantasy.NewAgentTool(
29 LSPRestartToolName,
29 LSPRestartToolName,
30− FirstLineDescription(lspRestartDescription),
30+ lspRestartDescription,
31 func(ctx context.Context, params LSPRestartParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
31 func(ctx context.Context, params LSPRestartParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
32 if lspManager.Clients().Len() == 0 {
32 if lspManager.Clients().Len() == 0 {
33 return fantasy.NewTextErrorResponse("no LSP clients available to restart"), nil
33 return fantasy.NewTextErrorResponse("no LSP clients available to restart"), nil
internal/agent/tools/lsp_restart.md+1−25
@@ -1,25 +1 @@
1−Restart one or all LSP clients by name; use when diagnostics are stale or the LSP is unresponsive.
1+Restart one or all LSP clients by name; use when diagnostics are stale or the LSP is unresponsive.
2−
3−<usage>
4−- Restart all running LSP clients or a specific LSP client by name
5−- Useful when LSP servers become unresponsive or need to be reloaded
6−- Parameters:
7− - name (optional): Specific LSP client name to restart. If not provided, all clients will be restarted.
8−</usage>
9−
10−<features>
11−- Gracefully shuts down all LSP clients
12−- Restarts them with their original configuration
13−- Reports success/failure for each client
14−</features>
15−
16−<limitations>
17−- Only restarts clients that were successfully started
18−- Does not modify LSP configurations
19−- Requires LSP clients to be already running
20−</limitations>
21−
22−<tips>
23−- Use when LSP diagnostics are stale or unresponsive
24−- Call this tool if you notice LSP features not working properly
25−</tips>
internal/agent/tools/multiedit.go+2−2
@@ -55,7 +55,7 @@ type MultiEditResponseMetadata struct {
55const MultiEditToolName = "multiedit"
55const MultiEditToolName = "multiedit"
56
56
57//go:embed multiedit.md
57//go:embed multiedit.md
58−var multieditDescription []byte
58+var multieditDescription string
59
59
60func NewMultiEditTool(
60func NewMultiEditTool(
61 lspManager *lsp.Manager,
61 lspManager *lsp.Manager,
@@ -66,7 +66,7 @@ func NewMultiEditTool(
66) fantasy.AgentTool {
66) fantasy.AgentTool {
67 return fantasy.NewAgentTool(
67 return fantasy.NewAgentTool(
68 MultiEditToolName,
68 MultiEditToolName,
69− FirstLineDescription(multieditDescription),
69+ multieditDescription,
70 func(ctx context.Context, params MultiEditParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
70 func(ctx context.Context, params MultiEditParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
71 if params.FilePath == "" {
71 if params.FilePath == "" {
72 return fantasy.NewTextErrorResponse("file_path is required"), nil
72 return fantasy.NewTextErrorResponse("file_path is required"), nil
internal/agent/tools/multiedit.md+1−125
@@ -1,125 +1 @@
1−Apply multiple find-and-replace edits to a single file in one operation; edits run sequentially. Prefer over edit for multiple changes to the same file. Same exact-match rules as edit apply.
1+Apply multiple find-and-replace edits to a single file in one operation; edits run sequentially. Prefer over edit for multiple changes to the same file. Same exact-match rules as edit apply.
2−
3−<prerequisites>
4−1. Use View tool to understand file contents and context
5−2. Verify directory path is correct
6−3. CRITICAL: Note exact whitespace, indentation, and formatting from View output
7−</prerequisites>
8−
9−<parameters>
10−1. file_path: Absolute path to file (required)
11−2. edits: Array of edit operations, each containing:
12− - old_string: Text to replace (must match exactly including whitespace/indentation)
13− - new_string: Replacement text
14− - replace_all: Replace all occurrences (optional, defaults to false)
15−</parameters>
16−
17−<operation>
18−- Edits applied sequentially in provided order.
19−- Each edit operates on result of previous edit.
20−- PARTIAL SUCCESS: If some edits fail, successful edits are still applied. Failed edits are returned in the response.
21−- File is modified if at least one edit succeeds.
22−- Ideal for several changes to different parts of same file.
23−</operation>
24−
25−<inherited_rules>
26−All instructions from the Edit tool documentation apply verbatim to every edit item:
27−- Critical requirements for exact matching and uniqueness
28−- Warnings and common failures (tabs vs spaces, blank lines, brace placement, etc.)
29−- Verification steps before using, recovery steps, best practices, and whitespace checklist
30−Use the same level of precision as Edit. Multiedit often fails due to formatting mismatches—double-check whitespace for every edit.
31−</inherited_rules>
32−
33−<critical_requirements>
34−1. Apply Edit tool rules to EACH edit (see edit.md).
35−2. Edits are applied in order; successful edits are kept even if later edits fail.
36−3. Plan sequence carefully: earlier edits change the file content that later edits must match.
37−4. Ensure each old_string is unique at its application time (after prior edits).
38−5. Check the response for failed edits and retry them if needed.
39−</critical_requirements>
40−
41−<verification_before_using>
42−1. View the file and copy exact text (including whitespace) for each target.
43−2. Check how many instances each old_string has BEFORE the sequence starts.
44−3. Dry-run mentally: after applying edit #N, will edit #N+1 still match? Adjust old_string/new_string accordingly.
45−4. Prefer fewer, larger context blocks over many tiny fragments that are easy to misalign.
46−5. If edits are independent, consider separate multiedit batches per logical region.
47−</verification_before_using>
48−
49−<warnings>
50−- Operation continues even if some edits fail; check response for failed edits.
51−- Earlier edits can invalidate later matches (added/removed spaces, lines, or reordered text).
52−- Mixed tabs/spaces, trailing spaces, or missing blank lines commonly cause failures.
53−- replace_all may affect unintended regions—use carefully or provide more context.
54−</warnings>
55−
56−<recovery_steps>
57−If some edits fail:
58−1. Check the response metadata for the list of failed edits with their error messages.
59−2. View the file again to see the current state after successful edits.
60−3. Adjust the failed edits based on the new file content.
61−4. Retry the failed edits with corrected old_string values.
62−5. Consider breaking complex batches into smaller, independent operations.
63−</recovery_steps>
64−
65−<best_practices>
66−- Ensure all edits result in correct, idiomatic code; don't leave code broken.
67−- Use absolute file paths (starting with /).
68−- Use replace_all only when you're certain; otherwise provide unique context.
69−- Match existing style exactly (spaces, tabs, blank lines).
70−- Review failed edits in the response and retry with corrections.
71−</best_practices>
72−
73−<whitespace_checklist>
74−For EACH edit, verify:
75−- [ ] Viewed the file first
76−- [ ] Counted indentation spaces/tabs
77−- [ ] Included blank lines if present
78−- [ ] Matched brace/bracket positioning
79−- [ ] Included 3–5 lines of surrounding context
80−- [ ] Verified text appears exactly once (or using replace_all deliberately)
81−- [ ] Copied text character-for-character, not approximated
82−</whitespace_checklist>
83−
84−<examples>
85−✅ Correct: Sequential edits where the second match accounts for the first change
86−
87−```
88−edits: [
89− {
90− old_string: "func A() {\n doOld()\n}",
91− new_string: "func A() {\n doNew()\n}",
92− },
93− {
94− // Uses context that still exists AFTER the first replacement
95− old_string: "func B() {\n callA()\n}",
96− new_string: "func B() {\n callA()\n logChange()\n}",
97− },
98−]
99−```
100−
101−❌ Incorrect: Second old_string no longer matches due to whitespace change introduced by the first edit
102−
103−```
104−edits: [
105− {
106− old_string: "func A() {\n doOld()\n}",
107− new_string: "func A() {\n\n doNew()\n}", // Added extra blank line
108− },
109− {
110− old_string: "func A() {\n doNew()\n}", // Missing the new blank line, will FAIL
111− new_string: "func A() {\n doNew()\n logChange()\n}",
112− },
113−]
114−```
115−
116−✅ Correct: Handling partial success
117−
118−```
119−// If edit 2 fails, edit 1 is still applied
120−// Response will indicate:
121−// - edits_applied: 1
122−// - edits_failed: [{index: 2, error: "...", edit: {...}}]
123−// You can then retry edit 2 with corrected context
124−```
125−</examples>
internal/agent/tools/read_mcp_resource.go+2−2
@@ -28,12 +28,12 @@ type ReadMCPResourcePermissionsParams struct {
28const ReadMCPResourceToolName = "read_mcp_resource"
28const ReadMCPResourceToolName = "read_mcp_resource"
29
29
30//go:embed read_mcp_resource.md
30//go:embed read_mcp_resource.md
31−var readMCPResourceDescription []byte
31+var readMCPResourceDescription string
32
32
33func NewReadMCPResourceTool(cfg *config.ConfigStore, permissions permission.Service) fantasy.AgentTool {
33func NewReadMCPResourceTool(cfg *config.ConfigStore, permissions permission.Service) fantasy.AgentTool {
34 return fantasy.NewParallelAgentTool(
34 return fantasy.NewParallelAgentTool(
35 ReadMCPResourceToolName,
35 ReadMCPResourceToolName,
36− FirstLineDescription(readMCPResourceDescription),
36+ readMCPResourceDescription,
37 func(ctx context.Context, params ReadMCPResourceParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
37 func(ctx context.Context, params ReadMCPResourceParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
38 params.MCPName = strings.TrimSpace(params.MCPName)
38 params.MCPName = strings.TrimSpace(params.MCPName)
39 params.URI = strings.TrimSpace(params.URI)
39 params.URI = strings.TrimSpace(params.URI)
internal/agent/tools/read_mcp_resource.md+1−20
@@ -1,20 +1 @@
1−Read a resource by URI from an MCP server; returns text content.
1+Read a resource by URI from an MCP server; returns text content.
2−
3−<when_to_use>
4−Use this tool to fetch a specific resource URI exposed by an MCP server.
5−</when_to_use>
6−
7−<usage>
8−- Provide MCP server name and resource URI
9−- Returns resource text content
10−</usage>
11−
12−<parameters>
13−- mcp_name: The MCP server name
14−- uri: The resource URI to read
15−</parameters>
16−
17−<notes>
18−- Returns text content by concatenating resource parts
19−- Binary resources are returned as UTF-8 text when possible
20−</notes>
internal/agent/tools/references.go+2−2
@@ -31,12 +31,12 @@ type referencesTool struct {
31const ReferencesToolName = "lsp_references"
31const ReferencesToolName = "lsp_references"
32
32
33//go:embed references.md
33//go:embed references.md
34−var referencesDescription []byte
34+var referencesDescription string
35
35
36func NewReferencesTool(lspManager *lsp.Manager) fantasy.AgentTool {
36func NewReferencesTool(lspManager *lsp.Manager) fantasy.AgentTool {
37 return fantasy.NewAgentTool(
37 return fantasy.NewAgentTool(
38 ReferencesToolName,
38 ReferencesToolName,
39− FirstLineDescription(referencesDescription),
39+ referencesDescription,
40 func(ctx context.Context, params ReferencesParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
40 func(ctx context.Context, params ReferencesParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
41 if params.Symbol == "" {
41 if params.Symbol == "" {
42 return fantasy.NewTextErrorResponse("symbol is required"), nil
42 return fantasy.NewTextErrorResponse("symbol is required"), nil
internal/agent/tools/references.md+1−26
@@ -1,26 +1 @@
1−Find all references to a symbol by name via LSP; more accurate than grep for code symbols.
1+Find all references to a symbol by name via LSP; more accurate than grep for code symbols.
2−
3−<usage>
4−- Provide symbol name (e.g., "MyFunction", "myVariable", "MyType").
5−- Optional path to narrow search to a directory or file (defaults to current directory).
6−- Tool automatically locates the symbol and returns all references.
7−</usage>
8−
9−<features>
10−- Semantic-aware reference search (more accurate than grep/glob).
11−- Returns references grouped by file with line and column numbers.
12−- Supports multiple programming languages via LSP.
13−- Finds only real references (not comments or unrelated strings).
14−</features>
15−
16−<limitations>
17−- May not find references in files not opened or indexed by the LSP server.
18−- Results depend on the capabilities of the active LSP providers.
19−</limitations>
20−
21−<tips>
22−- Use this first when searching for where a symbol is used.
23−- Do not use grep/glob for symbol searches.
24−- Narrow scope with the path parameter for faster, more relevant results.
25−- Use qualified names (e.g., pkg.Func, Class.method) for higher precision.
26−</tips>
internal/agent/tools/sourcegraph.go+2−2
@@ -29,7 +29,7 @@ type SourcegraphResponseMetadata struct {
29const SourcegraphToolName = "sourcegraph"
29const SourcegraphToolName = "sourcegraph"
30
30
31//go:embed sourcegraph.md
31//go:embed sourcegraph.md
32−var sourcegraphDescription []byte
32+var sourcegraphDescription string
33
33
34func NewSourcegraphTool(client *http.Client) fantasy.AgentTool {
34func NewSourcegraphTool(client *http.Client) fantasy.AgentTool {
35 if client == nil {
35 if client == nil {
@@ -45,7 +45,7 @@ func NewSourcegraphTool(client *http.Client) fantasy.AgentTool {
45 }
45 }
46 return fantasy.NewParallelAgentTool(
46 return fantasy.NewParallelAgentTool(
47 SourcegraphToolName,
47 SourcegraphToolName,
48− FirstLineDescription(sourcegraphDescription),
48+ sourcegraphDescription,
49 func(ctx context.Context, params SourcegraphParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
49 func(ctx context.Context, params SourcegraphParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
50 if params.Query == "" {
50 if params.Query == "" {
51 return fantasy.NewTextErrorResponse("Query parameter is required"), nil
51 return fantasy.NewTextErrorResponse("Query parameter is required"), nil
internal/agent/tools/sourcegraph.md+1−55
@@ -1,55 +1 @@
1−Search code across public GitHub repositories via Sourcegraph; supports regex, language/repo/file filters, and symbol search (max 20 results). Only searches public repos.
1+Search code across public GitHub repositories via Sourcegraph; supports regex, language/repo/file filters, and symbol search (max 20 results). Only searches public repos.
2−
3−<usage>
4−- Provide search query using Sourcegraph syntax
5−- Optional result count (default: 10, max: 20)
6−- Optional timeout for request
7−</usage>
8−
9−<basic_syntax>
10−- "fmt.Println" - exact matches
11−- "file:.go fmt.Println" - limit to Go files
12−- "repo:^github\.com/golang/go$ fmt.Println" - specific repos
13−- "lang:go fmt.Println" - limit to Go code
14−- "fmt.Println AND log.Fatal" - combined terms
15−- "fmt\.(Print|Printf|Println)" - regex patterns
16−- "\"exact phrase\"" - exact phrase matching
17−- "-file:test" or "-repo:forks" - exclude matches
18−</basic_syntax>
19−
20−<key_filters>
21−Repository: repo:name, repo:^exact$, repo:org/repo@branch, -repo:exclude, fork:yes, archived:yes, visibility:public
22−File: file:\.js$, file:internal/, -file:test, file:has.content(text)
23−Content: content:"exact", -content:"unwanted", case:yes
24−Type: type:symbol, type:file, type:path, type:diff, type:commit
25−Time: after:"1 month ago", before:"2023-01-01", author:name, message:"fix"
26−Result: select:repo, select:file, select:content, count:100, timeout:30s
27−</key_filters>
28−
29−<examples>
30−- "file:.go context.WithTimeout" - Go code using context.WithTimeout
31−- "lang:typescript useState type:symbol" - TypeScript React useState hooks
32−- "repo:^github\.com/kubernetes/kubernetes$ pod list type:file" - Kubernetes pod files
33−- "file:Dockerfile (alpine OR ubuntu) -content:alpine:latest" - Dockerfiles with base images
34−</examples>
35−
36−<boolean_operators>
37−- "term1 AND term2" - both terms
38−- "term1 OR term2" - either term
39−- "term1 NOT term2" - term1 but not term2
40−- "term1 and (term2 or term3)" - grouping with parentheses
41−</boolean_operators>
42−
43−<limitations>
44−- Only searches public repositories
45−- Rate limits may apply
46−- Complex queries take longer
47−- Max 20 results per query
48−</limitations>
49−
50−<tips>
51−- Use specific file extensions to narrow results
52−- Add repo: filters for targeted searches
53−- Use type:symbol for function/method definitions
54−- Use type:file to find relevant files
55−</tips>
internal/agent/tools/todos.go+2−2
@@ -10,7 +10,7 @@ import (
10)
10)
11
11
12//go:embed todos.md
12//go:embed todos.md
13−var todosDescription []byte
13+var todosDescription string
14
14
15const TodosToolName = "todos"
15const TodosToolName = "todos"
16
16
@@ -36,7 +36,7 @@ type TodosResponseMetadata struct {
36func NewTodosTool(sessions session.Service) fantasy.AgentTool {
36func NewTodosTool(sessions session.Service) fantasy.AgentTool {
37 return fantasy.NewAgentTool(
37 return fantasy.NewAgentTool(
38 TodosToolName,
38 TodosToolName,
39− FirstLineDescription(todosDescription),
39+ todosDescription,
40 func(ctx context.Context, params TodosParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
40 func(ctx context.Context, params TodosParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
41 sessionID := GetSessionFromContext(ctx)
41 sessionID := GetSessionFromContext(ctx)
42 if sessionID == "" {
42 if sessionID == "" {
internal/agent/tools/todos.md+1−90
@@ -1,90 +1 @@
1−Manage a structured task list for multi-step work; each task has pending/in_progress/completed state. Keep exactly one task in_progress at a time. Skip for simple or single-step tasks.
1+Manage a structured task list for multi-step work; each task has pending/in_progress/completed state. Keep exactly one task in_progress at a time. Skip for simple or single-step tasks.
2−
3−<when_to_use>
4−Use this tool proactively in these scenarios:
5−
6−- Complex multi-step tasks requiring 3+ distinct steps or actions
7−- Non-trivial tasks requiring careful planning or multiple operations
8−- User explicitly requests todo list management
9−- User provides multiple tasks (numbered or comma-separated list)
10−- After receiving new instructions to capture requirements
11−- When starting work on a task (mark as in_progress BEFORE beginning)
12−- After completing a task (mark completed and add new follow-up tasks)
13−</when_to_use>
14−
15−<when_not_to_use>
16−Skip this tool when:
17−
18−- Single, straightforward task
19−- Trivial task with no organizational benefit
20−- Task completable in less than 3 trivial steps
21−- Purely conversational or informational request
22−</when_not_to_use>
23−
24−<task_states>
25−- **pending**: Task not yet started
26−- **in_progress**: Currently working on (limit to ONE task at a time)
27−- **completed**: Task finished successfully
28−
29−**IMPORTANT**: Each task requires two forms:
30−- **content**: Imperative form describing what needs to be done (e.g., "Run tests", "Build the project")
31−- **active_form**: Present continuous form shown during execution (e.g., "Running tests", "Building the project")
32−</task_states>
33−
34−<task_management>
35−- Update task status in real-time as you work
36−- Mark tasks complete IMMEDIATELY after finishing (don't batch completions)
37−- Exactly ONE task must be in_progress at any time (not less, not more)
38−- Complete current tasks before starting new ones
39−- Remove tasks that are no longer relevant from the list entirely
40−</task_management>
41−
42−<completion_requirements>
43−ONLY mark a task as completed when you have FULLY accomplished it.
44−
45−Never mark completed if:
46−- Tests are failing
47−- Implementation is partial
48−- You encountered unresolved errors
49−- You couldn't find necessary files or dependencies
50−
51−If blocked:
52−- Keep task as in_progress
53−- Create new task describing what needs to be resolved
54−</completion_requirements>
55−
56−<task_breakdown>
57−- Create specific, actionable items
58−- Break complex tasks into smaller, manageable steps
59−- Use clear, descriptive task names
60−- Always provide both content and active_form
61−</task_breakdown>
62−
63−<examples>
64−✅ Good task:
65−```json
66−{
67− "content": "Implement user authentication with JWT tokens",
68− "status": "in_progress",
69− "active_form": "Implementing user authentication with JWT tokens"
70−}
71−```
72−
73−❌ Bad task (missing active_form):
74−```json
75−{
76− "content": "Fix bug",
77− "status": "pending"
78−}
79−```
80−</examples>
81−
82−<output_behavior>
83−**NEVER** print or list todos in your response text. The user sees the todo list in real-time in the UI.
84−</output_behavior>
85−
86−<tips>
87−- When in doubt, use this tool - being proactive demonstrates attentiveness
88−- One task in_progress at a time keeps work focused
89−- Update immediately after state changes for accurate tracking
90−</tips>
internal/agent/tools/tools.go−22
@@ -2,10 +2,6 @@ package tools
2
2
3import (
3import (
4 "context"
4 "context"
5− "os"
6− "strconv"
7− "strings"
8− "testing"
9
5
10 "charm.land/fantasy"
6 "charm.land/fantasy"
11)
7)
@@ -68,21 +64,3 @@ func NewPermissionDeniedResponse() fantasy.ToolResponse {
68 resp.StopTurn = true
64 resp.StopTurn = true
69 return resp
65 return resp
70}
66}
71−
72−// FirstLineDescription returns just the first non-empty line from the embedded
73−// markdown description. The full description can be used by setting
74−// CRUSH_SHORT_TOOL_DESCRIPTIONS=0.
75−func FirstLineDescription(content []byte) string {
76− if !testing.Testing() {
77− if v, err := strconv.ParseBool(os.Getenv("CRUSH_SHORT_TOOL_DESCRIPTIONS")); err == nil && !v {
78− return strings.TrimSpace(string(content))
79− }
80− }
81− for line := range strings.SplitSeq(string(content), "\n") {
82− line = strings.TrimSpace(line)
83− if line != "" {
84− return line
85− }
86− }
87− return ""
88−}
internal/agent/tools/view.go+2−2
@@ -24,7 +24,7 @@ import (
24)
24)
25
25
26//go:embed view.md
26//go:embed view.md
27−var viewDescription []byte
27+var viewDescription string
28
28
29type ViewParams struct {
29type ViewParams struct {
30 FilePath string `json:"file_path" description:"The path to the file to read"`
30 FilePath string `json:"file_path" description:"The path to the file to read"`
@@ -79,7 +79,7 @@ func NewViewTool(
79) fantasy.AgentTool {
79) fantasy.AgentTool {
80 return fantasy.NewAgentTool(
80 return fantasy.NewAgentTool(
81 ViewToolName,
81 ViewToolName,
82− FirstLineDescription(viewDescription),
82+ viewDescription,
83 func(ctx context.Context, params ViewParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
83 func(ctx context.Context, params ViewParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
84 if params.FilePath == "" {
84 if params.FilePath == "" {
85 return fantasy.NewTextErrorResponse("file_path is required"), nil
85 return fantasy.NewTextErrorResponse("file_path is required"), nil
internal/agent/tools/view.md+1−38
@@ -1,38 +1 @@
1−Read a file by path with line numbers; supports offset and line limit (default 2000, max 200KB returned file content section); renders images (PNG, JPEG, GIF, WebP); use ls for directories.
1+Read a file by path with line numbers; supports offset and line limit (default 2000, max 200KB); renders images (PNG, JPEG, GIF, BMP, SVG, WebP); use ls for directories.
2−
3−<usage>
4−- Provide file path to read
5−- Optional offset: start reading from specific line (0-based)
6−- Optional limit: control lines read (default 2000)
7−- Don't use for directories (use LS tool instead)
8−- Supports image files (PNG, JPEG, GIF, WebP)
9−</usage>
10−
11−<features>
12−- Displays contents with line numbers
13−- Can read from any file position using offset
14−- Handles large files by limiting lines read
15−- Auto-truncates very long lines for display
16−- Suggests similar filenames when file not found
17−- Renders image files directly in terminal
18−</features>
19−
20−<limitations>
21−- Max returned file content section: 200KB after offset/limit are applied
22−- Default limit: 2000 lines
23−- Lines >2000 chars truncated
24−- Binary files (except images) cannot be displayed
25−</limitations>
26−
27−<cross_platform>
28−- Handles Windows (CRLF) and Unix (LF) line endings
29−- Works with forward slashes (/) and backslashes (\)
30−- Auto-detects text encoding for common formats
31−</cross_platform>
32−
33−<tips>
34−- Use with Glob to find files first
35−- For code exploration: Grep to find relevant files, then View to examine
36−- For large files: use offset parameter for specific sections
37−- View tool automatically detects and renders image files
38−</tips>
internal/agent/tools/web_fetch.go+2−2
@@ -13,7 +13,7 @@ import (
13)
13)
14
14
15//go:embed web_fetch.md
15//go:embed web_fetch.md
16−var webFetchToolDescription []byte
16+var webFetchToolDescription string
17
17
18// NewWebFetchTool creates a simple web fetch tool for sub-agents (no permissions needed).
18// NewWebFetchTool creates a simple web fetch tool for sub-agents (no permissions needed).
19func NewWebFetchTool(workingDir string, client *http.Client) fantasy.AgentTool {
19func NewWebFetchTool(workingDir string, client *http.Client) fantasy.AgentTool {
@@ -31,7 +31,7 @@ func NewWebFetchTool(workingDir string, client *http.Client) fantasy.AgentTool {
31
31
32 return fantasy.NewParallelAgentTool(
32 return fantasy.NewParallelAgentTool(
33 WebFetchToolName,
33 WebFetchToolName,
34− FirstLineDescription(webFetchToolDescription),
34+ webFetchToolDescription,
35 func(ctx context.Context, params WebFetchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
35 func(ctx context.Context, params WebFetchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
36 if params.URL == "" {
36 if params.URL == "" {
37 return fantasy.NewTextErrorResponse("url is required"), nil
37 return fantasy.NewTextErrorResponse("url is required"), nil
internal/agent/tools/web_fetch.md+1−28
@@ -1,28 +1 @@
1−Fetch a web URL and return content as markdown; for use inside sub-agents. Large pages (>50KB) are saved to a temp file for grep/view.
1+Fetch a web URL and return content as markdown; for use inside sub-agents. Large pages (>50KB) are saved to a temp file for grep/view.
2−
3−<usage>
4−- Provide a URL to fetch
5−- The tool fetches the content and returns it as markdown
6−- Use this when you need to follow links from the current page
7−- After fetching, analyze the content to answer the user's question
8−</usage>
9−
10−<features>
11−- Automatically converts HTML to markdown for easier analysis
12−- For large pages (>50KB), saves content to a temporary file and provides the path
13−- You can then use grep/view tools to search through the file
14−- Handles UTF-8 content validation
15−</features>
16−
17−<limitations>
18−- Max response size: 5MB
19−- Only supports HTTP and HTTPS protocols
20−- Cannot handle authentication or cookies
21−- Some websites may block automated requests
22−</limitations>
23−
24−<tips>
25−- For large pages saved to files, use grep to find relevant sections first
26−- Don't fetch unnecessary pages - only when needed to answer the question
27−- Focus on extracting specific information from the fetched content
28−</tips>
internal/agent/tools/web_search.go+2−2
@@ -11,7 +11,7 @@ import (
11)
11)
12
12
13//go:embed web_search.md
13//go:embed web_search.md
14−var webSearchToolDescription []byte
14+var webSearchToolDescription string
15
15
16// NewWebSearchTool creates a web search tool for sub-agents (no permissions needed).
16// NewWebSearchTool creates a web search tool for sub-agents (no permissions needed).
17func NewWebSearchTool(client *http.Client) fantasy.AgentTool {
17func NewWebSearchTool(client *http.Client) fantasy.AgentTool {
@@ -29,7 +29,7 @@ func NewWebSearchTool(client *http.Client) fantasy.AgentTool {
29
29
30 return fantasy.NewParallelAgentTool(
30 return fantasy.NewParallelAgentTool(
31 WebSearchToolName,
31 WebSearchToolName,
32− FirstLineDescription(webSearchToolDescription),
32+ webSearchToolDescription,
33 func(ctx context.Context, params WebSearchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
33 func(ctx context.Context, params WebSearchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
34 if params.Query == "" {
34 if params.Query == "" {
35 return fantasy.NewTextErrorResponse("query is required"), nil
35 return fantasy.NewTextErrorResponse("query is required"), nil
internal/agent/tools/web_search.md+1−18
@@ -1,18 +1 @@
1−Search the web via DuckDuckGo; returns titles, URLs, and snippets. Follow up with web_fetch to get full page content.
1+Search the web via DuckDuckGo; returns titles, URLs, and snippets. Follow up with web_fetch to get full page content.
2−
3−<usage>
4−- Provide a search query to find information on the web
5−- Returns a list of search results with titles, URLs, and snippets
6−- Use this to find relevant web pages, then use web_fetch to get full content
7−</usage>
8−
9−<parameters>
10−- query: The search query string (required)
11−- max_results: Maximum number of results to return (default: 10, max: 20)
12−</parameters>
13−
14−<tips>
15−- Use specific, targeted search queries for better results
16−- After getting results, use web_fetch to get the full content of relevant pages
17−- Combine multiple searches to gather comprehensive information
18−</tips>
internal/agent/tools/write.go+2−2
@@ -22,7 +22,7 @@ import (
22)
22)
23
23
24//go:embed write.md
24//go:embed write.md
25−var writeDescription []byte
25+var writeDescription string
26
26
27type WriteParams struct {
27type WriteParams struct {
28 FilePath string `json:"file_path" description:"The path to the file to write"`
28 FilePath string `json:"file_path" description:"The path to the file to write"`
@@ -52,7 +52,7 @@ func NewWriteTool(
52) fantasy.AgentTool {
52) fantasy.AgentTool {
53 return fantasy.NewAgentTool(
53 return fantasy.NewAgentTool(
54 WriteToolName,
54 WriteToolName,
55− FirstLineDescription(writeDescription),
55+ writeDescription,
56 func(ctx context.Context, params WriteParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
56 func(ctx context.Context, params WriteParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
57 if params.FilePath == "" {
57 if params.FilePath == "" {
58 return fantasy.NewTextErrorResponse("file_path is required"), nil
58 return fantasy.NewTextErrorResponse("file_path is required"), nil
internal/agent/tools/write.md+1−30
@@ -1,30 +1 @@
1−Create or overwrite a file with given content; auto-creates parent dirs. Cannot append. Read the file first to avoid conflicts. For surgical changes use edit or multiedit.
1+Create or overwrite a file with given content; auto-creates parent dirs. Cannot append. Read the file first to avoid conflicts. For surgical changes use edit or multiedit.
2−
3−<usage>
4−- Provide file path to write
5−- Include content to write to file (may be empty for a new empty file)
6−- Tool creates necessary parent directories automatically
7−</usage>
8−
9−<features>
10−- Creates new files or overwrites existing ones
11−- Auto-creates parent directories if missing
12−- Checks if file modified since last read for safety
13−- Avoids unnecessary writes when content unchanged
14−</features>
15−
16−<limitations>
17−- Read file before writing to avoid conflicts
18−- Cannot append (rewrites entire file)
19−</limitations>
20−
21−<cross_platform>
22−- Use forward slashes (/) for compatibility
23−</cross_platform>
24−
25−<tips>
26−- Use View tool first to examine existing files before modifying
27−- Use LS tool to verify location when creating new files
28−- Combine with Glob/Grep to find and modify multiple files
29−- Include descriptive comments when changing existing code
30−</tips>