Commit Graph

164 Commits

Author SHA1 Message Date
Levi Neely dcd119c853 virtfs: refactor Stream to accept (readFn, signalFn)
Same pattern as BlockOnce: framework handles the blocking loop.
readFn takes a base string, returns (data, nextBase, error).
signal fires when new data may be available.

Chat stream handlers now use Stream(a.ChatRead, a.ChatSignal).
StreamRaw retained for custom handlers.

streamChat() in support.go is now dead code (replaced by ChatRead).
2026-08-13 20:50:27 +02:00
Levi Neely 794673a5d0 virtfs: refactor BlockOnce to accept (readFn, signalFn)
BlockOnce now takes a value-reader and a signal source. The framework
handles the blocking loop: read → compare hash to base → if different
return → else wait on signal or timeout.

On timeout (ctx.Done), returns empty (not error) so clients re-open
cleanly.

BlockOnceRaw retained for queue-style handlers (bypass/pending,
proc/wait) that manage their own blocking.

EventValue adapter wraps a <-chan Event into BlockOnce-compatible
(readFn, signalFn) pair via a thin goroutine.
2026-08-13 20:44:50 +02:00
Levi Neely b5cb8c397d feed: BlockOnce with base init, ConsumeFeed via lib9p client
- BlockOnce handler initializes base to current hash on fresh open,
  then blocks until hash changes. Same pattern as statewait.
- ConsumeFeed is a plain function: dials 9P, reads feed in a loop
  (open → block → data → close → repeat), submits to agent.
- Context cancellation closes the 9P client, unblocking Read().
- Called as go ConsumeFeed(ctx, ag) from AddAgent and session resume.
2026-08-13 20:26:08 +02:00
Levi Neely e2695d6f50 feed: export Feed field, remove wrapper methods, use direct access everywhere 2026-08-13 20:09:28 +02:00
Levi Neely 1537e35685 feed: inline handlers, remove FeedBlockingRead wrapper 2026-08-13 20:06:32 +02:00
Levi Neely 721b49ffa2 feed: use BlockOnce for read side, dedup via base comparison
Feed is now read-write (0666):
- Write: store data, signal via notifyChange
- Read (BlockOnce): block until content hash differs from base

Dedup lives in the read layer (same pattern as statewait). The
internal consumer goroutine and external 9P clients both call
FeedBlockingRead — same codepath, same dedup logic.

Consumer started from AddAgent (if ctx available) and session resume.
2026-08-13 20:05:46 +02:00
Levi Neely dfacdfed94 agent: add feed file — change-detecting streaming input gate
Feed is a write-only file in the agent namespace. Writes are
deduplicated against the previous value; the internal consumer
(blocking on WaitChange/WatchFeed) only wakes when genuinely new
data arrives.

Wiring is external and source-agnostic:
  # human → observer (poll git):
  while :; do git diff HEAD; sleep 5; done | ollie-9p write .../feed
  # agent → observer (stream chat):
  ollie-9p read .../coder/chat | ollie-9p write .../observer/feed

Uses the agent's existing signalCh/notifyChange plumbing — no new
channel infrastructure. Consumer goroutine spawned at agent creation
(AddAgent) and session resume, tied to session context.
2026-08-13 20:02:06 +02:00
Levi Neely 5459e7fa2a fix: XDG default fallback in prompt resolver; deduplicate tool headers
- envLookup: provide XDG spec defaults (XDG_CONFIG_HOME, XDG_DATA_HOME,
  XDG_CACHE_HOME) when env vars are unset, so agent profile prompts
  resolve correctly on systems without explicit XDG vars.
- renderTools: skip generated '## name' header when the tool's prompt
  text already starts with a markdown header, eliminating duplicates.
