8503ff1f3d
F1: the server model-replay loaded history via findAllByChat().map(rowToUiMessage) WITHOUT hydrating parts from ai_chat_run_steps. A HARD crash mid-run (SIGKILL/OOM) fires no terminal callback, so the assistant row stays parts:[] and its partial tool-calls/results/text (durable in the steps table) dropped out of the model's next-turn context. Hydrate needy assistant rows (role==='assistant' && !rowHasInlineParts) via findByMessageIds + hydrateAssistantParts before the replay map — mirroring the controller's withReconstructedParts exactly — guarded on the optional repo. Fix the now-false interrupt-resume comment. F2: add a service int-spec that drives the REAL onStep append-persist WRITE branch through AiChatService.stream with a real AiChatRunStepRepo injected, asserting the per-step rows' stepIndex + parts slice and the step-marker metadata match a single-row flush (catches an stepsPersisted-1 off-by-one). F3: add a controller int-spec that drives withReconstructedParts through getMessages WITH the repo present (a mid-run marker-only row + its step rows), asserting the reconstructed metadata.parts and workspace-scoping. F4: remove the dead countByMessage (zero prod callers; reconstructRunParts derives stepsPersisted inline) + its now-unused sql import and the redundant test assertion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>