Skip to content

fix(infinity-daemon): replay in-progress thinking and response state to clients attaching mid-response#60

Merged
shadaj merged 1 commit into
mainfrom
sandbox-f0264431-af62-4fe9-b3ad-4ae12efeb5ce
Jul 13, 2026
Merged

fix(infinity-daemon): replay in-progress thinking and response state to clients attaching mid-response#60
shadaj merged 1 commit into
mainfrom
sandbox-f0264431-af62-4fe9-b3ad-4ae12efeb5ce

Conversation

@shadaj

@shadaj shadaj commented Jul 8, 2026

Copy link
Copy Markdown
Member

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
    ThinkingChunks 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

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 8, 2026

Copy link
Copy Markdown

Deploying infinity with  Cloudflare Pages  Cloudflare Pages

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

View logs

@shadaj shadaj marked this pull request as ready for review July 8, 2026 22:30
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
@shadaj shadaj force-pushed the sandbox-f0264431-af62-4fe9-b3ad-4ae12efeb5ce branch from ea4d339 to c29d75c Compare July 8, 2026 22:34
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
@shadaj shadaj force-pushed the sandbox-f0264431-af62-4fe9-b3ad-4ae12efeb5ce branch from c29d75c to 2c42da0 Compare July 10, 2026 21:49
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
@shadaj shadaj force-pushed the sandbox-f0264431-af62-4fe9-b3ad-4ae12efeb5ce branch from 2c42da0 to c4ba863 Compare July 10, 2026 22:06
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
@shadaj shadaj force-pushed the sandbox-f0264431-af62-4fe9-b3ad-4ae12efeb5ce branch from c4ba863 to 4005ddb Compare July 13, 2026 18:07
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
@shadaj shadaj force-pushed the sandbox-f0264431-af62-4fe9-b3ad-4ae12efeb5ce branch from 4005ddb to a785927 Compare July 13, 2026 20:27
…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
@shadaj shadaj force-pushed the sandbox-f0264431-af62-4fe9-b3ad-4ae12efeb5ce branch from a785927 to 59bf1ec Compare July 13, 2026 20:35
@shadaj shadaj merged commit 7a6e971 into main Jul 13, 2026
8 checks passed
@shadaj shadaj deleted the sandbox-f0264431-af62-4fe9-b3ad-4ae12efeb5ce branch July 13, 2026 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants