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:
parent
d5c591ad26
commit
75b76b118e
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Reference in New Issue