Commit Graph

305 Commits

Author SHA1 Message Date
Levi Neely 13b17cd777 backend: extract parseOllamaContextLength, 100% coverage 2026-04-21 15:39:52 +02:00
Levi Neely 6a60f5014b backend: extract newFromEnv, 100% coverage for new.go 2026-04-21 15:36:26 +02:00
Levi Neely 9d8398bab0 backend: test coverage for new.go (loadEnvFile, openAIName, Backends) 2026-04-21 15:34:01 +02:00
Levi Neely 21827c57eb backend: extract stream parsers, shared HTTP, url.URL, comprehensive tests
- Extract streamOpenAISSE, streamOllamaNDJSON from ChatStream goroutines
  into standalone functions taking io.Reader for direct testability
- Remove ctx parameter from streamAnthropicSSE (caller handles cancellation)
- Extract streamRequest into backend.go: shared HTTP execution, 429/error
  handling, and goroutine lifecycle for all three backends
- Extract encodeOpenAIMessages, encodeOpenAITools from ChatStream
- Replace string baseURL with *url.URL; validate in constructors
- Constructors (NewOpenAI, NewOllama, NewAnthropic, NewCopilot) now return error
- Remove unreachable json.Marshal and http.NewRequestWithContext error branches

ChatStream coverage: 100% (OpenAI, Ollama, Anthropic)
Stream parsers: 100% (OpenAI, Ollama), 96.6% (Anthropic)
streamRequest: 100%

Test files:
- openai_encode_test.go: message/tool encoding, JSON round-trip
- openai_stream_test.go: SSE parsing via io.Reader
- ollama_stream_test.go: NDJSON parsing via io.Reader
- anthropic_stream_test.go: SSE parsing, mapAnthropicStopReason
- openai_test.go: HTTP integration, error paths, models, context length
- backend_test.go: contract checks (all interface methods), stream text/usage/
  tool calls/tool conversation, HTTP errors, rate limits for all backends
2026-04-21 15:32:03 +02:00
Levi Neely 549e1ccb45 pkg/log: remove globals, inject Sink as dependency
- Sink owns async writers and Flush; created once at startup
- Logger created via Sink.Logger/NewLogger/NewWriter
- agentCore.log field replaces package-level clog
- hooks.Run/runHookCmd accept *Logger parameter
- No globals, no init(). 100% test coverage.
2026-04-21 14:48:41 +02:00
Levi Neely 6b4d401d03 pkg/env: add tests (0% -> 100% coverage) 2026-04-21 13:53:38 +02:00
Levi Neely aa8301a900 pkg/config, pkg/skills: accept io.Reader, remove filesystem access
- config.Load takes io.Reader, uses json.NewDecoder (100% coverage)
- skills package reduced to pure ParseFrontMatter(io.Reader) parser
- Filesystem operations moved to callers (9p/skillstore, agent/core)
- All tests use strings.NewReader — no temp files
2026-04-21 13:51:46 +02:00
Levi Neely 0b64808ef5 pkg/tools: add contract tests, remove Close from Server/Dispatcher
- Add contract tests for tools.Server and tools.Dispatcher interfaces
  (tools_test.go), covering all exported behavior including error paths.
  pkg/tools coverage: 0% -> 100%.
- Add contract test for execute.Server (ListTools, CallTool), Decl
  factory test, and executeElevated early-exit test. execute coverage:
  68% -> 71%.
- Remove Close() from Server and Dispatcher interfaces and all
  implementations/callers. These existed for MCP server cleanup which
  has been removed.
2026-04-21 13:38:02 +02:00
Levi Neely 85b0d7f6fa remove MCP support, add execute_code tests
- Remove pkg/mcp/ (client, transport, JSON-RPC handshake)
- Remove mcpServer adapter and NewServer() from tools.go
- Remove MCPServers/ServerConfig from config
- Remove MCP server connection loop from BuildAgentEnv
- Remove ListServers() from Core interface and agentCore
- Remove /mcp command and help line
- Rename extractMCPResult -> extractToolResult
- Rename mcpToolsToBackend -> toolInfosToBackend
- Add execute_test.go: 28 tests covering detectLanguage, injectArgs,
  ansiCEscape, ValidateCode, allowed, ReadTool, Execute, Dispatch
  (pipeline, parallel, tool steps), limitedWriter, rate limiting,
  SetEnv, SetCWD (68.2% coverage from 0%)
