Draw a peer message above the turn it started, not below it

The live Claude Code path only learns a turn was another agent's when
the turn ends -- the whole of the message arrives as an `origin` object
on the `result` -- so the note was appended after everything it caused,
and the transcript showed the answer above the question.

It cannot be recorded in place: by the time anyone knows, the reply is
already written, and the transcript is append-only. So the event carries
where it belongs instead. `PeerMessage` gains `turnStart`, the seq of
the status that opened its turn, stamped by the pump -- the only thing
that knows a seq and the only thing that sees every driver's turns. The
phone gives the note that seq, so it sorts into place rather than being
drawn out of order at the end. A status draws no row, so there is
nothing for it to collide with and the list stays sorted, which the
scroll anchor and paging both depend on.

Absent where there is nothing to correct: a message replayed out of a
session file by `import` is already in the right place, and one that
opened no turn has no turn to sit above. Both stay where they arrive.

The echo driver gets `/peer-turn` for the live shape, beside `/peer` for
the in-place one. Verified on the emulator both ways, live and on
replay, plus an ordinary `/tools` turn to confirm the run grouping the
insertion cuts through is unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-01 01:20:38 -04:00
1 parent 7525fc925a
commit 465645cefb
9 files changed
+246 -13

No files matched your search

+38
View File
@@ -278,6 +278,42 @@ impl EchoDriver {
// asked for it is still announced -- every driver owes exactly one
// `MessageTaken` per message, and a command that quietly vanishes
// from the transcript is the one thing echo must not model.
// The live Claude Code shape, which is the one the ordering has to
// survive: the CLI says nothing about a peer message until the
// turn's `result`, so the event arrives below the whole reply it
// caused and the phone has to put it back. Checked before `/peer`,
// which would otherwise take the rest of this word as the body.
if let Some(rest) = text.strip_prefix("/peer-turn") {
if announce {
self.emit(Event::MessageTaken {
id: None,
text: text.clone(),
images: images.clone(),
});
}
self.emit(Event::Status {
state: SessionStatus::Running,
});
self.some_calls("peer");
self.emit(Event::AssistantText {
delta: "Pulled, and AGENTS.md is up to date here now.".to_string(),
});
self.emit(Event::PeerMessage {
from: "dev-updater-f5".to_string(),
text: if rest.trim().is_empty() {
"Pull before you touch AGENTS.md.".to_string()
} else {
rest.trim().to_string()
},
// Stamped by the manager, exactly as a real one is.
turn_start: None,
});
self.emit(Event::Status {
state: SessionStatus::Idle,
});
return;
}
if let Some(rest) = text.strip_prefix("/peer") {
if announce {
self.emit(Event::MessageTaken {
@@ -296,6 +332,7 @@ impl EchoDriver {
} else {
rest.trim().to_string()
},
turn_start: None,
});
return;
}
@@ -710,6 +747,7 @@ async fn write_beat(sink: &EventSink, session_dir: &Path, beat: usize) {
send(Event::PeerMessage {
from: format!("beat-{beat}-peer"),
text: format!("Message {beat} from another session, for the row it becomes."),
turn_start: None,
});
}
}