zot/internal/core
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.go session: persist each turn as it happens, flush on SIGTERM/SIGHUP 2026-04-28 08:01:12 +02:00
compact.go feat(compact): silent compaction with status line and orphan repair 2026-04-23 14:20:15 +02:00
confirm.go fix(no-yolo): don't auto-refuse tool calls in non-interactive modes 2026-04-19 19:17:05 +02:00
confirm_test.go feat(tool-gate): --no-yolo flag, confirm dialog, /yolo runtime toggle 2026-04-19 19:12:45 +02:00
core_test.go fix ci on windows: close reopened session in TestSessionRoundTrip 2026-04-18 11:01:42 +02:00
cost.go initial commit 2026-04-17 20:36:38 +02:00
events.go feat(tui): live-stream file body during write/edit tool calls 2026-04-20 08:37:14 +02:00
intercept_test.go feat(ext): phase 4 - full-event interception, arg rewrites, /reload-ext 2026-04-19 17:02:04 +02:00
session.go tui: unify accent bar, narrow status split, restore session usage 2026-04-27 19:51:36 +02:00
session_portable.go feat(session): /session fork + /session tree 2026-04-20 11:10:56 +02:00
session_portable_test.go feat(session): /session fork + /session tree 2026-04-20 11:10:56 +02:00
session_repair_test.go fix(core): repair orphan tool_use rows on session load 2026-04-21 19:36:47 +02:00
tool.go perf(anthropic): fix cost double-count, tighten caching, correct catalog 2026-04-19 18:57:18 +02:00