Commit Graph

654 Commits

Author SHA1 Message Date
Levi Neely 554697e259 agent: preserve session state on interrupt, discard only on real error
Interrupting a long turn previously cleared the session (first-turn case)
or rolled back messages (subsequent turns). An interrupt is intentional —
keep whatever state was built so the user can continue.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:35:13 +02:00
Levi Neely c6d6f2f162 backend: recover DeepSeek DSML tool calls leaking into content stream
DeepSeek's native tool-call format uses <|DSML|invoke> tokens. When
OpenRouter fails to convert them to standard tool_calls chunks, they
arrive as raw content text. Detect the marker, buffer the content, and
parse it into ToolCall structs at [DONE] if no structured tool calls
accumulated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 13:17:29 +02:00
Levi Neely a2e01840b3 store: add s/{sid}/plan — session-scoped markdown task checklist
Session.plan field and SessionFile handler for 9p endpoint s/{id}/plan.
Agents write markdown checklists here via task_add/task_check/task_clear;
peers and parent sessions can read it directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 12:29:58 +02:00
Levi Neely 6abf0cdb3d execute: parallel dispatch via flock; agent: preTool/postTool hooks, turn rollback, mid-turn compact
execute_code auto-batches consecutive tool steps annotated '# ollie:parallel read'
into goroutine fan-outs. Write/global steps serialize. LOCK_SH/LOCK_EX via
syscall.Flock in a per-session lock dir for cross-process safety. Tool scripts
declare their own parallelism profile; no tool names hardcoded in Go.

- flock.go: lockClass, classifyStep (annotation-driven), acquireFlock
- tools.go: LockDirSetter interface
- server.go: lockDir field, SetLockDir method
- pipe.go: runReadBatch with shared-lock goroutine fan-out
- code.go: auto-batching pipeline loop
- core.go: pushLockDir wires session tmpdir to execute server at init;
  preTool/postTool hook callbacks in agentConfig; AutoCompact after each
  tool round; session snapshot/restore on turn failure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 09:00:29 +02:00
Levi Neely ac238f6903 agent: roll back session on turn failure
Snapshot session state before each turn. On any error from run(),
restore to the pre-turn snapshot so the session is clean for the next
attempt. Remove recordInterruption — those messages were appended to
state that gets rolled back anyway, making them dead code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 07:30:59 +02:00
Levi Neely 0fba869ab7 agent: add preTool and postTool hooks
preTool fires before each tool execution; exit 2 blocks the call and
returns the hook's stderr as the tool error result. postTool fires after;
exit 0 appends context to the result, exit 2 replaces it entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 07:19:35 +02:00
Levi Neely aa890502cd agent: inject postTurn context into session history
Non-blocked postTurn hook output is now appended to session history so
the model sees it at the start of the next turn without triggering
autonomous continuation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 23:53:00 +02:00
Levi Neely 51922b78fc agent: mid-turn compaction and runCompact consolidation
Extract runCompact() to handle the full compact cycle (pre-hook, compact,
spawn-context re-injection, post-hook) consistently across all three call
sites. Wire AutoCompact into agentConfig so loop.go triggers it after each
tool round, not just before turns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 20:51:53 +02:00
Levi Neely 77dd5c6168 agent: refactor prompt architecture and session lifecycle
- Rename loopConfig→agentConfig, agentCore→agent, loopcfg→cfg, systemPrompt→preamble
  for clarity: the struct represents agent runtime config, the field is its config,
  and preamble accurately describes the compiled system+agent prompt sent as system role

- Separate preamble from agent context: preamble now contains only SYSTEM_PROMPT.md;
  agent prompt and agentSpawn hook output are injected into session history at
  context refresh points instead of being bolted onto the system role

- Replace fireAgentSpawn (mutated preamble once, guarded by agentSpawnFired flag)
  with spawnContext (assembles agent prompt + hook output, returns string for caller
  to inject); remove agentSpawnFired entirely — session==nil is the sufficient guard

- Three refresh points inject spawn context into session history:
  session start, post-clear (session=nil triggers same path), post-compaction
  (auto and manual)

