Move ReadableStore, WritableStore, ReadWriteStore, BlobStore, Store
interfaces and FlatDir (née FlatDirStore) implementation from 9p into
core so the agent runtime and other consumers can use them directly.
- Extract expandSystemPrompt(content, cwd) from loadSystemPrompt for
testability without file I/O
- Add tests: ListTools error, tools populated, TrustedTools injection,
agent prompt without system prompt, exec dispatch success/error/isError
- Add mockDispatcher and mockTrustedSetter test helpers
- Test all buildAnthropicMessages branches: system concat, assistant
tool-calls-only, nil arguments default, tool batching, unknown role,
full multi-turn conversation
- Test streamAnthropicSSE done re-entry: events after message_stop and
error are ignored
- Remove unreachable done guard in process closure
- 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.
- 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
- 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.
- 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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>