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

302 lines
8.5 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 (
"context"
"ollie/cmd/olliesrv/internal/agent"
"ollie/cmd/olliesrv/internal/bypass"
"ollie/fsedsl"
olog "ollie/log"
)
// 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 {
context.Context // per-operation cancellation deadline
// Root-level (always available).
Root *Tree
RootState *RootState
Log *olog.Logger
Models *ModelCache
Bypass *bypass.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
// 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]
Binding = fsedsl.Binding[HandlerCtx]
Tree = fsedsl.Tree
File = fsedsl.File
SyntheticFileInfo = fsedsl.SyntheticFileInfo
)
// 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("/",
GID("agent"),
FileNode("backends", 0444,
Doc("Available backend names, one per line (ollama, openai, anthropic, etc.)"),
Read(readBackends),
),
FileNode("help", 0444,
Doc("Filesystem reference (this output). Generated from spec."),
Read(readHelp),
),
FileNode("models", 0444,
Doc("Available models across all backends. Format: backend<tab>model per line."),
Read(readModels),
),
FileNode("agents", 0444,
Doc("Available agent profile names (from agents/*.json), one per line"),
Read(readAgents),
),
FileNode("tools", 0444,
Doc("Available tools and their descriptions. Format: name<spaces>description per line."),
Read(readTools),
),
FileNode("ctl", 0666,
Doc("Server control. Write: 'invalidate' (refresh caches), 'kill' (shutdown server)"),
Request(requestRootCtl),
),
FileNode("eventwait", 0444,
Doc("Blocking read for server events. Returns next event as 'topic payload'."),
Read(readEventwait),
BlockOnce(blockEventwait),
),
FileNode("generate", 0666,
Doc("One-shot LLM generation. Write JSON {prompt,...} or plain text. Read returns result."),
Request(requestGenerate),
),
FileNode("aliases", 0444,
Doc("Alias table for ID-based lookups. Format: id<tab>path per line."),
Read(readAliases),
),
DirNode("bypass",
Doc("Bypass broker for sandboxed command approval"),
FileNode("policy", 0666,
Doc("Global bypass policy. Read: current policy. Write: key=value to update."),
Read(readBypassPolicy),
Write(writeBypassPolicy),
),
Each("{reqid}", bypassBindings,
Doc("Pending bypass request. Read: request summary. Write: 'approve', 'deny', or 'persist'."),
Bind(BindData),
Read(readBypassRequest),
Write(writeBypassRequest),
),
),
DirNode("session",
Doc("Session management. Contains per-session subdirectories."),
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: kill, save, invalidate, pause, resume."),
Request(requestSessionCtl),
),
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("bypass", 0666,
Doc("Session-scoped bypass policy. Overrides global policy for this session."),
Read(readSessionBypass),
Write(writeSessionBypass),
),
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("fifo", 0666,
Doc("Prompt queue. Write: enqueue. Read: dequeue (empty if none)."),
Write(writeAgentFifoIn),
Read(readAgentFifoOut),
),
FileNode("chat", 0444,
Doc("Filtered text presentation of conversation. Strips markers and source fences for terminal display."),
Stat(statAgentChat),
Read(readAgentChatText),
Stream(streamAgentChatText),
),
FileNode("chat.raw", 0444,
Doc("Full conversation with block markers and fences. Used by GUIs."),
Stat(statAgentChat),
Read(readAgentChat),
Stream(streamAgentChat),
),
FileNode("statewait", 0444,
Doc("Blocking read that returns when state changes. Returns new state value."),
Read(func(ctx HandlerCtx) ([]byte, error) {
return []byte(ctx.Agent.State() + "\n"), nil
}),
BlockOnce(blockAgentStateWait),
),
FileNode("log", 0444,
Doc("Agent activity log (last 64KB). Debug output and internal events."),
Read(readAgentLog),
),
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 (rdwr). Write command, read response. Commands: stop, compact, clear, inject, agent, model, cwd, name."),
Request(requestAgentCtl),
),
FileNode("stats", 0444,
Doc("Agent statistics. Key=value lines: usage=, cost=, ctxsz=."),
Read(readAgentStats),
),
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),
),
),
),
),
),
),
)