134 lines
3.2 KiB
Go
134 lines
3.2 KiB
Go
package fs
|
|
|
|
// Shared utilities for the 9P namespace handlers.
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"ollie/cmd/olliesrv/internal/agent"
|
|
"ollie/cmd/olliesrv/internal/session"
|
|
"ollie/toolsrv"
|
|
"ollie/virtfs"
|
|
)
|
|
|
|
// wireAgentEvents sets the state change callback on an agent.
|
|
func wireAgentEvents(sessID string, ag *agent.Agent) {
|
|
ag.SetOnStateChange(func(agentID, state string) {
|
|
session.PublishEvent("session."+sessID+".agent."+agentID+".state", state)
|
|
})
|
|
}
|
|
|
|
// dispatch routes a command to the matching handler based on the first word.
|
|
func dispatch(handlers map[string]func([]string) ([]byte, error), data []byte) ([]byte, error) {
|
|
input := strings.TrimSpace(string(data))
|
|
if input == "" {
|
|
return nil, nil
|
|
}
|
|
parts := strings.Fields(input)
|
|
h, ok := handlers[parts[0]]
|
|
if !ok {
|
|
return nil, fmt.Errorf("unknown ctl command: %s", parts[0])
|
|
}
|
|
return h(parts[1:])
|
|
}
|
|
|
|
// stripMarkers removes [[[...]]] marker lines and source fence delimiter lines
|
|
// from raw chat data. Content inside fences is preserved — only the ``` lines
|
|
// themselves are stripped.
|
|
func stripMarkers(data []byte) []byte {
|
|
var out []byte
|
|
for len(data) > 0 {
|
|
nl := bytes.IndexByte(data, '\n')
|
|
if nl < 0 {
|
|
out = append(out, data...)
|
|
break
|
|
}
|
|
line := data[:nl+1]
|
|
data = data[nl+1:]
|
|
|
|
trimmed := bytes.TrimRight(line, "\n")
|
|
if len(trimmed) >= 7 && bytes.HasPrefix(trimmed, []byte("[[[")) && bytes.HasSuffix(trimmed, []byte("]]]")) {
|
|
continue
|
|
}
|
|
stripped := bytes.TrimSpace(trimmed)
|
|
if bytes.HasPrefix(stripped, []byte("```")) && !bytes.HasPrefix(stripped, []byte("````")) {
|
|
continue
|
|
}
|
|
|
|
out = append(out, line...)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// helpSpec stores the spec for help generation. Set during tree build.
|
|
var helpSpec *virtfs.FsNodeDecl
|
|
|
|
var (
|
|
helpOnce sync.Once
|
|
helpText string
|
|
helpFn = func() string {
|
|
helpOnce.Do(func() {
|
|
if helpSpec != nil {
|
|
helpText = virtfs.GenerateHelp(*helpSpec)
|
|
}
|
|
})
|
|
return helpText
|
|
}
|
|
)
|
|
|
|
// parseAgentNewRequest parses agent/new payloads in both formats:
|
|
// - Newline-separated key=value (new format, supports multi-line values via \n escape)
|
|
// - Space-separated key=value (legacy format from GUI)
|
|
//
|
|
// A bare word (no =) is treated as the profile name (legacy positional arg).
|
|
// Returns AgentParams, the prompt (empty if not a sub-agent request), and the parent agent ID.
|
|
func parseAgentNewRequest(data []byte) (session.AgentParams, string, string) {
|
|
s := strings.TrimSpace(string(data))
|
|
if s == "" {
|
|
return session.AgentParams{Profile: "default"}, "", ""
|
|
}
|
|
|
|
var tokens []string
|
|
if strings.Contains(s, "\n") {
|
|
tokens = strings.Split(s, "\n")
|
|
} else {
|
|
tokens = strings.Fields(s)
|
|
}
|
|
|
|
p := session.AgentParams{Profile: "default"}
|
|
var prompt, parent string
|
|
for _, tok := range tokens {
|
|
tok = strings.TrimSpace(tok)
|
|
if tok == "" {
|
|
continue
|
|
}
|
|
if k, v, ok := strings.Cut(tok, "="); ok {
|
|
v = toolsrv.UnescapeValue(v)
|
|
switch k {
|
|
case "cwd":
|
|
p.CWD = v
|
|
case "backend":
|
|
p.Backend = v
|
|
case "model":
|
|
p.Model = v
|
|
case "name":
|
|
p.Name = v
|
|
case "remote":
|
|
p.Remote = v
|
|
case "profile":
|
|
p.Profile = v
|
|
case "prompt":
|
|
prompt = v
|
|
case "parent":
|
|
parent = v
|
|
}
|
|
} else if p.Profile == "default" {
|
|
p.Profile = tok
|
|
}
|
|
}
|
|
return p, prompt, parent
|
|
}
|