Commit Graph

75 Commits

Author SHA1 Message Date
Ollie Agent ee7426d628 rename fsedsl → virtfs
The package has outgrown its original 'eDSL' framing. It's a virtual
filesystem library. Rename to match.
2026-08-11 17:21:44 +02:00
Ollie Agent d71ff80993 olliesrv/fs: embed *session.Session in SessionNode, kill delegation
SessionNode now embeds *session.Session instead of wrapping it.
All delegation methods (ID(), Name(), Ctx(), Pause(), etc.) are removed.
Handlers access session fields/methods directly through promotion.

Moved pause/resume event publishing into session.Session itself since
the session package already owns PublishEvent.

Eliminated all s.Session.X stutter from handler code.
2026-08-11 17:19:10 +02:00
Ollie Agent 1474eeaaed olliesrv/fs: eliminate Hctx adapter struct
Handlers now accept their actual dependencies directly:
- Root handlers: ModelCache, Broker, context.Context, etc.
- Session handlers: *SessionNode, removeFn, renameFn
- Agent handlers: *agent.Agent, *AgentLog, *SessionNode

Ctl dispatch tables are now per-instance closures built at binding
time rather than package-level vars with generic Hctx.

Also inlined the intermediate internalBinding type — adapt functions
now build fsedsl.Binding directly from raw data.

Removed: Hctx struct, rdwrHandler type, rdwrDispatch func,
bypassBindings helper, processBindings helper, internalBinding type.
2026-08-11 17:15:01 +02:00
Ollie Agent 95eb402c68 olliesrv/fs: migrate to closure-capture fsedsl API
Converted the entire 9P namespace spec from progressive Hctx population
to closure capture. Handlers are now closures that capture their
dependencies (session, agent, etc.) at binding time.

Hctx remains as a transitional adapter — handlers still receive it,
but it's constructed per-call from captured state rather than threaded
through the tree. Will be removed in a follow-up once handlers are
converted to use captured state directly.
2026-08-11 17:04:40 +02:00
Ollie Agent 8a5883fcb2 fs: rename HandlerCtx → Hctx, SessionNode.Core → Session 2026-08-11 16:30:05 +02:00
Ollie Agent de5456e4aa toolsrv: fix per-agent tool registry to actually work
The registry was keyed by agent ID but read it from a shared env map
(st.env["OLLIE_UNAME"]) that all agents overwrote — making it
effectively per-session with a race condition.

Fix: pass agent ID explicitly through the protocol at every call site.

- ctl protocol: 'load <agentID> <tool>', 'unload <agentID> <tool>'
- tools file: rdwr (Request) — write agent ID, read filtered list
- proc/new payload: 'agent=<id>' field required
- Client: SetAgentID() stores identity, included in all operations
- Empty agent ID is a hard error everywhere
- Setting OLLIE_UNAME via env ctl is blocked (prevents reintroduction)
2026-08-11 16:26:12 +02:00
Ollie Agent 7dfd769c25 server: fix BlockingReadMode truncation, separate stream/blocking paths
BlockingReadMode had the same 512-byte truncation bug: it returned EOF
on offset>0 before the full content was delivered. Now caches content
on the fid like normal reads.

StreamMode is separated into its own path since it has fundamentally
different semantics (each Tread blocks for the next chunk, no caching).
2026-08-11 11:45:47 +02:00
Ollie Agent cfc526b0d7 server: cache read content on fid for offset-based paging
In 9P, clients issue multiple Treads at increasing offsets to read
files larger than the initial buffer (io.ReadAll starts at 512 bytes).
The server was calling entry.Read() on every Tread, which for Request
files cleared the result after the first call, causing truncation.