2026-08-13 15:45:35 +02:00
Levi Neely 6a19c135eb prompts: drop tool path from system prompt, use absolute path in copilot example 2026-08-13 12:04:52 +02:00
Levi Neely 83b0b59a0a prompts: remove /home/user path examples that bias tilde expansion
- file_grep.meta: replace /home/user/project with /abs/path
- system_prompt.md: use $XDG_CONFIG_HOME instead of ~
- agent-copilot.md: use relative path in code block example
2026-08-13 12:02:27 +02:00
Levi Neely 1ac50d97e1 agent: drain FIFO after interrupted/failed turns
Fix holes where queued prompts were orphaned:
- After turn loop exits (interrupt, toolsrv unavailable), drain remaining FIFO items
- On panic recovery, pop next FIFO item and re-submit in a new goroutine
- fifo file write triggers Submit when agent is idle
2026-08-13 11:59:11 +02:00
Levi Neely 9b98a958cc fix: treat meta cmd as shell command string, prepend tools dir to PATH
- ResolveTool now returns cmd field as-is instead of trying to resolve
  it as a binary path. This allows meta-only tools to use full shell
  command strings with env vars, pipes, and subshells.

- Prepend ~/.config/ollie/tools to PATH when executing tools so cmd
  fields can reference other tools by name.
2026-08-12 18:44:16 +02:00
Levi Neely 23f3a6092b refactor: eliminate TurnCtx, make loop functions Agent methods
- Delete TurnCtx struct (was progressively populated with optional nil fields)
- Convert run(), streamResponse(), execToolCalls(), execBatch(), execOne(),
  trackErrors(), retryCountdown() to Agent methods
- Extract autoCompact() and popInject() as Agent methods
- turn.go now installs a turn-scoped output handler wrapper instead of
  building TurnCtx closures
- context.Context is now passed as plain context.Context, not smuggled
  with unrelated state

Add history compaction tests:
- TestBuildCompactedHistory_OrphanedToolWalkback: verifies walk-back logic
  prevents orphaned tool messages at hot zone boundary
- TestBuildCompactedHistory_NoOrphanAtBoundary: verifies tool messages
  always have preceding assistant with tool_calls
2026-08-12 18:06:19 +02:00
Levi Neely 0a4149218a fix: set agent ID on toolsrv connection after resume 2026-08-12 17:10:22 +02:00
Levi Neely bc7f51a9dc refactor: push proc completion from toolsrv to agent prompt
- toolsrv pushes <system-proc-complete> to agent prompt when bg proc exits
- Remove bgTracker and CollectInterrupts from olliesrv
- Add Cmd, AgentID, SessionID fields to Proc
- Add OnProcExit callback to State
- Add command field to Proc.Stat() output
2026-08-12 16:39:43 +02:00
Levi Neely 6eb3bf960b refactor: query toolsrv directly for proc list
- ListProcs() now queries toolsrv directly instead of local tracker
- Remove ownership verification from SignalProc/ProcOutput/DismissProc
- Clean up stale procs from local tracker when they disappear from toolsrv
- Document local tracker quirk in TODO.md for future cleanup
2026-08-12 16:06:22 +02:00
Levi Neely 1d5e9c4f8d fix: background proc output capture and status display
- Detach background proc context from request context so processes survive
  after the 9P request returns (fixes premature SIGTERM)