- Move all preTurn tool primes (tools-file, tools-reasoning, tools-memory,
  tools-subagent, tools-elevate) to agentSpawn in all agent configs so they are
  injected at session refresh points alongside bd prime, not repeated every turn

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 19:38:18 +02:00
Levi Neely d304014ba3 env: add OLLIE_ELEVATE_SOCKET to managed list and EnsureDefaults
Default to $XDG_RUNTIME_DIR/ollie/elevate.sock (falling back to
/run/user/<uid>/ollie/elevate.sock) so the daemon sets and propagates
the value without requiring explicit configuration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 11:45:46 +02:00
Levi Neely cb99bed7bb sort session index by ID for deterministic ordering 2026-04-24 21:44:42 +02:00
Levi Neely 3cef168806 add :: prefix for info events in chat log
- FormatEvent now prefixes info role with ':: '
- Session rename uses :: prefix
- Update test expectation
2026-04-24 21:33:12 +02:00
Levi Neely a4bb7608c7 expand ~ in cwd across agent core, hooks, and execute server
- Add paths.ExpandHome() helper for tilde expansion
- Apply in NewAgentCore, SetCWD (agent + execute server), and hook runner
- Fixes 'chdir ~/path: no such file or directory' errors
2026-04-24 20:10:42 +02:00
Levi Neely 07d362767d fix: update tests for statewait and hook changes 2026-04-24 14:08:02 +02:00
Levi Neely a7cc80af75 fix: set offset after assistant prefix for clean bfg output 2026-04-24 14:03:08 +02:00
Levi Neely ccbb5168b7 fix: statewait blocking and bfg composability
- Report non-zero size for wait files so FUSE clients read them
- Return current state on timeout instead of nil
- Set ChatOffset after user message so offset points past it
- Comment out hooksRan info events (TODO: route to debug file)
2026-04-24 14:00:27 +02:00
Levi Neely 4a8faf0ae3 store: remove BatchStore; sessions are the only session type
Batch jobs were a separate concept (one-and-done lifecycle, distinct
dirspec) that turned out to be a front-end concern. s/b, s/bfg, and
s/bbg now compose session primitives directly: create session, submit
prompt, loop statewait until idle, extract chat[offset:], kill.

Also renames spec -> cfg throughout (session config file) and removes
the parallel/output fields that previously lived in the batch spec;
parallel is now a shell loop, output=json is a prompt suffix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 08:41:42 +02:00
Levi Neely dacb9ead43 store: replace 9 session files with a single r/w spec file
Remove state, backend, model, agent, cwd, params, cwdwait, usagewait,
ctxszwait as individual files. Add spec (0666) which on read returns a
KV snapshot of all config and current state; on write, dispatches
partial KV pairs to the appropriate handlers (backend/model/agent via
Submit, cwd via SetCWD, generation params via SetGenerationParams).
Read-only fields (state, usage, ctxsz, offset) are silently ignored on
write. Only statewait survives from the *wait set — it has genuine
control-flow semantics; usagewait/ctxszwait/cwdwait are better served
by post-turn hooks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 07:42:33 +02:00
Levi Neely 10dd4ee82f store: SessionStore absorbs BatchStore, unified s/ namespace
SessionStore now embeds a *BatchStore and presents a unified
interface. s/new detects batch format (---  delimiter) and
delegates accordingly. list/stat/del/OpenStore/index all check
both session and batch maps. Shutdown/InterruptAll delegate to
both. Batch scripts (job, q, sched, cleanup) added to session
store file table.

This is the store-side half of Option C: single namespace with
composable file tables. The 9p server changes are in the parent
repo.
2026-04-23 18:49:35 +02:00
Levi Neely 8f856ea186 store: unify SessionFileStore and BatchJobStore via RunFileStore
Introduce FileSpec and RunFileStore as a shared, table-driven
implementation of RunnableStore. Both SessionFileStore and
BatchJobStore are now type aliases for RunFileStore, constructed
from a list of FileSpecs that define per-file read/write/wait
behavior via function pointers.

This eliminates ~94 lines of duplicated store plumbing (stat/list/
open/entryFor methods) while preserving the existing filesystem
layout and all public API signatures.