Fix: cache the Read() result on the fid (readCache). Subsequent Treads
at higher offsets serve from the cache via readSlice. The cache is
cleared on write (for rdwr files) so the next request-response cycle
gets fresh data. This mirrors how dirCache already works for directories.
2026-08-11 11:44:30 +02:00
Ollie Agent 686a560e84 agent: toolsrv is sole owner of tool state
Remove all cached tool state from agent:
- Remove toolRegistryRevision from Runtime
- Remove wireToolsChanged (OnToolsChanged callback)
- Remove toolsNeedRefresh (revision-based cache invalidation)
- Remove refreshToolListing (redundant with RefreshTools)

Tools are now fetched fresh from toolsrv at the start of every turn.
The preamble tools section is also rebuilt at turn start. No local
caching means tool_load/unload take effect immediately on the next
turn and /tools always shows the live state from toolsrv.
2026-08-11 10:59:31 +02:00
Ollie Agent 6b4e97aaf7 fix: tool_load and tools use agent's ToolServer consistently
tool_load was loading on the session connection but tools listed from
the agent connection. Now both use ctx.Agent.ToolServer() so loads are
immediately visible. writeAgentTools also calls RefreshTools after load.
2026-08-11 10:50:38 +02:00
Ollie Agent c3afe1453d ctl: add /i alias for inject 2026-08-11 10:47:58 +02:00
Ollie Agent 573c9f4462 cleanup: fix structural inconsistencies, remove dead code
- backend/openai: fix DefaultModel() dead 'anthropic' case, default to gpt-4o
- agent: SetCWD no longer sets stale envBlock first, uses runtime.GOOS
- backend/kiro: ContextLength cache now checks ctxModel consistently
- agent: AgentConfigPath uses agentsDir as the only search location
- fs: ctl 'tools' handler delegates to readAgentTools (dedup)
- agent: remove redundant TurnCtx field assignments in executeTurn
- agent: remove spending cap feature entirely (OLLIE_MAX_SESSION_COST)
- agent: inline stripColdResults into stripCold, remove wrapper
- agent: remove orphaned doc comment at end of text_parse.go
- agent: remove dead Prompt.MarshalJSON
- agent: cloneMessages now copies ID, Reasoning, ContentBlocks
- agent: fix newHistory comment (Session → History)
- agent: fix orphaned CompactionModel godoc on ListModels
- agent: toolCalls only captured from Done event (fix contract)
- agent: HandleCommand uses lib9p client instead of shelling out
2026-08-11 10:46:38 +02:00
Ollie Agent 5740333ad2 prompt: instruct agent to make parallel tool calls
Agents now know to batch independent reads and writes in a single
turn rather than serializing them across multiple turns.
2026-08-11 10:24:53 +02:00
Ollie Agent 391465be77 doc+fix: background procs force timeout=0, update docs
- Background procs get timeout=0 (no deadline) set by toolsrv at
  the execution layer — not injected as an arg from olliesrv.
  Foreground default remains 30s. User can still override via args.
- Timeout logic simplified: caller sets default, ExecuteTool honors it.
- System prompt: clarify background has no timeout, uses 'stop' not 'kill'
- architecture.md: document lifecycle (connection-based ownership)
- evolution.md: update interrupt section with streaming, lifecycle, id= format
- README.md: add parallel execution to capabilities table
2026-08-11 10:18:30 +02:00
Ollie Agent 0d1eeaa46b fix: background process lifecycle, streaming output, and connection deadlocks
- Timeout: timeout=0 means no deadline (was defaulting to 30s)
- Signal: send to process group (-pgid) not just process; SIGTERM no longer
  cancels context (only SIGKILL does); cmd.Cancel sends SIGTERM with 5s WaitDelay
- Streaming: background procs stream output in real-time via procWriter;
  shell tool no longer buffers all output into a bash variable
- Proc tree: olliesrv exposes proc/{id}/out, proc/{id}/ctl, proc/{id}/status
  as proper 9P directory (was broken flat file)
- Connection: proc handlers dial fresh toolsrv conn per request via
  Session.DialToolServer() to avoid deadlocking the agent's blocked conn
