ollie/cmd/toolsrv/internal/fs/spec.go

113 lines
3.3 KiB
Go

// spec.go - fsedsl-based namespace specification for toolsrv.
package fs
import (
"context"
"fmt"
"os"
"strconv"
"strings"
"ollie/fsedsl"
)
// Spec returns the fsedsl specification for the toolsrv namespace.
// The server is captured by closures — no context threading needed.
func Spec(srv *Server) fsedsl.FsNodeDecl {
return fsedsl.DirNode("/",
fsedsl.FileNode("ctl", 0222,
fsedsl.Doc("Control: write 'load <agentID> <tool>' or 'unload <agentID> <tool>'"),
fsedsl.Write(func(data []byte) error {
return srv.Fs.HandleCtl(strings.TrimSpace(string(data)))
}),
),
fsedsl.FileNode("tools", 0666,
fsedsl.Doc("Tools: write agent ID, read tool list (rdwr)"),
fsedsl.Request(func(data []byte) ([]byte, error) {
agentID := strings.TrimSpace(string(data))
if agentID == "" {
return nil, fmt.Errorf("agent ID required")
}
return []byte(srv.Fs.HandleToolsRequest(agentID)), nil
}),
),
fsedsl.FileNode("info", 0444,
fsedsl.Doc("Host info: platform, arch"),
fsedsl.Read(func() ([]byte, error) {
return []byte(srv.HostInfo()), nil
}),
),
fsedsl.DirNode("proc",
fsedsl.FileNode("new", 0666,
fsedsl.Doc("Execute tool: write token + tool + args, read result (blocking)"),
fsedsl.Request(func(data []byte) ([]byte, error) {
result, err := srv.Fs.HandleProcNew(context.Background(), string(data))
if err != nil {
return nil, err
}
return []byte(result), nil
}),
),
fsedsl.FileNode("new.bg", 0666,
fsedsl.Doc("Execute tool in background: write token + tool + args, read pid"),
fsedsl.Request(func(data []byte) ([]byte, error) {
result, err := srv.Fs.HandleProcNewBg(context.Background(), string(data))
if err != nil {
return nil, err
}
return []byte(result), nil
}),
),
fsedsl.Each("{pid}", func() ([]fsedsl.Binding, error) {
pids := srv.Fs.ListProcs()
var bindings []fsedsl.Binding
for _, pid := range pids {
proc := srv.Fs.GetProc(pid)
if proc == nil {
continue
}
p := proc
bindings = append(bindings, fsedsl.Binding{
Name: strconv.Itoa(pid),
Children: []fsedsl.FsNodeDecl{
fsedsl.FileNode("out", 0444,
fsedsl.Doc("Process output"),
fsedsl.Read(func() ([]byte, error) {
return []byte(p.Output()), nil
}),
),
fsedsl.FileNode("wait", 0444,
fsedsl.Doc("Block until process exits, returns exit code"),
fsedsl.BlockOnce(func(_ context.Context, _ string) ([]byte, string, error) {
exitCode := p.Wait()
return []byte(fmt.Sprintf("%d\n", exitCode)), "", nil
}),
),
fsedsl.FileNode("stat", 0444,
fsedsl.Doc("Process status: running/exited, runtime, tool"),
fsedsl.Read(func() ([]byte, error) {
return []byte(p.Stat()), nil
}),
),
fsedsl.FileNode("ctl", 0222,
fsedsl.Doc("Process control: write 'signal <N>' or 'dismiss'"),
fsedsl.Write(func(data []byte) error {
return srv.Fs.HandleProcCtl(p.ID, strings.TrimSpace(string(data)))
}),
),
},
})
}
return bindings, nil
}),
),
)
}
// Mode constants for convenience.
const (
ModeReadOnly os.FileMode = 0444
ModeWriteOnly os.FileMode = 0222
ModeReadWrite os.FileMode = 0666
)