- Add testdata/sandbox/default.yaml for real sandboxed test execution
2026-04-21 13:25:32 +02:00
Levi Neely 46272f22a6 test: sandbox coverage + backend contract tests
- sandbox: remove dead code (DefaultConfig, Load/Save, LayeredConfig, Merge,
  Version, SystemDefaults, validateName, ConfigPath, OpenSandbox); unexport
  internal symbols; LoadSandbox now takes io.Reader; loadSandboxConfig owns
  file opening; 83% package coverage via new sandbox_test.go
- backend: add BaseURL field to AnthropicBackend for testability; new
  backend_test.go runs checkContract against all five implementations
  (Ollama, OpenAI, Copilot, Anthropic, CodeWhisperer) via httptest servers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 13:00:06 +02:00
Levi Neely c6f6e7feeb agent: testability, dead code removal, coverage to 83%
- Hook timeout configurable via hookTimeout var (unlocks <-timeout branch)
- newBackend injection into agentCore (unlocks /backend switch in tests)
- Remove dead code: WatchSignals, Prompt(), Goal(), isComplete/markComplete,
  contextStatsString, extractMCPText, DefaultAgentsDir, DefaultSessionsDir,
  Dispatcher() on AgentEnv
- Signal handling moved to process entry points; core has no OS signal knowledge
- Tests: extractMCPResult, mcpToolsToBackend, RestoreSession, SetEnv,
  BuildAgentEnv, firstSentence, hook timeout, backend injection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 12:28:29 +02:00
Levi Neely 248b45e688 build: run tests as part of mk process
core/mkfile: add test:V target (go test ./...) and make all:V depend on it.
A test failure exits mk with a non-zero status, blocking any further targets.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 11:56:01 +02:00
Levi Neely 8d4e265b60 tests: fill critical-path coverage gaps
- loop.go: rate-limit retry (retryCountdown 100%), pre-tool cancellation,
  ChatStream+ctx-cancel (recordInterruption "request"), exec-cancel with
  pending inject
- executeTurn: startup messages, backend error event, auto-compact with
  preCompact/postCompact hook context injection
- session: compact backend error, empty summary, tool-message flattening
- handleCommand: /compact while running/nil-session/preHook-blocks,
  /clear while running, /agents with files
- hooks: non-zero exit code (non-blocking), context cancellation
- accessors: AgentName, BackendName, ModelName, SystemPrompt,
  GenerationParams (get+set), ListModels, CtxSz, Usage
- SetSessionID rename and same-ID no-op
- autoCompactLimit with zero ctxLen, agentSpawn with context output,
  hooksRan plural branch

