tools: add Python support; document tools in system prompt
- Detect language from shebang (bash default, python3 if shebang contains "python") - Inject args language-appropriately: set -- for bash, sys.argv for Python - Add python3 to executor language switch - Handle Python steps in execute_pipe using bash $'...' embedding - Update execute_tool description to mention supported languages - Add Tools section to SYSTEM_PROMPT.md pointing to $OLLIE_9MOUNT/t/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9775380532
commit
3d02de4a71
|
|
@ -22,7 +22,7 @@ func ExecuteDefs(toolsPath string) []ToolInfo {
|
|||
},
|
||||
{
|
||||
Name: "execute_tool",
|
||||
Description: "Run a named tool script from " + toolsPath + " in a sandboxed environment. Use this only for named scripts, not for inline shell commands.",
|
||||
Description: "Run a named tool script from " + toolsPath + " in a sandboxed environment. Supported languages: bash, python3 (detected from shebang). Use this only for named scripts, not for inline shell commands.",
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"required": ["tool"],
|
||||
|
|
|
|||
|
|
@ -81,13 +81,10 @@ func dispatchExecuteTool(ctx context.Context, e *Server, args json.RawMessage) (
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
language := detectLanguage(toolCode)
|
||||
code := toolCode
|
||||
if len(a.Args) > 0 {
|
||||
var escaped []string
|
||||
for _, arg := range a.Args {
|
||||
escaped = append(escaped, "'"+strings.ReplaceAll(arg, "'", "'\\''")+"'")
|
||||
}
|
||||
code = fmt.Sprintf("set -- %s\n%s", strings.Join(escaped, " "), code)
|
||||
code = injectArgs(language, a.Tool, a.Args, toolCode)
|
||||
}
|
||||
timeout := a.Timeout
|
||||
if timeout <= 0 {
|
||||
|
|
@ -97,7 +94,7 @@ func dispatchExecuteTool(ctx context.Context, e *Server, args json.RawMessage) (
|
|||
if sandbox == "" {
|
||||
sandbox = "default"
|
||||
}
|
||||
return e.Execute(ctx, code, "bash", timeout, sandbox, true)
|
||||
return e.Execute(ctx, code, language, timeout, sandbox, true)
|
||||
}
|
||||
|
||||
func dispatchExecutePipe(ctx context.Context, e *Server, args json.RawMessage) (string, error) {
|
||||
|
|
|
|||
|
|
@ -181,20 +181,24 @@ func (e *Server) Execute(ctx context.Context, code, language string, timeout int
|
|||
defer cancel()
|
||||
|
||||
var cmd *exec.Cmd
|
||||
var interpreter []string
|
||||
switch language {
|
||||
case "bash", "":
|
||||
wrapped := sandbox.WrapCommand(cfg, []string{"bash", "-c", code}, workDir)
|
||||
cmd = exec.CommandContext(ctx, wrapped[0], wrapped[1:]...)
|
||||
cmd.Dir = workDir
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
cmd.Cancel = func() error {
|
||||
if cmd.Process != nil {
|
||||
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
interpreter = []string{"bash", "-c", code}
|
||||
case "python3", "python":
|
||||
interpreter = []string{"python3", "-c", code}
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported language: %s (supported: bash)", language)
|
||||
return "", fmt.Errorf("unsupported language: %s (supported: bash, python3)", language)
|
||||
}
|
||||
wrapped := sandbox.WrapCommand(cfg, interpreter, workDir)
|
||||
cmd = exec.CommandContext(ctx, wrapped[0], wrapped[1:]...)
|
||||
cmd.Dir = workDir
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
cmd.Cancel = func() error {
|
||||
if cmd.Process != nil {
|
||||
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var outputBuf bytes.Buffer
|
||||
|
|
|
|||
|
|
@ -38,15 +38,20 @@ func BuildPipeline(steps []PipeStep) (string, bool, error) {
|
|||
} else {
|
||||
return "", false, fmt.Errorf("each pipe step requires either 'tool' or 'code'")
|
||||
}
|
||||
language := "bash"
|
||||
if step.Tool != "" {
|
||||
language = detectLanguage(code)
|
||||
}
|
||||
if len(step.Args) > 0 {
|
||||
var escaped []string
|
||||
for _, arg := range step.Args {
|
||||
escaped = append(escaped, "'"+strings.ReplaceAll(arg, "'", "'\\''")+"'")
|
||||
}
|
||||
parts = append(parts, fmt.Sprintf("( set -- %s\n%s )", strings.Join(escaped, " "), code))
|
||||
} else {
|
||||
code = injectArgs(language, step.Tool, step.Args, code)
|
||||
}
|
||||
switch language {
|
||||
case "python3":
|
||||
parts = append(parts, fmt.Sprintf("( python3 -c $'%s' )", ansiCEscape(code)))
|
||||
default:
|
||||
parts = append(parts, fmt.Sprintf("(\n%s\n)", code))
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, " |\n"), true, nil
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,57 @@ func ToolsPath() string {
|
|||
return filepath.Join(home, ".local", "share", "ollie", "tools")
|
||||
}
|
||||
|
||||
// detectLanguage infers the script language from the shebang line.
|
||||
// Returns "python3" for Python scripts, "bash" for everything else.
|
||||
func detectLanguage(code string) string {
|
||||
line, _, _ := strings.Cut(code, "\n")
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "#!") && strings.Contains(line, "python") {
|
||||
return "python3"
|
||||
}
|
||||
return "bash"
|
||||
}
|
||||
|
||||
// injectArgs prepends language-appropriate argument binding to code.
|
||||
func injectArgs(language, name string, args []string, code string) string {
|
||||
switch language {
|
||||
case "python3":
|
||||
quoted := make([]string, len(args))
|
||||
for i, a := range args {
|
||||
quoted[i] = fmt.Sprintf("%q", a)
|
||||
}
|
||||
return fmt.Sprintf("import sys\nsys.argv = [%q, %s]\n%s", name, strings.Join(quoted, ", "), code)
|
||||
default: // bash
|
||||
escaped := make([]string, len(args))
|
||||
for i, a := range args {
|
||||
escaped[i] = "'" + strings.ReplaceAll(a, "'", "'\\''") + "'"
|
||||
}
|
||||
return fmt.Sprintf("set -- %s\n%s", strings.Join(escaped, " "), code)
|
||||
}
|
||||
}
|
||||
|
||||
// ansiCEscape escapes a string for embedding in a bash $'...' literal.
|
||||
func ansiCEscape(s string) string {
|
||||
var b strings.Builder
|
||||
for _, r := range s {
|
||||
switch r {
|
||||
case '\\':
|
||||
b.WriteString(`\\`)
|
||||
case '\'':
|
||||
b.WriteString(`\'`)
|
||||
case '\n':
|
||||
b.WriteString(`\n`)
|
||||
case '\r':
|
||||
b.WriteString(`\r`)
|
||||
case '\t':
|
||||
b.WriteString(`\t`)
|
||||
default:
|
||||
b.WriteRune(r)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// ReadTool reads a named tool script from the tools directory.
|
||||
func ReadTool(name string) (string, error) {
|
||||
if strings.Contains(name, "/") || strings.Contains(name, "..") {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,18 @@ Be truthful. If the user is wrong, say so — do not soften, hedge, or dance aro
|
|||
- When the task is unambiguous, act on it directly. Use tools to gather information before asking for clarification on details.
|
||||
- Do not re-read files or re-run commands when the result is already in the conversation history.
|
||||
|
||||
# Tools
|
||||
|
||||
Tool scripts live at `${OLLIE_9MOUNT:-$HOME/mnt/ollie}/t/`. Run them with `execute_tool` or as steps in an `execute_pipe` pipeline.
|
||||
|
||||
```sh
|
||||
ls ${OLLIE_9MOUNT:-$HOME/mnt/ollie}/t/ # list available tools
|
||||
```
|
||||
|
||||
Supported languages are detected from the script's shebang line:
|
||||
- `#!/usr/bin/env bash` (or any bash shebang) — runs with `bash -c`
|
||||
- `#!/usr/bin/env python3` (or any python shebang) — runs with `python3 -c`; `sys.argv` is set automatically when args are provided
|
||||
|
||||
# Skills
|
||||
|
||||
Skills are domain-specific knowledge files. Discover and load them before starting non-trivial tasks.
|
||||
|
|
|
|||
Reference in New Issue