- Stat format: key=value (exited=true, exit_code=N, id=N) matching client parser
- GC: procs auto-removed 10min after LastRead (exited procs only)
- Rename: PID -> ID throughout (synthetic, not OS PID)
- Ctl commands: term (SIGTERM), kill (SIGKILL), signal <n>, dismiss
- System prompt: correct ollie-9p commands for proc management
2026-08-11 09:44:08 +02:00
Ollie Agent 945ce69984 agent: add tests for dispatch flag injection and background helpers 2026-08-11 08:28:54 +02:00
Ollie Agent a098fbff0c agent: inject dispatch flags (bypass, timeout, sandbox, background) into all tool schemas
Dispatch-level flags are now injected into every tool's JSON schema
at runtime via injectDispatchFlags(). No need to declare them in
individual .meta files — they're universal.

Removed redundant declarations from shell.meta (now injected globally).
2026-08-11 08:26:14 +02:00
Ollie Agent aa270525a5 agent: background process interrupts — auto-inject output at safe points
When a tool call includes "background": true, it executes via
proc/new.bg and returns immediately with a PID in a
<system-proc-background> tag. The agent tracks active background
processes and injects their output as <system-proc-interrupt> blocks
alongside subsequent tool results.

The model sees background updates without polling. It can react to
build failures, log events, etc. naturally. Kill via PID when done.

Implementation:
- toolsrv client: CallToolBackground (uses proc/new.bg as rdwr)
- toolsrv: proc/new.bg upgraded from write-only to request-response
- agent: bgTracker collects last 20 lines of output per proc
- agent loop: injects interrupts after execToolCalls, before h.update
- system prompt: documents the background mechanism and rules
2026-08-11 08:23:00 +02:00
Ollie Agent 2870da593d agent: default unset scope to global (safe by default)
Only tools that explicitly declare scope="write" get path-based
parallelism. Unset scope is now global (full barrier), avoiding the
"path is a lie" problem where a tool has a path arg but modifies
other files (e.g., lsp_rename).

Tools must opt in to parallelism:
- scope=read: never conflicts
- scope=write: conflicts on same path only
- scope=global (or unset): serialization barrier

Added scope=write to file_edit.meta and file_write.meta.
2026-08-11 07:57:22 +02:00
Ollie Agent 586edc1332 agent: replace ReadOnly with explicit scope field (read/write/global)
Rename ToolInfo.ReadOnly bool → ToolInfo.Scope string with three values:
- "read"  — path-scoped read, never conflicts (always parallel)
- "write" — path-scoped write, conflicts on same file path only
- "global" — full serialization barrier, runs alone

Tools declare scope in their .meta file. If unset, inferred from path
arg presence (write if path exists, global otherwise).

This correctly classifies lsp_rename as global (cross-file workspace
edits) despite having a path argument. The path arg in lsp_rename is
a symbol coordinate, not a resource scope.

All .meta files updated: readOnly:true → scope:read,
readOnly:false → scope:global, lsp_rename gets scope:global.
2026-08-11 07:56:00 +02:00
Ollie Agent 3dccb36e82 agent: resource-based parallel tool execution
Replace binary ReadOnly/not batching with path-based conflict detection.
Tool calls that operate on different file paths now execute in parallel,
while same-path writes and shell (global barrier) serialize.

Conflict rules:
- ReadOnly tools: never conflict (unchanged from before)
- Write tools with path arg: conflict only on same path
- Shell / unknown (no path): global barrier, runs alone

This parallelizes the common refactoring case where the model edits
multiple files in one turn (e.g., 5 file_edits on different files
complete in 1 round-trip instead of 5).

Safety argument:
- LLM API is turn-based: model reads files first, plans edits based
  on known content, emits writes in a subsequent turn
- file_edit carries old_string context: self-contained per-file
- Same-path conflicts serialize (detected via extractFilePath)
- Shell is conservative: always a full serialization barrier
2026-08-11 07:48:53 +02:00
Ollie Agent df74c3fa60 toolsrv: restructure into internal packages, fix process lifecycle
- Move toolsrv logic into internal/{fs,exec,registry} packages to match
  olliesrv's structure. server.go (9P protocol) stays at top level.