No behavioral changes. Tests pass unchanged.
2026-04-23 18:18:37 +02:00
Levi Neely 81fedb862c fix: ensure chat log lines are newline-terminated
assistant events are streamed without trailing newlines, causing
line-buffered 9P reads to lag one turn behind. Close the assistant
block with \n on role transitions and after Submit returns. Set
ChatOffset at EOF before the user event so offset spans the full
exchange. Remove 500-char truncation of tool call args in FormatEvent.
2026-04-22 16:34:06 +02:00
Levi Neely f220d0ee96 feat: add tail script to session files
Adds a synthetic `tail` file (mode 0555) to each session directory
that runs `tail -f chat`, allowing users to follow a session's chat
log from any working directory.
2026-04-22 12:29:24 +02:00
Levi Neely 197c3664c6 fix: resolve ${PWD} in system prompt from session cwd, not process env
expandSystemPrompt fell through to os.Getenv("PWD") which always
returned the process startup directory. Map "PWD" explicitly to the
cwd parameter so the system prompt reflects the session's working
directory.
2026-04-22 12:21:57 +02:00
Levi Neely e0bd5cefe8 docs: update README for refactor (packages, system prompt, sandbox) 2026-04-22 10:35:38 +02:00
Levi Neely 7884921dd0 fix TestWrapCommand flake when superpowerd is running 2026-04-22 08:59:54 +02:00
Levi Neely d89f458473 backend: mark codewhisperer as intentionally untested, remove test 2026-04-21 22:09:27 +02:00
Levi Neely 0fd3bf1cf8 test: paths 100%, execute 96.2% (was 71%)
- pkg/paths: cover CfgDir/DataDir env and default branches
- execute: cover all injectArgs languages, resolveCodeStep, runStage,
  dispatchExecuteCode, executeElevated via fake elevate script,
  elevated paths in runStage/dispatch, language branches (python, perl,
  bc, jq, awk, sed, ed, lua), limitedWriter edge cases, loadSandboxConfig,
  detectLanguage shebangs
