302 lines
8.4 KiB
Go
302 lines
8.4 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/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: 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),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
)
|