- Deduplicate parseKV/parsePayload: remove dead code from old spec9p.go,
  single implementation in internal/fs/parse.go.

- Add structured logging via log/slog (replaces fmt.Fprintf to stderr).

- Standardize qid path hashing to fnv.New64a (matches olliesrv).

- Fix toolsrv process leaks:
  - Add Pdeathsig to local spawn so toolsrv dies when olliesrv exits.
  - Add idle timeout (30s with no connections after first client seen).
  - ProcessKeeper.Dial() kills old process before respawning.
2026-08-11 07:21:14 +02:00
Levi Neely a0315a558a refactor: move toolsrv spawn/process management to toolclient
Move spawn.go from toolsrv/ to cmd/olliesrv/internal/toolclient/:
- Process, ProcessKeeper, Spawn, SpawnRemote now in toolclient package
- toolsrv/ now only contains client code (Dial, Conn, etc.)

This clarifies the architecture:
- toolsrv/ = client SDK for connecting to toolsrv
- cmd/toolsrv/ = the toolsrv server
- cmd/olliesrv/internal/toolclient/ = olliesrv's toolsrv process management

Removed ProcessKeeper tests from toolsrv integration tests since they
now belong to toolclient.
2026-08-10 20:49:22 +02:00
Levi Neely d292f2aa59 feat: make tool_load a built-in tool
tool_load must be handled specially by the agent runtime because tools
run inside toolsrv's sandbox and cannot load other tools into
themselves. The runtime now intercepts tool_load calls and directly
invokes ToolServer.LoadTool().

This is the only built-in tool - all others are external scripts.
2026-08-10 20:37:37 +02:00
Levi Neely be4f6aaf09 feat: add tools file to agent namespace
Add /session/{sid}/agent/{aid}/tools file:
- Read: list loaded tools for this agent
- Write: load a tool by name