- Add /proc/list file to toolsrv showing all procs with state
- Show status (running/exited (N)) in agent proc list
- Keep exited procs in tracker until explicitly dismissed
- Skip 'list' entry in ListDetachedRaw
2026-08-12 14:10:08 +02:00
Levi Neely 85435447bf fix: clean shutdown without connection errors
- Use read deadline + context check instead of forcibly closing connections
- ReadBypassPending now takes context and exits cleanly on cancellation
- Remove forced conn.Close() loop from Kill() - context cancellation suffices
2026-08-12 12:40:40 +02:00
Levi Neely 9057d36f05 docs: clarify tool usage in system prompt, add cancellation flow doc
- Explicitly tell model not to invoke tool scripts via filesystem path
- Add concrete examples of native tool calls vs shell
- Document context cancellation flow from interrupt to process kill
2026-08-12 12:20:34 +02:00
Levi Neely aedfdec564 fix: set agent ID on tool server after /agent switch
SwitchProfile creates a new tool server connection but wasn't calling
SetAgentID on it, causing 'agent ID required' errors when listing tools.
2026-08-12 11:08:37 +02:00
Levi Neely edd5ade471 refactor: simplify bypass - toolsrv is the broker, olliesrv is just a client
- Remove olliesrv's bypass broker machinery (pendingCh, EvaluateRequest, etc)
- Session bypass loop now just reads from toolsrv's bypass/pending and notifies
- Notification handler writes directly to toolsrv's bypass/resolve (fire and forget)
- Remove bypass/ directory from olliesrv's 9P namespace
- Remove session/*/bypass file (policy can be added back to toolsrv later if needed)

The flow is now:
1. toolsrv blocks tool execution, exposes request via bypass/pending
2. olliesrv reads from toolsrv, shows D-Bus notification
3. User clicks approve/deny, olliesrv writes to toolsrv's bypass/resolve
4. toolsrv unblocks and executes (or denies)
2026-08-12 11:01:29 +02:00
Levi Neely 46bd0dd999 fix: toolsrv 9P server now handles BlockOnce/Stream reads correctly
- Added BlockingReadMode() and StreamMode() handling to toolsrv's handleRead()
- Made NextPending() context-aware in both toolsrv and olliesrv bypass packages
- This fixes the bypass approval flow via 9P - reads now properly block until
  a request is available and respect context cancellation
2026-08-12 10:49:08 +02:00
Levi Neely d59f49ee54 feat: context cancellation for tool calls
Wire context cancellation through the toolsrv 9P server so that
canceling a CallTool context properly terminates the running tool.

Changes:
- virtfs: Add Close() to File interface with CloseFn for Request handlers
- toolsrv/p9.go: Handle requests concurrently to allow Tclunk during
  blocking Twrite; call entry.Close() in handleClunk
- toolsrv/internal/server/proc.go: Check ctx.Done() while waiting for
  tool completion in NewProc
- Integration tests for context cancellation chain

The cancellation chain: client cancels ctx → fid.Close() → Tclunk →
handleClunk calls entry.Close() → closeFn cancels reqCtx → NewProc
sees ctx.Done() and returns ctx.Err()
2026-08-12 10:00:44 +02:00
Levi Neely 31681cb4e7 Add context cancellation to bypass.Submit
bypass.Submit now takes a context and returns early when cancelled.
This allows the stop command to properly cancel pending bypass requests
that haven't been approved or denied yet.

The full context chain is now:
1. stop command -> Interrupt -> cancel actCtx
2. actCtx cancellation -> closes CallTool fid
3. toolsrv proc/new sees closed fid -> propagates ctx cancellation
4. ExecuteTool -> executeBypassDirect -> bypass.Submit
5. bypass.Submit returns ctx.Err() when context is cancelled
2026-08-12 09:32:21 +02:00
Levi Neely 5dc7cdc2ea Make Request() always context-aware
Remove RequestCtx variant - all Request handlers now take context.
This is required for proper cancellation of blocking operations
like tool execution when the client disconnects or stop is called.
2026-08-12 09:30:39 +02:00
Levi Neely 861cb47d13 Add RequestCtx for context-aware request handlers
- Add RequestCtx field to FsNodeDecl for handlers that need context
- Add RequestCtx() helper function in virtfs/decl.go
- Update builder.go to handle RequestCtx in validation and Open
- Use RequestCtx in toolsrv proc/new and proc/new.bg handlers

This allows the context to be cancelled when the 9P fid is closed,
enabling proper stop/interrupt of running tool calls.
2026-08-12 09:26:02 +02:00
Levi Neely 3a68b0be53 Fix bypass broker and stop command
- Wire BypassBroker into fs.Config before creating root tree
- Move defer bypassBroker.Close() outside block scope (was closing immediately)
- Rewrite /bypass/ FS nodes to match toolsrv's expected interface:
  - bypass/pending: blocking read returning JSON {id,cmd,cwd,env}
  - bypass/resolve: write JSON {id,approved,error}
- Add NextPending() method to broker with channel-based blocking
- Add JSON tags to Request struct for proper serialization
- Start bypass loop for restored sessions (was only for new sessions)
- Add context cancellation support to CallTool (closes fid on cancel)
2026-08-12 09:16:59 +02:00
Levi Neely 9bb3cd76b8 Remove sudo mechanism from bypass system
The sudo credential broker was never functional and added complexity
without value. This removes:

- Sudo field from bypass Request structs (broker, client, toolsrv)
- Sudo parameter from EvaluateRequest interface and implementations
- Sudo/ResetsCounter fields from MetaFile and Variant structs
- sudo: true from system_logs.meta variants
- All sudo documentation from writing-tools.md, tool-registry.md,
  core.md, evolution.md, and misc.md

Bypass remains fully functional for sandbox escapes. Tools that need
elevated privileges should handle that internally or be run manually.
2026-08-12 08:58:41 +02:00
Levi Neely 62a7d0d13f bypass: remove socket-based execution (dead code)
The old bypass broker used a Unix socket to receive requests and
execute commands locally. This was replaced by the 9P-based flow
where toolsrv executes commands after receiving approval from
olliesrv.

Removed:
- acceptLoop, handleConn, executeAndStream, executeWithSudo
- sendFrame, indexOf, socketDir helpers
- SocketPath and Credential config options
- Frame constants (FrameData, FrameExit)
- net.Conn field on Request

The broker now only handles:
- Policy management (global + per-session)
- Request evaluation (EvaluateRequest)
- Rate limiting
- User notification callbacks

Execution happens in toolsrv, not olliesrv.
2026-08-12 08:51:19 +02:00
Levi Neely 8bb5c098cc bypass: route approval through 9P instead of Unix socket
This refactors the bypass (sandbox escape) mechanism to work with remote
toolsrv deployments. Previously, bypass used a Unix socket which only
works when toolsrv runs locally. Now:

1. toolsrv exposes bypass/{pending,resolve} 9P files
   - pending: blocking read returns next bypass request as JSON
   - resolve: write JSON {id, approved, error} to complete request

2. olliesrv reads bypass/pending in a loop per session
   - Evaluates requests through the existing bypass broker
   - Policy check, rate limiting, user notification all stay in olliesrv
   - Writes approval/denial back to bypass/resolve

3. When approved, toolsrv executes the command directly (no sandbox)
   - Execution happens on toolsrv's host (local or remote)
   - Output streams back through the normal tool call path

This enables bypass to work when toolsrv is remote:
- User sees the approval notification locally
- Command executes on the remote host outside its sandbox

Architecture:
  toolsrv (remote)          olliesrv (local)
  ┌─────────────────┐      ┌──────────────────┐
  │ sandboxed cmd   │      │ bypass broker    │
  │      ↓          │      │  - policy        │
  │ bypass.Submit() │──────│  - notification  │
  │      ↓          │ 9P   │  - rate limit    │
  │ wait for result │←─────│  - user approval │
  │      ↓          │      └──────────────────┘
  │ execute direct  │
  └─────────────────┘
2026-08-12 08:48:32 +02:00
Levi Neely 1814f82928 toolsrv: include session ID in bypass broker requests
The bypass broker requires a 'session' field in the JSON request
to identify which session is making the bypass request. The toolsrv
was sending requests without this field, causing 'denied (no session
identity)' errors.

Fix: Include OLLIE_SESSION_ID from environment in the bypass request.
2026-08-12 08:34:52 +02:00
Levi Neely 23b427f97b toolsrv: fix integration test to provide sandbox.yaml 2026-08-12 08:30:09 +02:00
Levi Neely 3ee4f16653 fs: remove proc/ proxy, add tool_unload to agent ctl
- Remove session/{sname}/agent/{aname}/proc/ tree (redundant proxy to toolsrv)
- Add tool_unload command to agent ctl file
2026-08-12 08:25:22 +02:00
Ollie Agent 47b460b2da fs: eliminate AgentLog, SessionNode, RootState
State now lives where it belongs:
- Chat log/plan/condvar → agent.Agent (new chatlog.go)
- Models cache → session.Session
- Event handler → agent.Agent.initChatHandler() (self-wiring)
- Message replay → agent.Agent.ReplayMessages()

The fs package is now a pure presentation layer: spec.go (namespace),
support.go (shared utils), newroot.go (entry point), cache.go (ModelCache).
No wrapper types, no parallel registries.

Deleted: AgentLog, SessionNode, RootState, lifecycle.go, agent_log.go,
session_node.go. Removed replay_test.go (needs rewrite against agent pkg).
2026-08-11 21:50:18 +02:00
Ollie Agent 31d3466b3b agent: add chat log fields and methods
Moved log buffer, plan, mutex, and condvar onto agent.Agent directly.
These were previously on the fs package's AgentLog type. The fs package
will be migrated to use these in the next commit.

New methods: AppendChat, EnsureTrailingNewline, ChatMu, ChatCond,
ChatLog, Plan, SetPlan.
2026-08-11 21:41:12 +02:00
Ollie Agent b01dbdfba8 sandbox: single config, no named profiles
Removed the named sandbox concept. One config file: sandbox.yaml
(installed to ~/.config/ollie/sandbox.yaml). Removed sandbox-remote.yaml,
moved restricted.yaml to doc/ as a sample. Removed the 'sandbox' arg
from tool dispatch.
2026-08-11 21:23:23 +02:00
Ollie Agent d06183c8d5 fix file header comment 2026-08-11 21:19:05 +02:00
Ollie Agent b5411f930f toolsrv: rename server.go → p9.go 2026-08-11 21:17:18 +02:00
Ollie Agent 62fe1dab83 toolsrv: rename internal/fs → internal/server
The package defines the Server type and its namespace — 'fs' was a
leftover name from when it only held the filesystem spec. Now it's
the server definition. File renamed spec.go → server.go to match.
2026-08-11 21:15:03 +02:00
Ollie Agent 8ce8390421 toolsrv/fs: move Server into spec.go, delete state.go
Server is the top-level type that Spec() takes as input. It belongs
with the namespace declaration, not in a separate file.
2026-08-11 21:12:51 +02:00
Ollie Agent 7591b2369b fs: remove connected file, agent tools file; fix session/idx hang
- Removed session/connected (used blocking Ping() on potentially stale conn)
- Removed per-agent tools file (tool management now via toolsrv ctl)
- Fixed session/idx: replaced blocking IsConnected() with non-blocking
  toolsConn != nil check, preserving the field for GUI compatibility
- Removed dead IsConnected() method from session.Session
2026-08-11 21:06:42 +02:00
Ollie Agent 066659b8b6 olliesrv/fs: inline all handlers into spec.go
The namespace spec is now fully self-contained. Every handler is a
closure right where it's declared — no jumping between files.

Deleted: handlers_root.go, handlers_session.go, handlers_agent.go, ctl.go
Added: support.go (shared utilities: mergeCtx, streamChat, stripMarkers,
dispatch, wireAgentEvents, help memoization)

spec.go grew to ~920 lines but is now the single source of truth for
the entire 9P namespace. Know where to look.
2026-08-11 20:41:08 +02:00
Ollie Agent 5a09e603cd virtfs: eliminate Binding type
Each() now returns []FsNodeDecl directly. The Binding type was a
redundant subset of FsNodeDecl with a slightly different Remove
signature. Dynamic entries are now expressed uniformly — Remove,
Rename, Children, Aliases all live on FsNodeDecl like everything else.

Also added: Alias() and RenameNode() options, DirNode accepts
[]FsNodeDecl for passing pre-built child slices.
2026-08-11 20:24:29 +02:00
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 53b4618561 toolsrv: migrate to closure-capture fsedsl API
Spec() now takes *Server and all handlers are closures that capture it.
Removed Ctx type, type aliases, and separate handler functions.
The tree structure and behavior are declared together in one place.
2026-08-11 16:56:34 +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