Show a replayed session's images instead of dropping them
An imported session showed no screenshots. `text_of` kept only `text` blocks, so every image in the replayed tail was silently discarded -- while the *live* translator has always saved them into the session's `files/` and referenced them. Two readings of the same records, and the one used for history was the lesser. `save_image` moves out of `Translator` to a free function both paths call, since the naming scheme for that directory should exist once. `events_from` now takes the session directory to write into, which means the conversion has to happen where that directory exists -- so `Seed` carries the raw JSONL and `launch` turns it into events, rather than `routes` doing it before the session is created. Costs nothing in tokens, which is the point worth recording: this writes into ai-app's own session directory and the phone fetches a reference only when it draws one. Nothing here is ever written to the CLI's stdin -- it reads its own session file, and the only things this app sends it are typed messages, control requests and `/compact`. Verified against the 133 MB session behind the 2026-08-29 incident: 45 images in the replayed tail, written as real PNGs and served over the files route, with the transcript itself staying at 756 KB of references. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VETa8afmpWaYezLCqJhDB8
This commit is contained in:
1 parent
d96bc041a7
commit
d7c692a4ec
6 files changed
+148
-50
No files matched your search
@@ -831,7 +831,7 @@ fn spawn_import_sync(
|
||||
if lines <= cursor.lines {
|
||||
continue;
|
||||
}
|
||||
match import::replay_after(&transport, &cursor.path, cursor.lines).await {
|
||||
match import::replay_after(&transport, &cursor.path, cursor.lines, &dir).await {
|
||||
Ok(events) => {
|
||||
tracing::info!(
|
||||
"{} grew by {} lines with nothing from here; replaying {} events",
|
||||
@@ -866,10 +866,14 @@ pub struct Seed {
|
||||
/// Where that session's file is and how much of it has been shown, so
|
||||
/// the session can keep itself up to date afterwards.
|
||||
pub cursor: import::Cursor,
|
||||
/// Replayed into the transcript so the phone shows the conversation it
|
||||
/// is joining. The CLI reads the real file itself, so this is what the
|
||||
/// reader sees rather than what the model is given.
|
||||
pub events: Vec<Event>,
|
||||
/// The tail of that session's file, as the raw JSONL.
|
||||
///
|
||||
/// Turned into events in `launch`, not before, because doing so writes
|
||||
/// out the images the records carry and that needs the session
|
||||
/// directory to write them into -- which does not exist until the
|
||||
/// session does. The CLI reads the real file itself, so this only ever
|
||||
/// decides what the *reader* sees.
|
||||
pub records: String,
|
||||
}
|
||||
|
||||
fn launch(
|
||||
@@ -890,7 +894,7 @@ fn launch(
|
||||
claude::write_resume_token(&dir, &seed.resume);
|
||||
import::write_cursor(&dir, &seed.cursor);
|
||||
let at = now();
|
||||
for event in seed.events {
|
||||
for event in import::events_from(&seed.records, &dir) {
|
||||
transcript.append(event, at)?;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user