tools: pipe JSON args to stdin, sort tool/skill lists

- callPromotedTool now pipes raw JSON object to tool stdin instead of
  unpacking positional args. Tools parse their own input.
- Default schema changed from positional array to empty object.
- Registry.Discover() skips files without ollie:prompt marker.
- Registry.Summaries() and Loaded() return sorted results (both
  tools and skills registries).
This commit is contained in:
Levi Neely 2026-07-29 08:47:06 +02:00
parent d5c591ad26
commit 75b76b118e
4 changed files with 21 additions and 19 deletions

View File

@ -4,6 +4,7 @@ package skills
import ( import (
"fmt" "fmt"
"sort"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -86,6 +87,7 @@ func (r *Registry) Summaries() []SkillInfo {
for _, info := range r.global { for _, info := range r.global {
out = append(out, info) out = append(out, info)
} }
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
return out return out
} }
@ -133,6 +135,7 @@ func (r *Registry) Loaded(sessionID string) []SkillInfo {
out = append(out, info) out = append(out, info)
} }
} }
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
return out return out
} }

View File

@ -2,6 +2,8 @@ package execute
import ( import (
"fmt" "fmt"
"strings"
"sort"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
@ -45,6 +47,9 @@ func (r *Registry) Discover() error {
continue continue
} }
script := string(data) script := string(data)
if !strings.Contains(script, "ollie:prompt") {
continue
}
global[e.Name()] = ParseToolInfo(e.Name(), script) global[e.Name()] = ParseToolInfo(e.Name(), script)
} }
r.mu.Lock() r.mu.Lock()
@ -63,6 +68,7 @@ func (r *Registry) Summaries() []tools.ToolInfo {
Description: info.Description, Description: info.Description,
}) })
} }
sort.Slice(summaries, func(i, j int) bool { return summaries[i].Name < summaries[j].Name })
return summaries return summaries
} }
@ -121,6 +127,7 @@ func (r *Registry) Loaded(sessionID string) []tools.ToolInfo {
for _, info := range sessionTools { for _, info := range sessionTools {
loaded = append(loaded, info) loaded = append(loaded, info)
} }
sort.Slice(loaded, func(i, j int) bool { return loaded[i].Name < loaded[j].Name })
return loaded return loaded
} }

View File

@ -79,7 +79,7 @@ func ParseToolInfo(name, script string) tools.ToolInfo {
desc := ExtractShortDescription(prompt) desc := ExtractShortDescription(prompt)
argsSchema := ExtractArgsSchema(script) argsSchema := ExtractArgsSchema(script)
if argsSchema == nil { if argsSchema == nil {
argsSchema = json.RawMessage(`{"type":"object","properties":{"tool":{"type":"string"},"args":{"type":"array","items":{"type":"string"}}},"required":["tool","args"]}`) argsSchema = json.RawMessage(`{"type":"object","properties":{}}`)
} }
meta := ExtractMetadata(script) meta := ExtractMetadata(script)
tier := ExtractTier(script) tier := ExtractTier(script)

View File

@ -274,7 +274,7 @@ func (e *Server) SetToolRegistry(r *Registry, sessionID string) {
} }
// callPromotedTool executes a tool promoted via the registry by running the // callPromotedTool executes a tool promoted via the registry by running the
// script file inside the sandbox, or elevated if the args request it. // script file inside the sandbox, piping the JSON args to stdin.
func (e *Server) callPromotedTool(ctx context.Context, tool string, args json.RawMessage) (json.RawMessage, error) { func (e *Server) callPromotedTool(ctx context.Context, tool string, args json.RawMessage) (json.RawMessage, error) {
// Resolve script path. // Resolve script path.
if strings.Contains(tool, "/") || strings.Contains(tool, "..") { if strings.Contains(tool, "/") || strings.Contains(tool, "..") {
@ -282,32 +282,23 @@ func (e *Server) callPromotedTool(ctx context.Context, tool string, args json.Ra
} }
path := filepath.Join(ToolsPath(), tool) path := filepath.Join(ToolsPath(), tool)
// Parse args — extract positional args and optional elevated flag. // Extract elevated flag (dispatch-level concern, not passed to tool).
var positional []string
elevated := false elevated := false
var argMap map[string]interface{} var argMap map[string]interface{}
if err := json.Unmarshal(args, &argMap); err == nil { if err := json.Unmarshal(args, &argMap); err == nil {
if argsArr, ok := argMap["args"]; ok {
if arr, ok := argsArr.([]interface{}); ok {
for _, v := range arr {
positional = append(positional, fmt.Sprintf("%v", v))
}
}
}
if e, ok := argMap["elevated"]; ok { if e, ok := argMap["elevated"]; ok {
if b, ok := e.(bool); ok { if b, ok := e.(bool); ok {
elevated = b elevated = b
} }
// Remove elevated from the args passed to the tool.
delete(argMap, "elevated")
args, _ = json.Marshal(argMap)
} }
} }
// Build a shell command that runs the tool script with its args. // The tool script receives JSON args on stdin.
quoted := make([]string, 0, len(positional)+1) code := path
quoted = append(quoted, path) stdinData := string(args)
for _, a := range positional {
quoted = append(quoted, fmt.Sprintf("%q", a))
}
code := strings.Join(quoted, " ")
var result string var result string
var err error var err error
@ -317,7 +308,7 @@ func (e *Server) callPromotedTool(ctx context.Context, tool string, args json.Ra
e.wdMu.RUnlock() e.wdMu.RUnlock()
result, err = e.executeElevated(ctx, code, workDir, 30) result, err = e.executeElevated(ctx, code, workDir, 30)
} else { } else {
result, err = e.executeWithStdin(ctx, code, "bash", 30, "default", false, "") result, err = e.executeWithStdin(ctx, code, "bash", 30, "default", false, stdinData)
} }
if err != nil { if err != nil {
return json.Marshal(map[string]interface{}{ return json.Marshal(map[string]interface{}{
@ -332,6 +323,7 @@ func (e *Server) callPromotedTool(ctx context.Context, tool string, args json.Ra
// Close is called when the session ends. Calls OnClose hook if registered. // Close is called when the session ends. Calls OnClose hook if registered.
func (e *Server) Close() { func (e *Server) Close() {
e.cleanupDetached() e.cleanupDetached()