When cwd was passed with ~ or $HOME (e.g. ~/src/myproj), it was used
unexpanded in BuildAgentEnv -> resolvePrompt, causing prompt commands
to fail silently (invalid cmd.Dir) and producing an empty system prompt.
Apply os.ExpandEnv + paths.ExpandHome before cwd is used anywhere.
Fixes a race where the client starts a new chatwait read after the
turn has ended (EnsureTrailingNewline already fired), causing a
5-second blocking delay before the shell prompt reappears.
Replaces polling with a blocking file that returns new bytes as they
are appended to the session log. Unblocks immediately on end-of-turn
(EnsureTrailingNewline) so frontends exit cleanly.
Fids opened OWRITE or ORDWR with zero bytes written were silently
dropped on clunk — the old writeBuf-length guard prevented handleWrite
from being called, leaving the previous file content intact.
Track writable by open mode instead, and remove the blanket empty-input
early return in handleWrite. /s/new retains its own empty guard since
a zero-byte write there is meaningless.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
s/new routes to BatchStore (one-and-done lifecycle) when the write
contains a \n---\n separator, and to SessionStore (multi-turn) otherwise.
Document both dirspecs separately: batch jobs have spec/state/statewait/
result/usage/ctxsz/log; interactive sessions have the full file set.
Also add missing params file to session dirspec.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All /b/ routing removed from pathType, read, remove, handleWrite,
readDir, and makeStat. The batchStore field is gone from Server;
SessionStore now handles both session and batch job lifecycle.
-154 lines from server.go.
- Replace Get/Put calls with storeRead/storeWrite helpers
- Replace Wait calls with storeBlockingRead helper
- Wrapper types override Open instead of Get
- Use OpenStore for session/batch child stores
- All store fields typed as Store
- FlatDirStore alias points to BlobStore
- Server fields use BlobStore for flat stores
- Replace JobStore/SessionFileStore with Open
- Type-assert for Wait/LogInfo where needed
- Rename store variable to sfs to avoid package shadowing
Server no longer owns sessions map or session lifecycle methods.
SessionStore and SessionFileStore are type aliases to core.
Fid path rewriting on rename handled via OnRename callback.
- Remove mcp from session file list and read handler
- Remove mcp from ctl command list
- Remove mcp from server.go mode switch
- Update README: remove mcp from file table and ctl docs
- Add Server.InterruptAll() to cancel in-progress turns on all sessions
- Call InterruptAll() before Shutdown() on SIGINT/SIGTERM in main
- Remove defaultAgentsDir/defaultSessionsDir helpers; inline paths.CfgDir()
expressions directly (OLLIE_CFG_PATH already handles relocation)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
State is terminal-only (running→done/failed), so base comparison
adds nothing — block on the done channel directly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirrors s/{id}/chat: append-only event log, tailable via Qid version
polling. Also fixes Wait not treating empty base as current value,
which caused statewait to return immediately with "running".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop formatEvent "newline" case (event no longer emitted). Reset
assistantStarted on "user" event instead so chained turns get the
"assistant: " prefix correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename batchJob.status → .state; update all references
- Add done chan struct{} to batchJob, closed on terminal state
- Add statewait file to b/<id>/: blocks until state leaves "running"
- Add CurrentWaitValue/Wait methods to BatchJobStore
- Extend Topen handler to snapshot waitBase for b/ wait files
- Extend Tread handler for b/ to serve statewait with 5s timeout/retry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FUSE puts blocked processes in D state (uninterruptible sleep) waiting
for the read to return. SIGINT cannot interrupt D-state, so Ctrl+C has
no effect even after Tflush cancels the goroutine. A 5s timeout causes
the read to return periodically, letting FUSE unblock the client process
and deliver any pending signals.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two fixes:
- *wait files are one-shot per open: offset > 0 returns EOF immediately
instead of blocking on the next state transition (was consuming two
transitions per cat invocation)
- Tflush now cancels the in-flight request goroutine via per-tag contexts,
so Ctrl+C on a blocking cat properly unblocks the read
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous design read the current value at read time. If a state
change fired between one cat returning and the next cat opening, the
new cat would see the changed value as current and wait forever for
the next change.
Fix: snapshot the baseline in the fid at Topen time. Each read blocks
until the value changes from the open-time snapshot, then updates the
fid's baseline for the next read. The race window is closed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Serve now dispatches each 9P request to its own goroutine with a shared
response channel, so blocking reads no longer stall the serve loop.
*wait files are handled via SessionFileStore.Wait(connCtx, name), which
merges the connection context with the session context via
context.AfterFunc — the wait unblocks on either connection close or
session kill, preventing goroutine leaks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
*wait files block on read until the corresponding value changes, then
return the new value. enqueue/dequeue renamed to fifo.in/fifo.out.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>