Also add missing tool_load to justfile install.
2026-08-10 20:29:04 +02:00
Levi Neely 5815c3e0c9 fix: add nil check for runtime in SetSessionEnv 2026-08-10 20:26:14 +02:00
Levi Neely 1fc051e3bf refactor: move olliesrv and toolsrv packages to cmd/*/internal/
Move server-only packages under their respective cmd directories:

olliesrv:
- agent/ -> cmd/olliesrv/internal/agent/
- backend/ -> cmd/olliesrv/internal/backend/
- bypass/ -> cmd/olliesrv/internal/bypass/
- fs/ -> cmd/olliesrv/internal/fs/
- prompts/ -> cmd/olliesrv/internal/prompts/
- session/ -> cmd/olliesrv/internal/session/

toolsrv:
- Server-only code (exec9p, fs9p, server9p, spec9p, auth9p) -> cmd/toolsrv/
- sandbox/ -> cmd/toolsrv/internal/sandbox/
- Keep shared client code (client9p, spawn, registry, meta) in toolsrv/
- Add toolsrv/types.go for shared types (ToolResult, ToolResultContent)

This enforces package boundaries - code in cmd/*/internal/ cannot be
imported by external packages, while shared code remains importable.
2026-08-10 20:16:44 +02:00
Ollie Agent 74635b8e00 refactor: items 8-10, 12, 13 from code review
- Move parseRetryAfter to backend.go (where it's called)
- Extract paths.RuntimeDir() replacing 3 divergent XDG_RUNTIME_DIR impls
- Rename CodeWhispererBackend → KiroBackend, NewCodeWhisperer → NewKiro
- Export RPCRequest/RPCError/OutputNotification from toolsrv/rpcwire.go
- Factor tools/lsp/cmdutil: Run() and RunCustom() for 7 LSP binaries
2026-08-09 11:32:31 +02:00
Ollie Agent c7aea0db59 rename elevate→bypass throughout
The sandbox escape mechanism is a bypass, not privilege elevation.
The old name caused the agent to confuse it with sudo.

- elevate/ → bypass/ (package, types, tests)
- elevate_notify.go → bypass_notify.go
- Namespace: /elevate → /bypass, session/*/elevate → session/*/bypass
- Tool arg: "elevated" → "bypass"
- Env: OLLIE_ELEVATE_SOCKET → OLLIE_BYPASS_SOCKET
- File: elevate-policy.yaml → bypass-policy.yaml
- All docs, prompts, and scripts updated
2026-08-09 02:22:39 +02:00
Ollie Agent 8152dec075 all: delete dead code, tighten API surface
toolsrv:
- Delete WithOnClose, WithOnEnvSet, SetOnExit (dead Options)
- Delete ListDetached, ListDetachedInfo (only ListDetachedRaw used)
- Delete Execute (dead wrapper)
- Delete CWD, ToolRegistry, SessionID, InjectContent (dead accessors)
- Delete ExecuteInSandbox, ExecuteElevated (zero external callers)
- Delete Summaries, RefreshLoaded from Registry (dead)
- Delete DialSSH (alias for RemoteDial)
- Simplify Dial: takes socket string directly, kill Addr/LocalAddr/SSHAddr
- Rename Server receiver e → s
- Unexport context import (no longer needed in tools.go)

session:
- Delete SaveFuncs, AgentAt, AgentCount, NextUncheckedStep
- Delete SetDisallowTools, SaveAllSessions

env:
- Delete Set, Get, All, Format, managed (dead exports)
- Unexport LoadFile → loadFile

fs:
- Delete HandlerCtx.ToolReg, Skills, SaveFn (never read by handlers)
- Delete Config.MkdirAll, Config.SkillsRegistry (dead plumbing)
- Delete AgentLog: Plan, SetPlan, PrevPrompt, Mu, LogInfo, Remote methods
- Delete unused type aliases (NodeOption, FileTree, FileConfig, etc.)

backend:
- Delete ClosePool, PoolStats (dead), New() (dead)
- Delete kiroTokenExpiringSoon standalone func (dead)
- Unexport SharedTransport/SharedClient → sharedTransport/sharedClient

skills:
- Delete entire package (zero importers; feature works via tool scripts)

agent:
- Delete BroadcastChange (dead duplicate of notifyChange)
- Delete HasHistory, ToolCallCount, CompactionModel, SetCompactionModel
- Unexport SetReply, BuildPreamble, DefaultPromptsDir
2026-08-08 15:51:13 +02:00
Ollie Agent 3b83b9d373 all: delete dead interfaces, kill globalRootState
- agent: delete 'state' interface (one implementation: *History). run()
  now takes *History directly.
- agent: delete 'backendNamer' interface. resolveCompactionModel takes
  backend.Backend directly.
- fs: delete Shutdown/InterruptAll/Lookup wrappers that took *Tree they
  never used. Callers (olliesrv) now call session.* directly.
- fs: delete globalRootState. sessionBindings uses ctx.Root.Data.(*RootState)
  via the HandlerCtx that was always available.
2026-08-08 14:58:47 +02:00
Ollie Agent 1372a6b55b Remove OLLIE_ELEVATE_SOCKET from config: derived from XDG_RUNTIME_DIR convention
- Removed from env.go managed list and defaults map
- main.go and shell.go derive path from XDG_RUNTIME_DIR directly
- main.go sets OLLIE_ELEVATE_SOCKET in process env for subprocesses
2026-08-05 19:43:52 +02:00
Levi Neely 593584a673 Move mount/ package to ollie-9p
FUSE mount is a client-side QoL feature, not a server concern.
Usage: ollie-9p mount <address> [mountpoint]
2026-08-04 16:38:44 +02:00
Levi Neely 66850e822f cleanup: remove dead code from olliesrv
- Remove unused GroupTable.Remove() method, rename Add to Populate
- Remove unused toolPrompt field from Server struct and Config
- Remove unused ModelCache type alias
- Remove unused toolsrv import
- Flatten if true {} blocks (debugging remnants)
- Convert fcallTypeName switch to map lookup
- Remove unused Tree.Unmount() method

-44 lines
2026-08-04 10:19:12 +02:00
Levi Neely 6d902ba7e9 9p: default log level to warn, not debug
The 9P logger was hardcoded to LevelDebug, causing all 9P operations
to print timing info to stdout. This blocked the server on terminal
output, hurting GUI responsiveness.

Now uses NewLogger('9p') which:
- Defaults to the sink's level (warn)
- Can be overridden with OLLIE_9P_LOG=debug

Also updates kde submodule with incremental session tree updates.
2026-08-03 19:52:07 +02:00
Levi Neely f83b1f0f89 olliesrv: fix double-fire on Request files opened ORDWR
For Request-mode files (session/new, complete, generate, route), the
write handler fires immediately during Twrite. On clunk, the server
was also flushing the (empty) writeBuf to Write(), causing the handler
to fire a second time. This created duplicate sessions.

Skip the clunk-time writeBuf flush for Request-mode files since they
already handle writes inline during Twrite.
2026-08-03 09:20:49 +02:00
Ollie Agent 9af742acd6 fs/lifecycle: fix nil MkdirAll panic blocking agent creation and autoload
The panic at fs/lifecycle.go:463 was a nil function pointer: fs.Config.MkdirAll
is a func field, not a method, and the struct literal in cmd/olliesrv/main.go
omitted it, defaulting to nil. When CreateAgent called
rs.cfg.MkdirAll(...), it panicked before ever reaching the autoload loop.

- Add MkdirAll: os.MkdirAll to fs.Config in main.go
- Spawn tool server in Create() so empty session/new sessions are
  operational, then reuse it in CreateAgent (else if sess.proc != nil)
- Wrap create agent error with fmt.Errorf for clearer messages
- Add panic stack trace to stderr for debugging
2026-08-02 21:16:12 +02:00
Ollie Agent 8fba664f11 cleanup / unify toolsrv bootstrap 2026-08-02 20:08:45 +02:00
Ollie Agent e25157ffcd multiplex toolsrv, wip zero-tool server 2026-08-02 19:18:39 +02:00
Ollie Agent bb152f401d create: set f.entry after creating files
After Tcreate on a file, the fid is open and should be usable for
read/write without a separate Topen. Set f.entry via root.Open()
after creation so subsequent reads/writes on the same fid work.
2026-08-02 16:23:08 +02:00
Ollie Agent b23785a4d9 Fix directory read: entry not required for dirs
Tree.Open() returns an error for directories (is a directory), so
f.entry stays nil after Topen on a dir. The entry == nil guard in
read() was firing before the isDir branch, breaking directory reads.
Moved the guard after the isDir check — directories use buildDirData
directly and never need entry.
2026-08-02 16:22:13 +02:00
Ollie Agent e1e03e50ae Inline handle into Start; rename Serve→Start, Shutdown→Kill
- handle() is gone — the dispatch + panic recovery is inlined into
  the per-request goroutine in Start(). Panic recovery is lifecycle,
  not protocol handling.
- Serve → Start: accepts a connection and runs the 9P loop
- Shutdown → Kill: stops accepting, cancels sessions, waits for
  goroutines to drain.
- 2 methods remain on *Server: Start, Kill. Server is purely lifecycle.
- All 9P protocol handlers are package-level functions.
2026-08-02 16:18:32 +02:00
Ollie Agent 447156ce13 Extract attach and open from *Server; pass groups and log explicitly
- attach() is now a function taking (*olog.Logger, *GroupTable, ...)
- open() is now a function taking (*fs.Tree, *GroupTable, ...)
- Both captured in the handler map via closures over s.sink, s.groups
- 3 methods remain on *Server: Serve, handle, Shutdown
2026-08-02 16:13:23 +02:00
Ollie Agent 3242f108f9 Store opened file on fid; read/write use f.entry instead of path re-resolution
- Added entry (fs.File) field to fid struct
- open() stores the result of rootTree.Open() on f.entry after
  permission check succeeds
- read() uses f.entry instead of calling root.Open(path[1:]) —
  fids survive renames because the File object references the
  tree entry by pointer, not by path
- write() uses f.entry for RequestRespMode writes — no longer
  needs root parameter at all; it's a pure connState+fid function
2026-08-02 16:11:57 +02:00
Ollie Agent 05e0aba155 Server owns the fid map: validate newfids, GC stale fids
- attach: reject if fc.Fid already in use (was silently overwriting)
- walk: reject if fc.Newfid already in use (both clone and full walk)
- open: validate fid path exists; delete stale fid if path is gone
- stat: validate fid path exists; delete stale fid if path is gone
- wstat: after rename, GC all fids on this connection that reference
  the old path tree (was previously updating paths — now just deletes)

The server fully owns the fid map. The client proposes fid numbers,
the server validates and accepts or rejects. Stale fids (pointing to
renamed/deleted paths) are garbage collected when detected.
2026-08-02 16:08:23 +02:00
Ollie Agent 8601562962 Extract GroupTable from Server; replace permission boolean dance with bitmask OR
- GroupTable is a standalone type with Add/Remove/IsMember, owns its
  own mutex and map. Server now holds groups *GroupTable.
- Removed AddGroup/RemoveGroup/InGroup from *Server (3 fewer methods).
- Permission check in open() now computes effective bits as a mask:
    bits := perm & 7                      // world bits
    if in group { bits |= (perm >> 3) & 7 }  // group bits
    if is owner { bits |= (perm >> 6) & 7 }  // owner bits
    if !hasPermBits(mode, bits) → denied
- No boolean accumulator, no tiered if/else, just bitmask OR.
- 6 remaining Server methods (down from 23).
2026-08-02 15:59:50 +02:00
Ollie Agent 0b4daf016a Replace checkPermBits error-return with hasPermBits bool
- hasPermBits uses a 4-entry mask array indexed by mode&3 to select
  the required permission bits: OREAD→4, OWRITE→2, ORDWR→6, OEXEC→1
- Zero branches, zero error paths — returns bits&mask == mask
- open() uses OR semantics: checks owner, group, world independently;
  any tier granting access is sufficient
- checkPerm removed entirely; permission logic is pure boolean math
2026-08-02 15:55:25 +02:00
Ollie Agent 325ad224be Extract checkPerm to package-level function, resolve perms in open
- checkPerm no longer needs *Server: it takes uid, perm, and inGroup
  directly. The caller (open) resolves uid/gid via fileOwnerGroup,
  perm via makeStat, and group membership via InGroup.
- checkPerm is now a pure function: (uname, mode, uid, perm, inGroup)
  → returns only on permissions, needs nothing from Server state.
- open is the remaining method that resolves these values.

server.go: 10 remaining Server methods.
2026-08-02 15:50:31 +02:00
Ollie Agent 8a13174b4f Extract 9 rootTree-only methods to package-level functions; replace switch with handler map
Converted walk, create, read, write, stat, clunk, remove, makeStat,
and buildDirData from *Server methods to functions taking *fs.Tree
as first parameter. Exactly the same pattern as fileOwnerGroup.

Also:
- Replaced the switch in handle() with a s.handler map, built once
  in New(). All 11 9P message types (Tversion through Tremove) now
  dispatch through the map.
- Extracted flush() as a package-level function (was inlined in switch)
- handle() is now: recover + map lookup. No inline cases, no switch.
- server.go: 802 lines, 10 remaining Server methods (down from 23)
- 20 package-level functions handle the actual protocol logic
2026-08-02 15:44:19 +02:00
Ollie Agent 54403a1ce2 Inline readFile/writeFile; remove unused FileTree alias
- readFile was dead code (defined but never called)
- writeFile was a 7-line helper called only once in clunk;
  inlined as direct s.rootTree.Open + e.Write
- FileTree type alias now unused after both deletions
2026-08-02 15:33:06 +02:00