55.2% → 73.7% total; executeTurn 87.7%→98.6%, run 62.7%→93.1%,
retryCountdown 0%→100%, compact 84.2%→94.7%

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 11:05:30 +02:00
Levi Neely faaf968f64 tests: spec command behaviors guaranteed by 9P ctl/file interface
Adds tests for every slash command exposed through the session's ctl
file and direct r/w files (backend, model, agent, cwd). Each test pins
the exact behavioral guarantee 9P depends on: show-current, set-value,
error-while-running, empty-dir, current-item-marked, no-session guard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:52:56 +02:00
Levi Neely 8b8b675ff6 agent: /clear keeps session ID to avoid 9P desync
Generating a new ID on /clear silently orphaned the 9P session directory.
Now /clear only nils the in-memory session; the ID (and its directory)
remain valid. The next turn overwrites the session file in place.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:45:40 +02:00
Levi Neely 4b1229353f agent: add compacting state; assert in compact tests
setState("compacting") wraps session.compact() at both sites — manual
/compact (idle → compacting → idle) and auto-compact (thinking →
compacting → thinking). Tests capture state during the backend call to
verify the transition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:42:48 +02:00
Levi Neely d7e44e26ef agent: Submit-while-running always queues to FIFO; add /i inject command
Concurrent Submit now unconditionally pushes to the FIFO rather than
trying pendingInject first. Use /i <prompt> to inject into the running
turn's tool-call loop, and /irw <prompt> to overwrite a pending inject.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:38:26 +02:00
Levi Neely 40b003219b agent: add core primitive tests (3.6% → 43.3% coverage)
Tests cover the full agent turn lifecycle: happy path, state transitions
(idle→thinking→calling→idle), preTurn hook blocking and context injection,
postTurn hook continuation, FIFO drain, pendingInject as next turn,
interrupt mid-turn, manual and auto compaction, agentSpawn idempotency,
loadSystemPrompt panic on missing file, panic recovery in Submit, and
command dispatch (/help, /clear). Each test is a spec for a primitive the
9P layer and frontends depend on.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:27:52 +02:00
Levi Neely cfdaf18150 agent: replace handleCommand switch with dispatch map
Each slash command is now a closure in a map[string]cmdFn. Adding or
removing a command is a single map entry with no fall-through risk.
The ! shell handler is unchanged. handler(infoEvent("")) now fires only
for recognized commands, not unknown /foo inputs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:19:09 +02:00
Levi Neely c3d749d2d3 agent: sweep stale tmpdirs at startup
Crash-left /tmp/ollie/{id}/ directories are now cleared once at process
start via sync.Once before the first session tmpdir is created.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:15:02 +02:00
Levi Neely d6fa9458e5 agent: hard-fail on loadSystemPrompt error and auto-compact error
Both failures now panic with a descriptive message. Submit recovers,
logs the panic and stack trace via clog, cancels any in-progress action,
resets state to idle, and emits an error event to the handler.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:12:58 +02:00
Levi Neely 5ffda77972 agent: convert Submit recursion to explicit loop
Post-turn hook continue, unconsumed pendingInject, and FIFO drain all
previously re-entered Submit recursively, growing the stack with each
continuation. Replace with an explicit loop in Submit and extract the
single-turn logic into executeTurn, which returns the next prompt or "".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:07:14 +02:00
Levi Neely 5569b3917b agent: load SYSTEM_PROMPT.md as base system prompt, append agentPrompt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 09:14:07 +02:00
Levi Neely 93e5fe9f43 agent: remove Role:"newline" event
setState("idle") was called before handler(Event{Role:"newline"}),
meaning the final appendChat("\n") raced against statewait watchers
and clients that poll state. Remove the event entirely; clients no
longer see it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 21:01:04 +02:00
Levi Neely 1ab75978a1 README: rename p/ prompt files to semantic names
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 20:10:17 +02:00
Levi Neely 5a3f76d035 agent: fix AfterFunc broadcast missing lock; add WaitChange tests
context.AfterFunc callback was broadcasting without holding changeMu,
risking a missed wakeup if ctx cancels between the consumer's check
and cond.Wait(). Add unit tests covering the full state-change cycle
and the missed-wakeup race under -race.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 16:55:57 +02:00
Levi Neely f03ccadd43 agent: fix missed-wakeup race in WaitChange
notifyChange must hold changeMu when broadcasting. Without the lock,
the broadcast can fire between the consumer's value check and its
cond.Wait call — the signal is lost and the consumer sleeps until the
next change, causing an apparent off-by-one lag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 16:25:43 +02:00
Levi Neely fdc77618ab agent: add WaitChange for blocking reads on state/usage/ctxsz/cwd
Adds a sync.Cond-based change notification mechanism. setState,
SetCWD, and usage updates all broadcast. WaitChange blocks until the
named field changes from the given current value, with context
cancellation support via context.AfterFunc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 16:09:03 +02:00
Levi Neely 524dccdc81 agent: rename userPromptSubmit→preTurn, stop→postTurn
Names now reflect the turn lifecycle rather than implementation details.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 15:47:42 +02:00
Levi Neely d8c0f8a8a3 docs: document hook-based system prompt; fix hooks example to array form
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 12:16:42 +02:00
Levi Neely 800dfa526e agent: remove Go-based systemPrompt composition; priming now via hooks
systemPrompt() loaded and templated files from DefaultPromptsDir() at
startup and on cwd change. This is now handled entirely by the
agentSpawn hook using the prime script, eliminating the parallel
prompt-injection path.

DefaultPromptsDir() is kept for the 9P server's p/ directory exposure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 12:10:07 +02:00
Levi Neely be42fbd739 hooks: support array of commands per hook event
Hook values can now be a string or array of strings in agent JSON config.
Commands run in order; exit 2 from any command blocks immediately.
Context from all successful commands is concatenated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 11:53:57 +02:00
Levi Neely aeeb0a3e22 execute: prepend \$OLLIE/x/ to PATH for tool subprocesses
Allows wrapper scripts in ~/.config/ollie/scripts/x/ to shadow system
binaries transparently without modifying the user's shell environment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 11:07:23 +02:00
Levi Neely b7c1b317cb agent: fix first-turn chat display; setState thinking before agentSpawn hook
setState("thinking") was called after fireAgentSpawn, so if the agentSpawn
hook (e.g. bd prime) took longer than the TUI's 150ms grace period,
tailUntilIdle would see state=idle and exit before the LLM turn began.
Move setState("thinking") to before the session-nil block so the TUI
immediately sees a non-idle state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 10:41:21 +02:00
Levi Neely 031dc349e2 agent: add hook observability; log state changes only on transition
- Debug-log all hook firings unconditionally (name, cmd, exit, context_len)
- Emit "N hook(s) run" to chat for lifecycle hooks (agentSpawn, pre/postCompact,
  pre/postClear); suppress per-turn hooks (userPromptSubmit, stop) from chat