2026-04-21 22:00:08 +02:00
Levi Neely 694530a321 sandbox: 100% test coverage
- Test all expandPath branches (HOME, XDG_*, OLLIE_CFG/DATA_PATH)
- Test checkPath symlink resolution and ROX read/write
- Test LoadSandbox reader error
- Test WrapCommand: landrun unavailable, ROX/RWX entries, sort tiebreaker, superpowerd wrapping
- Test isSuperpowerdRunning: all socket dir paths, successful connection
- Make isAvailableFn/isSuperpowerdRunningFn overridable for testing
2026-04-21 21:49:22 +02:00
Levi Neely 2264f3f11d agent: fix backend race, split commands.go, use OLLIE_TMP_PATH
- Add backend.NewWithName(name) to avoid os.Setenv race on OLLIE_BACKEND
- Extract handleCommand into commands.go (core.go 1264 -> 859 lines)
- Replace hardcoded /tmp/ollie with OLLIE_TMP_PATH (guaranteed by env.Ensure)
2026-04-21 21:44:39 +02:00
Levi Neely dee0fda330 agent: emit hook timeout/start-failure warnings as info events
- Add Warning field to HookResult
- runHookCmd sets Warning on timeout and start failure
- Hooks.Run collects warnings across commands
- All hook call sites emit warnings via infoEvent
- Test verifies timeout warning is surfaced
2026-04-21 21:20:55 +02:00
Levi Neely 53442e193b store: cover renameSession OnRename + SetSessionID error (89.2%) 2026-04-21 20:33:21 +02:00
Levi Neely bd6d9a09a8 store: add NewCore factory injection, integration tests (88.9% coverage)
- Add NewCore to SessionStoreConfig and BatchStoreConfig
- When set, bypasses backend.New + agent.NewAgentCore for testability
- Integration tests: createSession, handleNewBatch, runJob, executeJob
- Tests cover named/auto-ID sessions, parallel jobs, duplicates, cancellation, factory errors
- 98 tests total (was 88)
2026-04-21 20:28:20 +02:00
Levi Neely d64a8d8854 store: improve test coverage 58.6% -> 78.2% (51 -> 88 tests)
- SessionFileStore: content, readFile, writeFile, handleCtl, blockingRead, makePublish
- SessionStore: OpenStore, createSession error paths, Interrupt
- BatchJobStore: AppendLog, RunnableID, Interrupt, Cancel, write rejection
- SkillStore: open write path, entry Stat on non-existent
- Enhanced stubCore with recording and controllable WaitChange
2026-04-21 20:18:53 +02:00
Levi Neely 130bf60708 store: replace Get/Put with Store+StoreEntry model
- Store: Stat, List, Open, Create, Delete, Rename
- StoreEntry: Stat, Read, Write, BlockingRead
- RunnableStore: Store + Runnable
- Open(name) returns StoreEntry (file handle)
- OpenStore(id) on SessionStore/BatchStore returns RunnableStore
- Remove ReadableStore, WritableStore, ReadWriteStore, BlobStore, BlockingReadStore
- Export EntryConfig for external store implementations
- Wait/LogInfo no longer require type assertions
2026-04-21 20:07:27 +02:00
Levi Neely 9b0bf8aca8 store: add Open to Store, move Rename to BlobStore
- Store = BlobStore + Open(name) (Store, error)
- BlobStore = ReadWriteStore + Delete, Create, Rename
- NewFlatDir/NewSkillStore return BlobStore (flat, no subdirectories)
- SessionStore/BatchStore satisfy Store via Open for child stores
- SessionFileStore/BatchJobStore embed *storeConfig, satisfy Store
- Replace SessionStore.SessionFileStore/BatchStore.JobStore with Open
- Add StoreEntry type alias for os.DirEntry
2026-04-21 18:59:31 +02:00
Levi Neely ac0970c5b2 store: add SessionStore.SessionFileStore to match BatchStore.JobStore pattern
- SessionStore now owns construction of its child SessionFileStore
- Add SaveTranscript to SessionStoreConfig
- Server delegates to SessionStore instead of assembling the child
2026-04-21 18:28:54 +02:00
Levi Neely 24e4209a95 store: refactor to storeConfig architecture
- Replace FlatDir type with storeConfig (unexported) + NewFlatDir returning Store
- Refactor SkillStore to skillState + storeConfig, NewSkillStore returns Store
- SessionStore/BatchStore embed *storeConfig for Store interface methods
- Add Runnable interface (RunnableID, Cancel, Interrupt, AppendLog, LogInfo)
- Session and batchJob satisfy Runnable
- Add Terminator-style Shutdown/InterruptAll on SessionStore and BatchStore
- NewSession constructor replaces exported field access
- Rename AppendChat->AppendLog, ChatInfo->LogInfo on Session
2026-04-21 18:23:18 +02:00
Levi Neely 500f7fe96b store: move Session, SessionStore, SessionFileStore to core
Session holds chat buffer + agent.Core + context.
SessionStore takes SessionStoreConfig with OnRename callback for
protocol-level fixups. SessionFileStore unchanged (already used callbacks).
Also adds FormatParams, ParseParams, SessionStoreFileMode, ChatInfo.
2026-04-21 16:37:34 +02:00
Levi Neely 72e4019493 store: move BatchStore, LoadAgentConfig, FormatEvent to core
BatchStore takes BatchStoreConfig instead of *Server, breaking the
9p coupling. LoadAgentConfig and FormatEvent are now exported from
pkg/store.
2026-04-21 16:29:31 +02:00
Levi Neely 0511f7c8b5 store: move SkillStore to core 2026-04-21 16:22:05 +02:00
Levi Neely 3c3e9cf917 store: add SyntheticFileInfo, SyntheticEntry, FileEntry, DirEntry 2026-04-21 16:14:34 +02:00
Levi Neely fb96721f2e store: new pkg/store with interfaces and FlatDir
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.
2026-04-21 16:10:13 +02:00
Levi Neely 4710e3239f agent: simplify AgentConfigPath to plain concatenation, remove dead config.json fallback 2026-04-21 16:06:07 +02:00
Levi Neely 31a6937d46 agent: test expandSystemPrompt git detection 2026-04-21 16:01:16 +02:00
Levi Neely a4d4744eae agent: inline loadSystemPrompt into BuildAgentEnv 2026-04-21 15:58:17 +02:00
Levi Neely 0b705a1ee7 agent: extract expandSystemPrompt, 100% coverage for BuildAgentEnv
- 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
2026-04-21 15:56:35 +02:00
Levi Neely d957bda8a0 backend: remove unreachable NewRequestWithContext guards (URL validated in ctor) 2026-04-21 15:47:34 +02:00
Levi Neely 5cfa5a0df9 backend: 100% coverage for buildAnthropicMessages and streamAnthropicSSE
- 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
2026-04-21 15:45:46 +02:00
Levi Neely 38dd34ea20 backend: cover Ollama Models/ContextLength HTTP paths, constructor URL validation 2026-04-21 15:42:27 +02:00
Levi Neely 13b17cd777 backend: extract parseOllamaContextLength, 100% coverage 2026-04-21 15:39:52 +02:00