387 lines
11 KiB
Go
387 lines
11 KiB
Go
package fs
|
|
|
|
// Filesystem specification — the single source of truth for the 9P namespace.
|
|
// Every file and directory is declared here with Doc() descriptions.
|
|
// treeSpecUID/GID are set at NewRoot() time from the current system user.
|
|
|
|
import (
|
|
"ollie/agent"
|
|
"ollie/elevate"
|
|
"ollie/fsedsl"
|
|
olog "ollie/log"
|
|
"ollie/skills"
|
|
"ollie/toolsrv"
|
|
)
|
|
|
|
// HandlerCtx carries runtime dependencies for handler functions.
|
|
// The wire step populates the correct subset per node scope.
|
|
// Handlers assert their scope by checking which pointer is non-nil.
|
|
type HandlerCtx struct {
|
|
// Root-level (always available).
|
|
Root *Tree
|
|
Log *olog.Logger
|
|
ToolReg *toolsrv.Registry
|
|
Skills *skills.Registry
|
|
Models *ModelCache
|
|
Elevate *elevate.Broker
|
|
Invalidate func() // called by root ctl "invalidate"
|
|
Shutdown func() // called by root ctl "kill"
|
|
|
|
// Session scope.
|
|
Session *SessionNode
|
|
Remove func()
|
|
Rename func(string) error
|
|
SaveFn func([]byte) error
|
|
|
|
// Agent scope.
|
|
Agent *agent.Agent
|
|
AgentLog *AgentLog
|
|
|
|
// Generic binding data — type depends on context.
|
|
Data any
|
|
}
|
|
|
|
// Type aliases from fsedsl, specialized to HandlerCtx.
|
|
type (
|
|
FsNodeDecl = fsedsl.FsNodeDecl[HandlerCtx]
|
|
NodeOption = fsedsl.NodeOption[HandlerCtx]
|
|
Binding = fsedsl.Binding[HandlerCtx]
|
|
Tree = fsedsl.Tree
|
|
File = fsedsl.File
|
|
FileTree = fsedsl.FileTree
|
|
FileConfig = fsedsl.FileConfig
|
|
SyntheticFileInfo = fsedsl.SyntheticFileInfo
|
|
SyntheticEntry = fsedsl.SyntheticEntry
|
|
TreeOption = fsedsl.TreeOption
|
|
BindTarget = fsedsl.BindTarget
|
|
)
|
|
|
|
// Structural permissions — used by Tree for mounted children and synthetic files.
|
|
const (
|
|
PermChildDir = fsedsl.PermChildDir
|
|
PermIdx = fsedsl.PermIdx
|
|
PermMkdir = fsedsl.PermMkdir
|
|
PermMkdirPrivate = fsedsl.PermMkdirPrivate
|
|
BindData = fsedsl.BindData
|
|
)
|
|
|
|
var (
|
|
DirNode = fsedsl.DirNode[HandlerCtx]
|
|
FileNode = fsedsl.FileNode[HandlerCtx]
|
|
Each = fsedsl.Each[HandlerCtx]
|
|
Doc = fsedsl.Doc[HandlerCtx]
|
|
Read = fsedsl.Read[HandlerCtx]
|
|
Write = fsedsl.Write[HandlerCtx]
|
|
Stream = fsedsl.Stream[HandlerCtx]
|
|
BlockOnce = fsedsl.BlockOnce[HandlerCtx]
|
|
Request = fsedsl.Request[HandlerCtx]
|
|
Remove = fsedsl.RemoveNode[HandlerCtx]
|
|
Stat = fsedsl.StatOverride[HandlerCtx]
|
|
UID = fsedsl.UID[HandlerCtx]
|
|
GID = fsedsl.GID[HandlerCtx]
|
|
Bind = fsedsl.Bind[HandlerCtx]
|
|
)
|
|
|
|
var treeSpecUID, treeSpecGID string
|
|
|
|
var treeSpec = DirNode("/",
|
|
FileNode("backends", 0444,
|
|
Doc("Available backend names, one per line (ollama, openai, anthropic, etc.)"),
|
|
Read(readBackends),
|
|
GID("agent"),
|
|
),
|
|
|
|
FileNode("help", 0444,
|
|
Doc("Filesystem reference (this output). Generated from spec."),
|
|
Read(readHelp),
|
|
GID("agent"),
|
|
),
|
|
|
|
FileNode("models", 0444,
|
|
Doc("Available models across all backends. Format: backend<tab>model per line."),
|
|
Read(readModels),
|
|
GID("agent"),
|
|
),
|
|
|
|
FileNode("agents", 0444,
|
|
Doc("Available agent profile names (from agents/*.json), one per line"),
|
|
Read(readAgents),
|
|
GID("agent"),
|
|
),
|
|
|
|
FileNode("tools", 0444,
|
|
Doc("Available tools and their descriptions. Format: name<spaces>description per line."),
|
|
Read(readTools),
|
|
GID("agent"),
|
|
),
|
|
|
|
FileNode("ctl", 0666,
|
|
Doc("Server control. Write: 'invalidate' (refresh caches), 'kill' (shutdown server)"),
|
|
Write(writeRootCtl),
|
|
GID("agent"),
|
|
),
|
|
|
|
FileNode("eventwait", 0444,
|
|
Doc("Blocking read for server events. Returns next event as 'topic payload'."),
|
|
Read(readEventwait),
|
|
BlockOnce(blockEventwait),
|
|
GID("agent"),
|
|
),
|
|
|
|
FileNode("complete", 0666,
|
|
Doc("Code completion. Write JSON {file,prefix,suffix,context} or plain prefix. Read returns completion."),
|
|
Request(requestComplete),
|
|
GID("agent"),
|
|
),
|
|
|
|
FileNode("generate", 0666,
|
|
Doc("One-shot LLM generation. Write JSON {prompt,...} or plain text. Read returns result."),
|
|
Request(requestGenerate),
|
|
GID("agent"),
|
|
),
|
|
|
|
FileNode("route", 0666,
|
|
Doc("Model routing. Write task description. Read returns 'backend=X model=Y' recommendation."),
|
|
Request(requestRoute),
|
|
GID("agent"),
|
|
),
|
|
|
|
FileNode("aliases", 0444,
|
|
Doc("Alias table for ID-based lookups. Format: id<tab>path per line."),
|
|
Read(readAliases),
|
|
GID("agent"),
|
|
),
|
|
|
|
DirNode("elevate",
|
|
Doc("Elevation broker for sandboxed command approval"),
|
|
GID("agent"),
|
|
|
|
FileNode("policy", 0666,
|
|
Doc("Global elevation policy. Read: current policy. Write: key=value to update."),
|
|
Read(readElevatePolicy),
|
|
Write(writeElevatePolicy),
|
|
),
|
|
|
|
Each("{reqid}", elevateBindings,
|
|
Doc("Pending elevation request. Read: request summary. Write: 'approve', 'deny', or 'persist'."),
|
|
Bind(BindData),
|
|
Read(readElevateRequest),
|
|
Write(writeElevateRequest),
|
|
),
|
|
),
|
|
|
|
DirNode("session",
|
|
Doc("Session management. Contains per-session subdirectories."),
|
|
GID("agent"),
|
|
|
|
FileNode("new", 0666,
|
|
Doc("Create session. Write key=value args (name=, cwd=, agent=, backend=, model=). Read returns session name."),
|
|
Request(requestSessionNew),
|
|
),
|
|
|
|
FileNode("idx", 0444,
|
|
Doc("Session index. Format: name<tab>state<tab>cwd<tab>backend<tab>model<tab>agentName<tab>id per line."),
|
|
Read(readSessionIdx),
|
|
),
|
|
|
|
Each("{sname}", sessionBindings,
|
|
Doc("Session directory. Addressable by name or immutable ID."),
|
|
|
|
FileNode("env", 0444,
|
|
Doc("Session environment. OLLIE_SESSION_ID plus inherited OLLIE_* variables."),
|
|
Read(readSessionEnv),
|
|
),
|
|
|
|
FileNode("paused", 0444,
|
|
Doc("Session pause status. Returns 'true' or 'false'."),
|
|
Read(readSessionPaused),
|
|
),
|
|
|
|
FileNode("connected", 0444,
|
|
Doc("RPC connection status to tool server. Returns 'true' or 'false'."),
|
|
Read(readSessionConnected),
|
|
),
|
|
|
|
FileNode("ctl", 0666,
|
|
Doc("Session control. Write: 'kill' (destroy), 'save', 'invalidate', 'pause', 'resume'."),
|
|
Read(nopRead),
|
|
Write(writeSessionCtl),
|
|
),
|
|
|
|
FileNode("name", 0666,
|
|
Doc("Session display name. Read: current name. Write: rename session."),
|
|
Read(readSessionName),
|
|
Write(writeSessionName),
|
|
),
|
|
|
|
FileNode("id", 0444,
|
|
Doc("Immutable session ID (UUID). Does not change when session is renamed."),
|
|
Read(readSessionID),
|
|
),
|
|
|
|
FileNode("elevate", 0666,
|
|
Doc("Session-scoped elevation policy. Overrides global policy for this session."),
|
|
Read(readSessionElevate),
|
|
Write(writeSessionElevate),
|
|
),
|
|
|
|
DirNode("agent",
|
|
Doc("Agent management within this session."),
|
|
|
|
FileNode("new", 0666,
|
|
Doc("Create agent. Write key=value args (name=, profile=). Read returns agent ID."),
|
|
Request(requestAgentNew),
|
|
),
|
|
|
|
FileNode("idx", 0444,
|
|
Doc("Agent index. Format: name<tab>id<tab>cwd<tab>backend<tab>model per line."),
|
|
Read(readAgentIdx),
|
|
),
|
|
|
|
Each("{aname}", agentBindings,
|
|
Doc("Agent directory. Addressable by name or immutable ID."),
|
|
|
|
FileNode("prompt", 0666,
|
|
Doc("Submit prompt to agent. Write: prompt text (triggers async execution). Special: '/invalidate' refreshes model cache."),
|
|
Write(writeAgentPrompt),
|
|
),
|
|
|
|
FileNode("prompt.prev", 0444,
|
|
Doc("Previous prompt text. Returns the last prompt submitted to this agent."),
|
|
Read(readAgentPromptPrev),
|
|
),
|
|
|
|
FileNode("fifo.in", 0222,
|
|
Doc("Queue a prompt for later execution (FIFO input). Write-only."),
|
|
Write(writeAgentFifoIn),
|
|
),
|
|
|
|
FileNode("fifo.out", 0444,
|
|
Doc("Pop queued prompt (FIFO output). Returns empty if queue is empty."),
|
|
Read(readAgentFifoOut),
|
|
),
|
|
|
|
FileNode("chat", 0444,
|
|
Doc("Full conversation history. Supports streaming reads for real-time updates."),
|
|
Stat(statAgentChat),
|
|
Read(readAgentChat),
|
|
Stream(streamAgentChat),
|
|
),
|
|
|
|
FileNode("connection", 0444,
|
|
Doc("Tool server connection status. Returns 'connected' or error."),
|
|
Read(readAgentConnection),
|
|
),
|
|
|
|
FileNode("state", 0444,
|
|
Doc("Agent state: 'idle', 'thinking', or 'calling: <tool>'."),
|
|
Read(readAgentState),
|
|
),
|
|
|
|
FileNode("statewait", 0444,
|
|
Doc("Blocking read that returns when state changes. Returns new state value."),
|
|
Read(readAgentState),
|
|
BlockOnce(blockAgentStateWait),
|
|
),
|
|
|
|
FileNode("log", 0444,
|
|
Doc("Agent activity log (last 64KB). Debug output and internal events."),
|
|
Read(readAgentLog),
|
|
),
|
|
|
|
FileNode("offset", 0444,
|
|
Doc("Byte offset after last user prompt in chat. For extracting latest response."),
|
|
Read(readAgentOffset),
|
|
),
|
|
|
|
FileNode("plan", 0666,
|
|
Doc("Session-scoped markdown checklist. Survives context compaction. Read/write."),
|
|
Read(readAgentPlan),
|
|
Write(writeAgentPlan),
|
|
),
|
|
|
|
FileNode("cfg", 0666,
|
|
Doc("Agent configuration. Read: all settings. Write: key=value to update (name=, backend=, model=)."),
|
|
Read(readAgentCfg),
|
|
Write(writeAgentCfg),
|
|
),
|
|
|
|
FileNode("ctl", 0666,
|
|
Doc("Agent control. Write: 'kill', 'stop', 'compact', 'clear', 'model <name>', 'cwd <path>'."),
|
|
Write(writeAgentCtl),
|
|
),
|
|
|
|
FileNode("cwd", 0666,
|
|
Doc("Agent working directory. Read: current path. Write: change directory."),
|
|
Read(readAgentCwd),
|
|
Write(writeAgentCwd),
|
|
),
|
|
|
|
FileNode("usage", 0444,
|
|
Doc("Token usage statistics for this agent."),
|
|
Read(readAgentUsage),
|
|
),
|
|
|
|
FileNode("cost", 0444,
|
|
Doc("Estimated API cost for this agent session."),
|
|
Read(readAgentCost),
|
|
),
|
|
|
|
FileNode("ctxsz", 0444,
|
|
Doc("Current context window size (tokens used / max)."),
|
|
Read(readAgentCtxsz),
|
|
),
|
|
|
|
FileNode("models", 0444,
|
|
Doc("Available models for this agent's backend."),
|
|
Read(readAgentModels),
|
|
),
|
|
|
|
FileNode("systemprompt", 0444,
|
|
Doc("Fully rendered system prompt for this agent."),
|
|
Read(readAgentSystemprompt),
|
|
),
|
|
|
|
FileNode("context", 0444,
|
|
Doc("Full context window as JSON lines (one message per line)."),
|
|
Read(readAgentContext),
|
|
),
|
|
|
|
FileNode("tail", 0555,
|
|
Doc("Executable shell script: tail -f chat. For convenience."),
|
|
Read(readAgentTail),
|
|
),
|
|
|
|
FileNode("tools", 0666,
|
|
Doc("Tool management. Read: loaded tools. Write: tool name to load."),
|
|
Read(readAgentTools),
|
|
Write(writeAgentTools),
|
|
),
|
|
|
|
FileNode("name", 0666,
|
|
Doc("Agent display name. Read: current name. Write: rename agent."),
|
|
Read(readAgentName),
|
|
Write(writeAgentName),
|
|
),
|
|
|
|
FileNode("id", 0444,
|
|
Doc("Immutable agent ID. Does not change when agent is renamed."),
|
|
Read(readAgentID),
|
|
),
|
|
|
|
DirNode("proc",
|
|
Doc("Detached background processes started by this agent."),
|
|
|
|
Each("{pid}", processBindings,
|
|
Doc("Process output buffer. Read: output. Remove: dismiss after completion."),
|
|
Bind(BindData),
|
|
Read(readProcess),
|
|
Remove(removeProcess),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
)
|