fix(edit-history): re-read at the end of a turn on session:idle

transcript:complete fires on a `result` line, and claude's interactive transcript has
none (checked against a live session: user/assistant/system turn_duration, no result),
so an open panel never re-read at the end of a turn. A background subagent writes after
the Agent tool end that started it, so its edits stayed missing until the next edit-
capable tool end or a manual refresh.

The panel now re-reads on its session's session:idle, through _onSessionIdle (the SSE
handler map holds one handler per event). Live: a backgrounded subagent's Write shows
up in the open panel at the end of the turn, without a click.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
RandalixandClaude Opus 5.5 committed 2026-10-23 14:38:54 +02:00
1 parent dcdeaab98d
commit f5ce80dce2
5 files changed
+27 -11

No files matched your search

+3 -1
View File
@@ -287,8 +287,8 @@ const _SSE_HANDLER_MAP = [
[SSE_EVENTS.ATTACHMENT_DETECTED, '_onAttachmentDetected'],
// Edit history (edit-history-ui.js): an open panel follows its session's tool ends
// (and its turn ends, from _onSessionIdle: one handler per event in this map)
[SSE_EVENTS.TRANSCRIPT_TOOL_END, '_onEditHistoryTranscriptEvent'],
[SSE_EVENTS.TRANSCRIPT_COMPLETE, '_onEditHistoryTranscriptEvent'],
// Tunnel
[SSE_EVENTS.TUNNEL_STARTED, '_onTunnelStarted'],
@@ -3518,6 +3518,8 @@ class CodemanApp {
this.sendPendingCtrlL(data.id);
if (data.id === this.activeSessionId) this._updateLocalEchoState();
}
// An open Edit history panel re-reads at the end of its session's turn (edit-history-ui.js).
this.onEditHistorySessionIdle?.(data.id);
// Start stuck detection timer (only if no respawn running). Not at the end of Codeman's
// own keep-alive turn (`keepAlive`): the session has been waiting since the last real
// turn, whose timer is still running or has already fired.
+15 -5
View File
@@ -8,8 +8,8 @@
* server's reading, which never runs in the background: it reads
* `GET /api/sessions/:id/edits?sync=1` when it opens and when the active session changes, and,
* only while it is open, re-reads (debounced) on the active session's `transcript:tool_end` for a
* tool that can change files and on `transcript:complete` (the existing transcript watcher's
* events). No poll, and nothing at all while the panel is closed.
* tool that can change files (the existing transcript watcher's event) and on its `session:idle`
* (the end of a turn). No poll, and nothing at all while the panel is closed.
*
* - A code entry opens its diff inline (`GET /api/sessions/:id/edits/:entryId`), drawn with the Git
* panel's diff classes (`git-diff`, `git-diff-line--hunk|add|del`).
@@ -182,9 +182,10 @@ Object.assign(CodemanApp.prototype, {
},
/**
* SSE `transcript:tool_end` ({ sessionId, toolName }) and `transcript:complete` ({ sessionId }):
* while the panel is open on that session, re-read after a tool that can change files, and at
* the end of a turn (a parallel tool call reports only one tool end). Closed: nothing.
* SSE `transcript:tool_end` ({ sessionId, toolName }), and the end of a turn through
* `onEditHistorySessionIdle`: while the panel is open on that session, re-read after a tool that
* can change files, and when the turn ends (a parallel tool call reports only one tool end; a
* background subagent writes after the tool end that started it). Closed: nothing.
*/
_onEditHistoryTranscriptEvent(data) {
if (!data || data.sessionId !== this.activeSessionId || !this._isEditHistoryPanelOpen()) return;
@@ -197,6 +198,15 @@ Object.assign(CodemanApp.prototype, {
}, EDIT_HISTORY_SSE_DEBOUNCE_MS);
},
/**
* Called by _onSessionIdle() (`session:idle`, { id }): the end of a turn. Claude's interactive
* transcript has no `result` line, so the transcript watcher's `transcript:complete` never fires
* for these sessions.
*/
onEditHistorySessionIdle(sessionId) {
this._onEditHistoryTranscriptEvent({ sessionId });
},
/** Called by selectSession(): the open panel follows the active session. */
onEditHistoryActiveSession(sessionId) {
if (!this.isEditHistoryEnabled() || !this._isEditHistoryPanelOpen()) return;