zot/internal/agent
patriceckhart 65135596a0 session: persist each turn as it happens, flush on SIGTERM/SIGHUP
Until now an interactive session's turns lived only in the running
agent's in-memory transcript; the session file on disk got nothing
new until iv.Run returned and the deferred WriteNewTranscript at
the bottom of runInteractive fired. That meant any process death
that bypassed the deferred flush \u2014 closed terminal window (SIGHUP),
system shutdown (SIGTERM), kill -9, an OS / power crash \u2014 took the
entire session with it. The window of data-at-risk was "everything
since the TUI started or last switched session."

Two changes that close the window:

- Add OnMessageAppended / OnUsage hooks on core.Agent, fired right
  after each transcript message is appended (user prompt, finalised
  assistant turn, tool-results message, the OpenAI image mirror)
  and after each usage event arrives. The interactive runtime in
  cli.go binds them to sess.AppendMessage / sess.AppendUsage so
  every finished turn lands in the session JSONL the moment it's
  durable in memory. A persistMu mutex coordinates the agent
  goroutine's per-message writes with the TUI goroutine's session
  swap (/sessions) and explicit flushes (/session export).
  sessBaselineMsgs advances in lock-step so the exit-time
  WriteNewTranscript no longer double-writes rows already on disk.

- Install a SIGTERM / SIGHUP handler in runInteractive that flushes
  any not-yet-persisted in-flight turn before exiting. SIGINT is
  intentionally NOT handled \u2014 the TUI consumes Ctrl+C as a regular
  key event for cancel/clear semantics, so installing a SIGINT
  notifier here would swallow it. The handler exits with os.Exit(0)
  rather than re-raising, because re-raising would skip the chance
  to flush and the only at-risk state we care about (the session
  file) is already flushed by the time we get there.

Build closures (BuildAgent / BuildAgentFor) are re-wrapped after
the persistence wiring exists so any agent the TUI constructs on
login or /model swap also gets the hooks; otherwise switching
provider would silently revert to the old in-memory-only behaviour.

Print and JSON modes are unaffected: they run a single turn and
already flush via WriteNewTranscript at the end of their handler.
2026-04-28 08:01:12 +02:00
..
extensions feat(ext): interactive extension panels + persistence 2026-04-22 08:53:21 +02:00
modes tui: unify accent bar, narrow status split, restore session usage 2026-04-27 19:51:36 +02:00
tools fix(bash): kill process group immediately on cancel 2026-04-23 18:47:44 +02:00
args.go tui: unify accent bar, narrow status split, restore session usage 2026-04-27 19:51:36 +02:00
botcmd.go fix ci on windows: split detach helper into posix/windows variants 2026-04-18 10:58:10 +02:00
botcmd_unix.go fix ci on windows: split detach helper into posix/windows variants 2026-04-18 10:58:10 +02:00
botcmd_windows.go fix ci on windows: split detach helper into posix/windows variants 2026-04-18 10:58:10 +02:00
build.go feat: remove default step limit 2026-04-26 13:32:46 +02:00
changelog.go fix(changelog): strip commit/date suffix from version strings 2026-04-25 17:50:35 +02:00
cli.go session: persist each turn as it happens, flush on SIGTERM/SIGHUP 2026-04-28 08:01:12 +02:00
config.go feat: auto-refresh OAuth tokens before each API call 2026-04-24 19:37:44 +02:00
extcmd.go feat: extension system (subprocess + json-rpc, any language) 2026-04-19 14:09:43 +02:00
modelsync.go feat(models): support user-defined models via models.json 2026-04-23 23:09:32 +02:00
rpc.go feat(ext): interactive extension panels + persistence 2026-04-22 08:53:21 +02:00
systemprompt.go feat(tui): context diffs + framed tool blocks + paced streaming 2026-04-20 15:50:39 +02:00
update.go fix(update): re-check every launch when the cache says up-to-date 2026-04-20 18:31:51 +02:00