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/agent" "ollie/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: backendmodel 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: namedescription 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: idpath 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: namestatecwdbackendmodelagentNameid 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: nameidcwdbackendmodel 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), ), ), ), ), ), ), )