Files
loveuer 3d403bd685 feat: Phase 1 code-agent capabilities — file_edit, git tools, parallel execution, tool output preview
New tools:
- file_edit: surgical line-range or pattern edits (yolo only)
- git_status: show changed files (read-only)
- git_diff: show unstaged/staged diff (read-only)
- git_log: show recent commits (read-only)

Agent improvements:
- parallel execution for independent read-only tool calls
- mutating tools (file_write, file_edit, shell_run) run sequentially
- tool output preview in logs with [output] marker

TUI improvements:
- tool cards now show truncated output preview (up to 8 lines)
- mergeToolLog updates placeholder cards with results
- toolStatusText keeps activity bar single-line

Documentation:
- README updated with git tools and file_edit descriptions
2026-06-24 19:47:14 -07:00

82 lines
2.0 KiB
Go

package tools
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"strings"
"agentu/pkg/llm"
)
type ShellRunTool struct {
workingDir string
}
func NewShellRunTool(workingDir string) *ShellRunTool {
return &ShellRunTool{workingDir: workingDir}
}
func (*ShellRunTool) Mutates() bool { return true }
func (t *ShellRunTool) Definition() llm.Tool {
return llm.Tool{
Type: "function",
Function: llm.ToolFunction{
Name: "shell_run",
Description: "Run a non-interactive shell command on the local machine. Use for builds, tests, local status inspection, and local automation. Avoid commands that require an interactive TTY, credentials, or open long-running sessions.",
Parameters: JSONSchema(`{
"type": "object",
"properties": {
"command": {"type": "string", "description": "Command to execute."},
"workdir": {"type": "string", "description": "Optional working directory. Relative paths are resolved from the configured agent working directory."}
},
"required": ["command"],
"additionalProperties": false
}`),
},
}
}
type shellRunArgs struct {
Command string `json:"command"`
Workdir string `json:"workdir"`
}
func (t *ShellRunTool) Execute(ctx context.Context, raw json.RawMessage) (string, error) {
var args shellRunArgs
if err := json.Unmarshal(raw, &args); err != nil {
return "", fmt.Errorf("parse arguments: %w", err)
}
if strings.TrimSpace(args.Command) == "" {
return "", errors.New("command is required")
}
workdir, err := resolvePath(t.workingDir, args.Workdir)
if err != nil {
return "", err
}
shell := os.Getenv("SHELL")
if shell == "" {
shell = "/bin/sh"
}
cmd := exec.CommandContext(ctx, shell, "-lc", args.Command)
cmd.Dir = workdir
output, err := cmd.CombinedOutput()
text := FormatResult(string(output))
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return text, fmt.Errorf("command timed out: %w", ctx.Err())
}
if err != nil {
if text != "" {
return text, fmt.Errorf("command failed: %w", err)
}
return "", fmt.Errorf("command failed: %w", err)
}
return text, nil
}