Files
goclaw/internal/tools/credential_context.go
T
viettranx 17170b404e fix(tools): scope credentialed-CLI blocked-command wording to [CREDENTIALED EXEC] marker
Generic "operation requires admin approval" wording was over-applied
by the LLM to plain shell exec failures, causing unjustified pre-refusals.
Scope the section to commands returning the [CREDENTIALED EXEC] error
marker (already emitted at 7 sites in credentialed_exec.go).

Adds characterization test pinning the new wording and rejecting the
unqualified phrase.
2026-04-24 20:53:28 +07:00

70 lines
2.8 KiB
Go

package tools
import (
"encoding/json"
"fmt"
"strings"
"github.com/nextlevelbuilder/goclaw/internal/store"
)
// GenerateCredentialContext builds a TOOLS.md supplement from enabled secure CLI configs.
// This context is injected into the agent's system prompt so the LLM knows:
// - Which CLIs are available with pre-configured auth
// - That these CLIs run in Direct Exec Mode (no shell operators)
// - Which operations are blocked per CLI
// Returns empty string if no credentialed CLIs are configured.
func GenerateCredentialContext(creds []store.SecureCLIBinary) string {
if len(creds) == 0 {
return ""
}
var b strings.Builder
b.WriteString("\n\n## Credentialed CLI Tools\n\n")
b.WriteString("The following CLI tools have pre-configured authentication.\n")
b.WriteString("Credentials are injected automatically — do NOT attempt to provide or read credentials.\n\n")
b.WriteString("⚠️ CRITICAL: These tools run in DIRECT EXEC MODE (no shell).\n")
b.WriteString("- Do NOT use shell operators: ; && || | > >> < $() ``\n")
b.WriteString("- Do NOT use environment variables: $VAR, ${VAR}\n")
b.WriteString("- Each exec() call runs ONE command only\n")
b.WriteString("- Use --json or --format=json for structured output\n")
b.WriteString("- Parse JSON output directly — do NOT pipe to jq\n\n")
b.WriteString("### Available CLIs:\n\n")
for _, c := range creds {
b.WriteString(fmt.Sprintf("**%s** — %s\n", c.BinaryName, c.Description))
if blocked := summarizeDenyPatterns(c.DenyArgs); blocked != "" {
b.WriteString(fmt.Sprintf(" Blocked: %s\n", blocked))
}
if c.Tips != "" {
b.WriteString(fmt.Sprintf(" Tip: %s\n", c.Tips))
}
b.WriteString("\n")
}
b.WriteString("### When a credentialed CLI command is blocked:\n")
b.WriteString("This section applies ONLY to commands that return a `[CREDENTIALED EXEC]` error.\n")
b.WriteString("Tell the user: \"This credentialed CLI operation is blocked by policy and may require admin approval.\"\n")
b.WriteString("Do NOT attempt workarounds to bypass blocked credentialed CLI commands.\n")
return b.String()
}
// summarizeDenyPatterns converts regex deny patterns to a human-readable summary.
// E.g. ["auth\\s+", "ssh-key", "repo\\s+delete"] -> "auth, ssh-key, repo delete"
func summarizeDenyPatterns(patternsJSON json.RawMessage) string {
if len(patternsJSON) == 0 {
return ""
}
var patterns []string
if err := json.Unmarshal(patternsJSON, &patterns); err != nil || len(patterns) == 0 {
return ""
}
// Convert regex patterns to readable form by stripping common regex syntax
readable := make([]string, 0, len(patterns))
replacer := strings.NewReplacer(`\s+`, " ", `\s*`, " ", `\b`, "", `\w+`, "*")
for _, p := range patterns {
readable = append(readable, replacer.Replace(p))
}
return strings.Join(readable, ", ")
}