Detailed changes
@@ -53,7 +53,7 @@ const (
BashNoOutput = "no output"
)
-//go:embed bash.tpl
+//go:embed bash.md.tpl
var bashDescriptionTmpl []byte
var bashDescriptionTpl = template.Must(
@@ -66,6 +66,7 @@ type bashDescriptionData struct {
MaxOutputLength int
Attribution config.Attribution
ModelID string
+ RgAvailable bool
}
var bannedCommands = []string{
@@ -149,6 +150,7 @@ func bashDescription(attribution *config.Attribution, modelID string) string {
MaxOutputLength: MaxOutputLength,
Attribution: *attribution,
ModelID: modelID,
+ RgAvailable: getRg() != "",
}); err != nil {
// this should never happen.
panic("failed to execute bash description template: " + err.Error())
@@ -21,6 +21,9 @@ Common shell builtins and core utils available on Windows.
- Chain with ';' or '&&', avoid newlines except in quoted strings
- Each command runs in independent shell (no state persistence between calls)
- Prefer absolute paths over 'cd' (use 'cd' only if user explicitly requests)
+{{- if .RgAvailable }}
+- Ripgrep (`rg`) is available; prefer it over `grep` for faster, more intuitive searching
+{{- end }}
</usage_notes>
<background_execution>
@@ -82,7 +85,7 @@ When user asks to create git commit:
6. Run git status to verify.
-Notes: Use "git commit -am" when possible, don't stage unrelated files, NEVER update config, don't push, no -i flags, no empty commits, return empty response.
+Notes: Use "git commit -am" when possible, don't stage unrelated files, NEVER update config, don't push, no -i flags, no empty commits, return empty response, when rebasing always use -m or GIT_EDITOR=true.
</git_commits>
<pull_requests>
@@ -4,6 +4,7 @@ import (
"context"
_ "embed"
"fmt"
+ "html/template"
"io"
"net/http"
"strings"
@@ -21,8 +22,13 @@ const (
MaxFetchSize = 100 * 1024 // 100KB
)
-//go:embed fetch.md
-var fetchDescription string
+//go:embed fetch.md.tpl
+var fetchDescriptionTmpl []byte
+
+var fetchDescriptionTpl = template.Must(
+ template.New("fetchDescription").
+ Parse(string(fetchDescriptionTmpl)),
+)
func NewFetchTool(permissions permission.Service, workingDir string, client *http.Client) fantasy.AgentTool {
if client == nil {
@@ -39,7 +45,7 @@ func NewFetchTool(permissions permission.Service, workingDir string, client *htt
return fantasy.NewParallelAgentTool(
FetchToolName,
- fetchDescription,
+ renderToolDescription(fetchDescriptionTpl),
func(ctx context.Context, params FetchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.URL == "" {
return fantasy.NewTextErrorResponse("URL parameter is required"), nil
@@ -1 +0,0 @@
-Fetch raw content from a URL as text, markdown, or html (max 100KB); no AI processing. For analysis or extraction use agentic_fetch.
@@ -0,0 +1,2 @@
+Fetch raw content from a URL as text, markdown, or html (max 100KB); no AI processing. For analysis or extraction use agentic_fetch.
+{{- if .GhAvailable }} For GitHub content when an exact repo, issue, or PR link is provided, use `gh` CLI in bash instead.{{- end }}
@@ -1,7 +1,10 @@
package tools
import (
+ "bytes"
"context"
+ "html/template"
+ "os/exec"
"charm.land/fantasy"
)
@@ -64,3 +67,26 @@ func NewPermissionDeniedResponse() fantasy.ToolResponse {
resp.StopTurn = true
return resp
}
+
+// ghAvailable indicates whether the `gh` CLI is available on PATH.
+var ghAvailable = func() bool {
+ _, err := exec.LookPath("gh")
+ return err == nil
+}()
+
+// toolDescriptionData is the common data structure for tool description templates.
+type toolDescriptionData struct {
+ GhAvailable bool
+}
+
+// renderToolDescription renders a tool description template with the given data.
+func renderToolDescription(tmpl *template.Template) string {
+ data := toolDescriptionData{
+ GhAvailable: ghAvailable,
+ }
+ var out bytes.Buffer
+ if err := tmpl.Execute(&out, data); err != nil {
+ panic("failed to execute tool description template: " + err.Error())
+ }
+ return out.String()
+}
@@ -4,6 +4,7 @@ import (
"context"
_ "embed"
"fmt"
+ "html/template"
"net/http"
"os"
"strings"
@@ -12,8 +13,13 @@ import (
"charm.land/fantasy"
)
-//go:embed web_fetch.md
-var webFetchToolDescription string
+//go:embed web_fetch.md.tpl
+var webFetchDescriptionTmpl []byte
+
+var webFetchDescriptionTpl = template.Must(
+ template.New("webFetchDescription").
+ Parse(string(webFetchDescriptionTmpl)),
+)
// NewWebFetchTool creates a simple web fetch tool for sub-agents (no permissions needed).
func NewWebFetchTool(workingDir string, client *http.Client) fantasy.AgentTool {
@@ -31,7 +37,7 @@ func NewWebFetchTool(workingDir string, client *http.Client) fantasy.AgentTool {
return fantasy.NewParallelAgentTool(
WebFetchToolName,
- webFetchToolDescription,
+ renderToolDescription(webFetchDescriptionTpl),
func(ctx context.Context, params WebFetchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.URL == "" {
return fantasy.NewTextErrorResponse("url is required"), nil
@@ -1 +0,0 @@
-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.
@@ -0,0 +1,2 @@
+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.
+{{- if .GhAvailable }} For GitHub content when an exact repo, issue, or PR link is provided, use `gh` CLI in bash instead.{{- end }}
@@ -3,6 +3,7 @@ package tools
import (
"context"
_ "embed"
+ "html/template"
"log/slog"
"net/http"
"time"
@@ -10,8 +11,13 @@ import (
"charm.land/fantasy"
)
-//go:embed web_search.md
-var webSearchToolDescription string
+//go:embed web_search.md.tpl
+var webSearchDescriptionTmpl []byte
+
+var webSearchDescriptionTpl = template.Must(
+ template.New("webSearchDescription").
+ Parse(string(webSearchDescriptionTmpl)),
+)
// NewWebSearchTool creates a web search tool for sub-agents (no permissions needed).
func NewWebSearchTool(client *http.Client) fantasy.AgentTool {
@@ -29,7 +35,7 @@ func NewWebSearchTool(client *http.Client) fantasy.AgentTool {
return fantasy.NewParallelAgentTool(
WebSearchToolName,
- webSearchToolDescription,
+ renderToolDescription(webSearchDescriptionTpl),
func(ctx context.Context, params WebSearchParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
if params.Query == "" {
return fantasy.NewTextErrorResponse("query is required"), nil
@@ -1 +0,0 @@
-Search the web via DuckDuckGo; returns titles, URLs, and snippets. Follow up with web_fetch to get full page content.
@@ -0,0 +1,2 @@
+Search the web via DuckDuckGo; returns titles, URLs, and snippets. Follow up with web_fetch to get full page content.
+{{- if .GhAvailable }} For GitHub searches when an exact repo name, issue, or link is provided, use `gh search` in bash instead.{{- end }}