fix(infinity-daemon): replay in-progress thinking and response state to clients attaching mid-response#60
Merged
Conversation
Deploying infinity with
|
| Latest commit: |
59bf1ec
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://781c0d3c.infinity-dc7.pages.dev |
| Branch Preview URL: | https://sandbox-f0264431-af62-4fe9-b.infinity-dc7.pages.dev |
shadaj
added a commit
that referenced
this pull request
Jul 8, 2026
…to clients attaching mid-response A client connecting to a running agent got a history `Replay` that always ended in an idle state: streamed reasoning is only committed to history once complete, and the CLI unconditionally appended an end-of-replay `ResponseDone` marker that cleared the spinner. The client would appear idle even though the model was actively thinking. ## Daemon * `thread_worker` keeps the in-progress thinking text in a per-worker `Rc<RefCell<Option<String>>>`: the display-event forwarder accumulates `ThinkingChunk`s and clears on anything that moves the stream past the chain (thinking end, text, tool call/result, response done) * On subscribe, the replayed history is extended with `ThinkingStart` + `ThinkingChunk` for the buffered thinking, so clients recompute a live "thinking" state from the end of the replay * `DaemonMessage::Replay` gains `#[serde(default)] in_progress: bool` — true iff a completion is currently in flight. A pending async tool result is *not* flagged; clients already derive "waiting for tool result" from the trailing unresolved `ToolCall` in the history * Dead-session replays (`attach_client`) send `in_progress: false`; remote message prefixing passes the field through ## Clients * CLI `daemon_client` only synthesizes the end-of-replay `ResponseDone` marker when `!in_progress`, so the spinner state implied by the end of the history stays live; `ResponseDone` continues to preserve `WaitingToolCall` for trailing tool calls * Terminal: `ThinkingStart` now sets the spinner to `Thinking` unconditionally (replays have no preceding `StartOutput`) * Web: end of replay mirrors a live `ResponseDone` (`tool` survives, everything else clears) instead of always clearing, and skips the implicit done entirely when `in_progress`; `ToolResult` now switches the spinner back to thinking like the terminal does ## Tests * `thread_worker`: subscribing mid-thinking yields a replay ending with the in-progress thinking and `in_progress: true`; after the chain closes no stale thinking is replayed; waiting-for-tool-result replays have `in_progress: false` with the trailing `ToolCall` last in the history * TUI snapshot test: an in-progress replay ending mid-thinking keeps the thinking spinner (no marker), and the subsequent live completion clears it Co-authored-by: Infinity 🤖 <infinity@hydro.run> PR: #60
ea4d339 to
c29d75c
Compare
shadaj
added a commit
that referenced
this pull request
Jul 10, 2026
…to clients attaching mid-response A client connecting to a running agent got a history `Replay` that always ended in an idle state: streamed reasoning is only committed to history once complete, and the CLI unconditionally appended an end-of-replay `ResponseDone` marker that cleared the spinner. The client would appear idle even though the model was actively thinking. ## Daemon * `thread_worker` keeps the in-progress thinking text in a per-worker `Rc<RefCell<Option<String>>>`: the display-event forwarder accumulates `ThinkingChunk`s and clears on anything that moves the stream past the chain (thinking end, text, tool call/result, response done) * On subscribe, the replayed history is extended with `ThinkingStart` + `ThinkingChunk` for the buffered thinking, so clients recompute a live "thinking" state from the end of the replay * `DaemonMessage::Replay` gains `#[serde(default)] in_progress: bool` — true iff a completion is currently in flight. A pending async tool result is *not* flagged; clients already derive "waiting for tool result" from the trailing unresolved `ToolCall` in the history * Dead-session replays (`attach_client`) send `in_progress: false`; remote message prefixing passes the field through ## Clients * CLI `daemon_client` only synthesizes the end-of-replay `ResponseDone` marker when `!in_progress`, so the spinner state implied by the end of the history stays live; `ResponseDone` continues to preserve `WaitingToolCall` for trailing tool calls * Terminal: `ThinkingStart` now sets the spinner to `Thinking` unconditionally (replays have no preceding `StartOutput`) * Web: end of replay mirrors a live `ResponseDone` (`tool` survives, everything else clears) instead of always clearing, and skips the implicit done entirely when `in_progress`; `ToolResult` now switches the spinner back to thinking like the terminal does ## Tests * `thread_worker`: subscribing mid-thinking yields a replay ending with the in-progress thinking and `in_progress: true`; after the chain closes no stale thinking is replayed; waiting-for-tool-result replays have `in_progress: false` with the trailing `ToolCall` last in the history * TUI snapshot test: an in-progress replay ending mid-thinking keeps the thinking spinner (no marker), and the subsequent live completion clears it Co-authored-by: Infinity 🤖 <infinity@hydro.run> PR: #60
c29d75c to
2c42da0
Compare
shadaj
added a commit
that referenced
this pull request
Jul 10, 2026
…to clients attaching mid-response A client connecting to a running agent got a history `Replay` that always ended in an idle state: streamed reasoning is only committed to history once complete, and the CLI unconditionally appended an end-of-replay `ResponseDone` marker that cleared the spinner. The client would appear idle even though the model was actively thinking. ## Daemon * `thread_worker` keeps the in-progress thinking text in a per-worker `Rc<RefCell<Option<String>>>`: the display-event forwarder accumulates `ThinkingChunk`s and clears on anything that moves the stream past the chain (thinking end, text, tool call/result, response done) * On subscribe, the replayed history is extended with `ThinkingStart` + `ThinkingChunk` for the buffered thinking, so clients recompute a live "thinking" state from the end of the replay * `DaemonMessage::Replay` gains `#[serde(default)] in_progress: bool` — true iff a completion is currently in flight. A pending async tool result is *not* flagged; clients already derive "waiting for tool result" from the trailing unresolved `ToolCall` in the history * Dead-session replays (`attach_client`) send `in_progress: false`; remote message prefixing passes the field through ## Clients * CLI `daemon_client` only synthesizes the end-of-replay `ResponseDone` marker when `!in_progress`, so the spinner state implied by the end of the history stays live; `ResponseDone` continues to preserve `WaitingToolCall` for trailing tool calls * Terminal: `ThinkingStart` now sets the spinner to `Thinking` unconditionally (replays have no preceding `StartOutput`) * Web: end of replay mirrors a live `ResponseDone` (`tool` survives, everything else clears) instead of always clearing, and skips the implicit done entirely when `in_progress`; `ToolResult` now switches the spinner back to thinking like the terminal does ## Tests * `thread_worker` unit tests: subscribing mid-thinking yields a replay ending with the in-progress thinking and `in_progress: true`; after the chain closes no stale thinking is replayed; waiting-for-tool-result replays have `in_progress: false` with the trailing unresolved `ToolCall` last in history * e2e TUI (`e2e_daemon_tui`): `switch_back_mid_thinking_revives_spinner` runs the real daemon + TUI client, streams reasoning deltas mid-completion, switches away (`/new`) and back (`/load`) with a single client, and snapshots the live, post-reconnect (spinner revived with buffered thought), and finished screens * e2e web (`web_e2e`, Playwright): `reload_mid_thinking_keeps_spinner` reloads the page mid-thinking, reconnects, and asserts the thinking text and "Thinking…" spinner are restored and clear once the stream finishes live — with golden screenshots of the live, reconnected, and finished states Co-authored-by: Infinity 🤖 <infinity@hydro.run> PR: #60
2c42da0 to
c4ba863
Compare
shadaj
added a commit
that referenced
this pull request
Jul 13, 2026
…to clients attaching mid-response A client connecting to a running agent got a history `Replay` that always ended in an idle state: streamed reasoning is only committed to history once complete, and the CLI unconditionally appended an end-of-replay `ResponseDone` marker that cleared the spinner. The client would appear idle even though the model was actively thinking. ## Daemon * `thread_worker` keeps the in-progress thinking text in a per-worker `Rc<RefCell<Option<String>>>`: the display-event forwarder accumulates `ThinkingChunk`s and clears on anything that moves the stream past the chain (thinking end, text, tool call/result, response done) * On subscribe, the replayed history is extended with `ThinkingStart` + `ThinkingChunk` for the buffered thinking, so clients recompute a live "thinking" state from the end of the replay * `DaemonMessage::Replay` gains `#[serde(default)] in_progress: bool` — true iff a completion is currently in flight. A pending async tool result is *not* flagged; clients already derive "waiting for tool result" from the trailing unresolved `ToolCall` in the history * Dead-session replays (`attach_client`) send `in_progress: false`; remote message prefixing passes the field through ## Clients * CLI `daemon_client` only synthesizes the end-of-replay `ResponseDone` marker when `!in_progress`, so the spinner state implied by the end of the history stays live; `ResponseDone` continues to preserve `WaitingToolCall` for trailing tool calls * Terminal: `ThinkingStart` now sets the spinner to `Thinking` unconditionally (replays have no preceding `StartOutput`) * Web: end of replay mirrors a live `ResponseDone` (`tool` survives, everything else clears) instead of always clearing, and skips the implicit done entirely when `in_progress`; `ToolResult` now switches the spinner back to thinking like the terminal does ## Tests * `thread_worker` unit tests: subscribing mid-thinking yields a replay ending with the in-progress thinking and `in_progress: true`; after the chain closes no stale thinking is replayed; waiting-for-tool-result replays have `in_progress: false` with the trailing unresolved `ToolCall` last in history * e2e TUI (`e2e_daemon_tui`): `switch_back_mid_thinking_revives_spinner` runs the real daemon + TUI client, streams reasoning deltas mid-completion, switches away (`/new`) and back (`/load`) with a single client, and snapshots the live, post-reconnect (spinner revived with buffered thought), and finished screens * e2e web (`web_e2e`, Playwright): `reload_mid_thinking_keeps_spinner` reloads the page mid-thinking, reconnects, and asserts the thinking text and "Thinking…" spinner are restored and clear once the stream finishes live — with golden screenshots of the live, reconnected, and finished states Co-authored-by: Infinity 🤖 <infinity@hydro.run> PR: #60
c4ba863 to
4005ddb
Compare
luckyworkama
approved these changes
Jul 13, 2026
shadaj
added a commit
that referenced
this pull request
Jul 13, 2026
…to clients attaching mid-response A client connecting to a running agent got a history `Replay` that always ended in an idle state: streamed reasoning is only committed to history once complete, and the CLI unconditionally appended an end-of-replay `ResponseDone` marker that cleared the spinner. The client would appear idle even though the model was actively thinking. ## Daemon * `thread_worker` keeps the in-progress thinking text in a per-worker `Rc<RefCell<Option<String>>>`: the display-event forwarder accumulates `ThinkingChunk`s and clears on anything that moves the stream past the chain (thinking end, text, tool call/result, response done) * On subscribe, the replayed history is extended with `ThinkingStart` + `ThinkingChunk` for the buffered thinking, so clients recompute a live "thinking" state from the end of the replay * `DaemonMessage::Replay` gains `#[serde(default)] in_progress: bool` — true iff a completion is currently in flight. A pending async tool result is *not* flagged; clients already derive "waiting for tool result" from the trailing unresolved `ToolCall` in the history * Dead-session replays (`attach_client`) send `in_progress: false`; remote message prefixing passes the field through ## Clients * CLI `daemon_client` only synthesizes the end-of-replay `ResponseDone` marker when `!in_progress`, so the spinner state implied by the end of the history stays live; `ResponseDone` continues to preserve `WaitingToolCall` for trailing tool calls * Terminal: `ThinkingStart` now sets the spinner to `Thinking` unconditionally (replays have no preceding `StartOutput`) * Web: end of replay mirrors a live `ResponseDone` (`tool` survives, everything else clears) instead of always clearing, and skips the implicit done entirely when `in_progress`; `ToolResult` now switches the spinner back to thinking like the terminal does ## Tests * `thread_worker` unit tests: subscribing mid-thinking yields a replay ending with the in-progress thinking and `in_progress: true`; after the chain closes no stale thinking is replayed; waiting-for-tool-result replays have `in_progress: false` with the trailing unresolved `ToolCall` last in history * e2e TUI (`e2e_daemon_tui`): `switch_back_mid_thinking_revives_spinner` runs the real daemon + TUI client, streams reasoning deltas mid-completion, switches away (`/new`) and back (`/load`) with a single client, and snapshots the live, post-reconnect (spinner revived with buffered thought), and finished screens * e2e web (`web_e2e`, Playwright): `reload_mid_thinking_keeps_spinner` reloads the page mid-thinking, reconnects, and asserts the thinking text and "Thinking…" spinner are restored and clear once the stream finishes live — with golden screenshots of the live, reconnected, and finished states Co-authored-by: Infinity 🤖 <infinity@hydro.run> PR: #60
4005ddb to
a785927
Compare
…to clients attaching mid-response A client connecting to a running agent got a history `Replay` that always ended in an idle state: streamed reasoning is only committed to history once complete, and the CLI unconditionally appended an end-of-replay `ResponseDone` marker that cleared the spinner. The client would appear idle even though the model was actively thinking. ## Daemon * `thread_worker` keeps the in-progress thinking text in a per-worker `Rc<RefCell<Option<String>>>`: the display-event forwarder accumulates `ThinkingChunk`s and clears on anything that moves the stream past the chain (thinking end, text, tool call/result, response done) * On subscribe, the replayed history is extended with `ThinkingStart` + `ThinkingChunk` for the buffered thinking, so clients recompute a live "thinking" state from the end of the replay * `DaemonMessage::Replay` gains `#[serde(default)] in_progress: bool` — true iff a completion is currently in flight. A pending async tool result is *not* flagged; clients already derive "waiting for tool result" from the trailing unresolved `ToolCall` in the history * Dead-session replays (`attach_client`) send `in_progress: false`; remote message prefixing passes the field through ## Clients * CLI `daemon_client` only synthesizes the end-of-replay `ResponseDone` marker when `!in_progress`, so the spinner state implied by the end of the history stays live; `ResponseDone` continues to preserve `WaitingToolCall` for trailing tool calls * Terminal: `ThinkingStart` now sets the spinner to `Thinking` unconditionally (replays have no preceding `StartOutput`) * Web: end of replay mirrors a live `ResponseDone` (`tool` survives, everything else clears) instead of always clearing, and skips the implicit done entirely when `in_progress`; `ToolResult` now switches the spinner back to thinking like the terminal does ## Tests * `thread_worker` unit tests: subscribing mid-thinking yields a replay ending with the in-progress thinking and `in_progress: true`; after the chain closes no stale thinking is replayed; waiting-for-tool-result replays have `in_progress: false` with the trailing unresolved `ToolCall` last in history * e2e TUI (`e2e_daemon_tui`): `switch_back_mid_thinking_revives_spinner` runs the real daemon + TUI client, streams reasoning deltas mid-completion, switches away (`/new`) and back (`/load`) with a single client, and snapshots the live, post-reconnect (spinner revived with buffered thought), and finished screens * e2e web (`web_e2e`, Playwright): `reload_mid_thinking_keeps_spinner` reloads the page mid-thinking, reconnects, and asserts the thinking text and "Thinking…" spinner are restored and clear once the stream finishes live — with golden screenshots of the live, reconnected, and finished states Co-authored-by: Infinity 🤖 <infinity@hydro.run> PR: #60
a785927 to
59bf1ec
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A client connecting to a running agent got a history
Replaythat always endedin an idle state: streamed reasoning is only committed to history once complete,
and the CLI unconditionally appended an end-of-replay
ResponseDonemarker thatcleared the spinner. The client would appear idle even though the model was
actively thinking.
Daemon
thread_workerkeeps the in-progress thinking text in a per-workerRc<RefCell<Option<String>>>: the display-event forwarder accumulatesThinkingChunks and clears on anything that moves the stream past the chain(thinking end, text, tool call/result, response done)
ThinkingStart+ThinkingChunkfor the buffered thinking, so clients recompute a live"thinking" state from the end of the replay
DaemonMessage::Replaygains#[serde(default)] in_progress: bool— true iffa completion is currently in flight. A pending async tool result is not
flagged; clients already derive "waiting for tool result" from the trailing
unresolved
ToolCallin the historyattach_client) sendin_progress: false; remotemessage prefixing passes the field through
Clients
daemon_clientonly synthesizes the end-of-replayResponseDonemarkerwhen
!in_progress, so the spinner state implied by the end of the historystays live;
ResponseDonecontinues to preserveWaitingToolCallfortrailing tool calls
ThinkingStartnow sets the spinner toThinkingunconditionally(replays have no preceding
StartOutput)ResponseDone(toolsurvives, everythingelse clears) instead of always clearing, and skips the implicit done entirely
when
in_progress;ToolResultnow switches the spinner back to thinkinglike the terminal does
Tests
thread_worker: subscribing mid-thinking yields a replay ending with thein-progress thinking and
in_progress: true; after the chain closes no stalethinking is replayed; waiting-for-tool-result replays have
in_progress: falsewith the trailingToolCalllast in the historythinking spinner (no marker), and the subsequent live completion clears it