- Store env.Messages (MCP connections) on agentCore; emit to chat on first turn,
  debug-log unconditionally
- Replace inline s.state assignments with setState() so debug logs emit only
  on state transitions, not on every State() poll

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 07:24:44 +02:00
Levi Neely d66b8f473f agent: fix agentSpawn hook; add pre/post compact and clear hooks
agentSpawn previously fired on every turn with its result discarded.
It now fires once on the first turn of a session, injecting stdout into
the opening message. It re-fires naturally after /agent switch since
that resets the session.

Add preCompact/postCompact hooks at both compact sites (auto and manual).
Add preClear/postClear hooks around /clear. Pre-hooks can block the
operation (exit 2); post-hooks are fire-and-forget notifications.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 06:54:44 +02:00
Levi Neely 56c299d29b elevation: replace Elevator interface with x/elevate plugin script
Remove the elevation package and Elevator interface entirely. The
execute server now invokes x/elevate directly, with detection reduced
to a stat check. Decl() loses the elevator parameter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 20:50:20 +02:00
Levi Neely e09a6c1250 elevation: add Elevator interface + superpowers backend; add elevated:true to execute_code steps
Remove session-scoped env injection from frontend (s/{id}/env, -e/-env flags) — superseded
by per-invocation superpowers run-session in the elevator. Keep agentCore env map and
OLLIE_SESSION_ID propagation via pushSessionEnv.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 20:06:43 +02:00
Levi Neely fc1d1c6f83 agent: include session env in !cmd subprocess
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 19:19:55 +02:00
Levi Neely 97e453d453 env: session-scoped env injection via session spec; no daemon-global env
- BuildAgentEnv accepts sessionEnv map for MCP ${VAR} expansion without
  touching os.Setenv — session vars stay isolated from other sessions
- STDIOTransport uses os.Expand with session lookup instead of os.ExpandEnv
- Core.SetEnv injects into execute server envExtra only (not process env)
- Remove global env exposure; env is injected at session creation time

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 15:59:51 +02:00
Levi Neely 44bff71a65 env: centralize daemon env in pkg/env; clean up session env propagation
- New pkg/env package owns EnsureDefaults, Set, Get, All, Format
- BuildAgentEnv reverted to 3-arg signature (no initialEnv)
- agentCore.setEnv removed; pushSessionEnv handles OLLIE_SESSION_ID only
- execute.Server.SetEnv no longer calls os.Setenv (session-scoped only)
- !cmd handler sets OLLIE_SESSION_ID explicitly in subprocess env
- mcp/transport: seed cmd.Env from os.Environ() so MCP servers inherit PATH etc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 15:43:01 +02:00
Levi Neely 2695567aa4 Merge ollie-tmp into master 2026-04-18 09:51:43 +02:00
Levi Neely f6aa68b80e agent: expose GenerationParams on Core interface
Add GenerationParams() and SetGenerationParams() to the Core interface
and implement them on agentCore, allowing frontends to read and update
sampling parameters at runtime.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 09:49:42 +02:00
Levi Neely 95fdc90c18 sandbox: allow write access to OLLIE_TMP_PATH
Add {OLLIE_TMP_PATH} to DefaultConfig filesystem RW list so landrun
permits writes to the tmp directory. Also mkdir the path at session
init so the pathExists check in WrapCommand doesn't skip it.
2026-04-17 16:09:44 +02:00
Levi Neely 2703e754b8 propagate OLLIE_TMP_PATH to sandbox
Add OLLIE_TMP_PATH to DefaultConfig env list and inject it via SetEnv
in NewAgentCore and SetSessionID, matching the pattern used for OLLIE
and OLLIE_SESSION_ID.
2026-04-17 16:07:33 +02:00
Levi Neely 92034b5342 add OLLIE_CFG_PATH and OLLIE_DATA_PATH env vars
Replace all hard-coded ~/.config/ollie and ~/.local/share/ollie paths
with a new paths package that reads OLLIE_CFG_PATH and OLLIE_DATA_PATH,
falling back to the previous defaults when unset.
2026-04-17 15:31:19 +02:00
Levi Neely e0a0dba76a rename OLLIE_9MOUNT to OLLIE; remove redundant sandbox RWX entry 2026-04-17 14:54:40 +02:00
Levi Neely 9ef3dd07cc Merge branch 'remove-execute-pipe' 2026-04-16 21:39:57 +02:00
Levi Neely c2ba1728e4 execute: unify execute_pipe into execute_code as pipeline primitive
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 21:36:22 +02:00
Levi Neely fe51f96dcc Merge branch 'remove-execute-tool' 2026-04-16 21:28:32 +02:00