zot/internal
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
..
agent session: persist each turn as it happens, flush on SIGTERM/SIGHUP 2026-04-28 08:01:12 +02:00
assets assets: refresh zot logo to cleaner pixel-art Z 2026-04-20 12:01:43 +02:00
auth feat(auth): headless OAuth with paste-code input 2026-04-22 17:49:11 +02:00
core session: persist each turn as it happens, flush on SIGTERM/SIGHUP 2026-04-28 08:01:12 +02:00
extproto feat(ext): interactive extension panels + persistence 2026-04-22 08:53:21 +02:00
provider feat: auto-refresh OAuth tokens before each API call 2026-04-24 19:37:44 +02:00
skills perf(prompt): cut system prompt to the bone (410 -> 54 tokens) 2026-04-19 17:39:38 +02:00
tui tui: unify accent bar, narrow status split, restore session usage 2026-04-27 19:51:36 +02:00