Skip to content

cmux-next agent pane: keep the session picker, history and lag recovery in sync with the daemon - #16426

Merged
teamleaderleo merged 11 commits into
feat-cmux-nextfrom
feat-cmux-next-agent-pane-ts-followups
Oct 1, 2026
Merged

teamleaderleo merged 11 commits into
feat-cmux-nextfrom
feat-cmux-next-agent-pane-ts-followups

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

These are the four client-side fixes from #16203 that the React agent pane (#16231) didn't include, ported to webviews/src/agent-session/acpmux/direct.ts:

  • Session picker: it refreshes on every session-list change. Before, a session created, updated or purged elsewhere left the picker stale.
  • Older history: it now pages through _acpmux/events instead of reattaching, and stops when the daemon replies more: false. Reattaching replaced the live summary and queue and dropped a pending permission. A page that lands after the selection changed is discarded.
  • Closed socket: a request on a socket that isn't open rejects at once instead of hanging.
  • _acpmux/lagged: it fetches the missed events and merges them, instead of being ignored. It follows the daemon's real notice shapes: {sessionIds, watch, dropped} from agent-gui daemons and {dropped} from older ones, where no sessionIds means the selected session. It pages from its own cursor until more is false, waits for the selected session's attach before resyncing, and rereads the session list when the watch stream lagged. Prompts still in flight keep their optimistic rows.
  • Switching sessions: selecting a session resets all of the previous session's client state (queue, summary, permission, pending prompt rows and message mapping) through one resetSessionState helper.

The bundled agent-pane/index.html is rebuilt.

Verification

  • bun test src/agent-session/acpmux/ in webviews/:
    • At 16c261e (the tests only): 24 pass, 7 fail. The 7 new client tests fail, as intended.
    • At head: 38 pass, 0 fail.
    • At 154cdbb, after a review against the acpmux daemon's sources found that lag recovery read a sessionId the daemon never sends, the fixtures switched to the real shapes and 6 tests failed. 4d6b7f5 fixes them. A second review found that lag paging skipped ranges when live events landed mid-resync; 2c57221 (2 red tests) and 276e7e8 fix that.
  • bun run test (268 pass), typecheck and lint:ci pass.
  • build-agent-pane-web.sh --check and build-webviews-app.sh --check pass.
  • The changes are TypeScript only. No in-app dogfood against a live acpmux daemon yet.

Changelog

none

🤖 Generated with Claude Code

https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Keeps the cmux-next agent pane's session picker, history paging, and lag recovery in sync with the daemon, porting four client-side fixes from #16203 into AcpmuxDirectClient and matching the daemon's actual wire shapes.

Behavior changes

  • The session picker refreshes on every session-list change, and a lagged watch stream rereads the list.
  • Older history pages through _acpmux/events instead of reattaching, preserving the live summary, queue, and pending permission; a page landing after a selection change is discarded, and paging stops when more is false.
  • Requests on a socket that isn't open reject immediately instead of hanging.
  • _acpmux/lagged follows the daemon's shapes ({sessionIds, watch, dropped} or {dropped} only): it resyncs the selected session, merges missed events, and keeps in-flight prompts' optimistic rows.
  • Lag recovery pages from its own cursor instead of the shared lastSeq, so a live event landing mid-resync no longer makes the next page skip the rest of the gap, and it ignores a lag notice until the selected session's attach reply has landed.
  • Switching sessions resets all of the previous session's state via one resetSessionState helper.
  • Adds client tests over a scripted socket and rebuilds the bundled agent-pane/index.html.

The branch is merged up to date with feat-cmux-next; the only change from that merge is the rebuilt bundle.

Written for commit 89fb8df. Summary will update on new commits.

Review in cubic

teamleaderleo and others added 6 commits October 1, 2026 13:42
…readiness, lag recovery and select reset

Seven client tests drive AcpmuxDirectClient over a scripted socket. All
fail on this commit: an unselected purge leaves the picker stale, history
reattaches instead of paging _acpmux/events, a request on a closed socket
resolves silently, _acpmux/lagged is ignored, and select keeps the old
session's queue, summary and permission.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD
sessionChanged emitted only when the selected session changed, so a
session created, updated or purged elsewhere left the picker stale until
some other event. It now emits after every list mutation, including the
unselected purge branch. Ported from #16203 (0b8b161).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD
select cleared only rows, sequence, turn, streaming and permission, so the
old session's queue, summary, optimistic prompt rows, superseded IDs,
message mapping and assistant message ID lingered until the new attach
replaced some of them. One resetSessionState helper now does the full
reset for select, the open missing-session path and the selected purge
path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD
request sent through socket?.send and waited forever when the socket was
absent or not open. It now rejects at once. The client also handles
_acpmux/lagged: it fetches _acpmux/events after the last seen sequence,
drops the reply if the session or selection generation changed, and
merges the missed events into the transcript instead of clearing it.
rebuild keeps optimistic rows for prompts still in flight, so a lag
replay no longer blanks a prompt the user just sent; the lag test now
covers that.

Ported from #16203 (a974f73, 8fc1387). The reconnect test's fake
socket gains readyState so the guard sees an open socket.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD
loadOlder reattached with beforeSeq, which replaced the live summary and
queue with the attach reply and rebuilt away the pending permission. It
now asks _acpmux/events for the page before the first loaded sequence,
captures the session and selection generation first and drops the reply
if either changed, merges the older events, and restores the live
summary, queue and permission after the rebuild. History stops when the
daemon reports no more, returns an empty page, or reaches sequence 1.
attach loses its now-unused beforeSeq parameter.

Ported from #16203 (8fc1387, guard from 4d91ad2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD
@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: e0df3c3b-fa34-4251-a188-75a8e7435ed2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

teamleaderleo and others added 2 commits October 1, 2026 13:48
…apes

The acpmux daemon sends _acpmux/lagged as {sessionIds, watch, dropped}
(agent-gui) or {dropped} (older daemons), never {sessionId}, and ends
_acpmux/events pages with `more`, not `hasMore`. With the real shapes the
client ignores every lag notice, stops lag recovery after one page,
leaves the picker stale after a watch lag, and keeps offering older
history after `more: false`. Six tests fail here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD
Lag recovery read a sessionId the daemon never sends, so it never ran.
It now resyncs the selected session when the notice lists it, or when
the notice has no sessionIds (older daemons send only dropped), pages
_acpmux/events until `more` is false, and rereads the session list when
the watch stream lagged. History paging reads `more` instead of
`hasMore`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Subagent review at 7eed115: REQUEST_CHANGES. _acpmux/lagged never carries sessionId (the daemon sends {sessionIds, watch, dropped} or {dropped}), so lag recovery never ran, and history read hasMore instead of more. Fixed in 154cdbb (red tests with the real shapes) and 4d6b7f5, which also pages lag recovery on more and refreshes the picker after a watch lag. A fresh review of the new head follows.

teamleaderleo and others added 2 commits October 1, 2026 13:54
…ore attach

Lag recovery pages from this.lastSeq, which live events advance between
pages, so a live event that lands mid-resync makes the next page skip the
rest of the gap. A lag notice that arrives while a newly selected
session's attach is in flight resyncs from seq 0. Both tests fail here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD
The resync loop now advances a local cursor to the newest seq of each
page instead of reading lastSeq, which live events move ahead mid-resync.
A lag notice is ignored until the selected session's attach reply has
landed, since that reply already carries the latest events and there is
no cursor before it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Subagent review at 4d6b7f5: REQUEST_CHANGES. The lag-resync loop paged from this.lastSeq, which live events advance between pages, so a gap longer than one page lost the rest. A lag notice during a select's attach also resynced from seq 0. Fixed in 2c57221 (red tests) and 276e7e8, which uses a page-local cursor and waits for attach. Still open, as non-blocking notes tracked in #16424:

  • after a watch lag that dropped a purge of the selected session, the pane doesn't reselect;
  • against a pre-agent-gui daemon, loadOlder can leave a gap because that daemon ignores beforeSeq.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Subagent review at 276e7e8: APPROVE.

  • Cursor: the page-local cursor matches how the agent-gui daemon orders lag notices: it drops events after a lag until its queue drains, so the notice precedes every event after the gap.
  • Attach gate: attachedGeneration never suppresses resync permanently on the connect, reconnect, missing-session, purge or attach-failure paths.
  • Dedup and termination: mergeEventRecords dedupes live and paged events, and the loop stops on more: false or an empty page.
  • Red then green: the 2 new tests fail against 4d6b7f5's direct.ts and pass at head.

Non-blocking, added to #16424:

  • the reconnect re-attach (limit: 400) can leave a gap after a long disconnect (this predates the PR);
  • the status can stay at "resyncing" if the fetch fails;
  • the missing-session reconnect path pages full history.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="webviews/src/agent-session/acpmux/direct.ts">

<violation number="1" location="webviews/src/agent-session/acpmux/direct.ts:249">
P1: Lag recovery can erase a live permission prompt when the prompt exists only in the `_acpmux/permission_pending` notification: `rebuild()` clears `pendingPermission`, and this path does not preserve it as `loadOlder()` does. Preserve or reconcile the current permission while replaying missed events.</violation>

<violation number="2" location="webviews/src/agent-session/acpmux/direct.ts:259">
P2: A watch lag that dropped the selected session’s purge leaves that deleted session selected after this refresh. Reconcile a missing `selectedSessionId` using the same fallback/reset/attach behavior as the purge handler.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

if (generation !== this.selectionGeneration || this.selectedSessionId !== sessionId) return;
const missed: EventRecord[] = result?.events ?? [];
this.events = mergeEventRecords(this.events, missed);
this.rebuild();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Lag recovery can erase a live permission prompt when the prompt exists only in the _acpmux/permission_pending notification: rebuild() clears pendingPermission, and this path does not preserve it as loadOlder() does. Preserve or reconcile the current permission while replaying missed events.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/direct.ts, line 245:

<comment>Lag recovery can erase a live permission prompt when the prompt exists only in the `_acpmux/permission_pending` notification: `rebuild()` clears `pendingPermission`, and this path does not preserve it as `loadOlder()` does. Preserve or reconcile the current permission while replaying missed events.</comment>

<file context>
@@ -213,21 +220,53 @@ export class AcpmuxDirectClient {
+      if (generation !== this.selectionGeneration || this.selectedSessionId !== sessionId) return;
+      const missed: EventRecord[] = result?.events ?? [];
+      this.events = mergeEventRecords(this.events, missed);
+      this.rebuild();
+      if (result?.more !== true || missed.length === 0) break;
+    }
</file context>

/// session_changed notices dropped by a watch lag: reread the whole list.
private async refreshSessions(): Promise<void> {
const watched = await this.request("_acpmux/watch", { enabled: true });
this.sessions = (watched?.sessions ?? []).filter((session: Session) => session.sessionId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: A watch lag that dropped the selected session’s purge leaves that deleted session selected after this refresh. Reconcile a missing selectedSessionId using the same fallback/reset/attach behavior as the purge handler.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/direct.ts, line 254:

<comment>A watch lag that dropped the selected session’s purge leaves that deleted session selected after this refresh. Reconcile a missing `selectedSessionId` using the same fallback/reset/attach behavior as the purge handler.</comment>

<file context>
@@ -213,21 +220,53 @@ export class AcpmuxDirectClient {
+  /// session_changed notices dropped by a watch lag: reread the whole list.
+  private async refreshSessions(): Promise<void> {
+    const watched = await this.request("_acpmux/watch", { enabled: true });
+    this.sessions = (watched?.sessions ?? []).filter((session: Session) => session.sessionId);
+    this.emit("session changed");
   }
</file context>

…ext-agent-pane-ts-followups

# Conflicts:
#	Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/Resources/agent-pane/index.html
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

89fb8df merges feat-cmux-next (#16425) into the reviewed head 276e7e8. The only conflict was the generated agent-pane/index.html, rebuilt from the merged sources; build-agent-pane-web.sh --check passes. After the merge, webviews bun run test (269 pass), typecheck, lint:ci and build-webviews-app.sh --check pass. There are no hand edits beyond the reviewed commits.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Merging with only cmux-next swift test outstanding. That's the lane's existing debt, owned by cx-next-swift-tests (same as #16206, #16231 and #16425), and this PR changes only TypeScript and the generated bundle. Every other check is green, and a fresh review approved 276e7e8; 89fb8df only merges the base in and regenerates the bundle.

@teamleaderleo
teamleaderleo merged commit 2e5d1ea into feat-cmux-next Oct 1, 2026
51 of 52 checks passed
@teamleaderleo
teamleaderleo deleted the feat-cmux-next-agent-pane-ts-followups branch October 1, 2026 18:27
teamleaderleo added a commit that referenced this pull request Oct 1, 2026
… after #16426 (#16452)

* cmux-next agent pane: add failing tests for lag, reconnect and registry gaps

Covers the #16424 follow-ups: lag resync dropping the live permission,
a failed lag fetch stuck on "resyncing", a reconnect that switches the
missing selection without bumping the generation, the event gap after a
reconnect re-attach, a watch lag dropping the selected session, close()
leaving requests pending, failed prompt rows lost on rebuild, the
open-at-latest jump on a height-only shrink, and register() re-rendering
for an unchanged renderer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD

* cmux-next agent pane: close the direct client's lag and reconnect gaps

- Lag resync keeps the live summary, queue and permission through the
  rebuild (shared with loadOlder) and replays the missed mux events on
  top, so a missed permission decision or status still lands.
- A failed lag fetch ends "resyncing" with "failed", or "disconnected"
  when the socket is gone.
- A connect that finds the selected session missing bumps the selection
  generation, so a lag notice before the new attach waits for it.
- A reconnect re-attach of the same session fetches the events between
  the old cursor and the attach page.
- A watch-lag refresh that no longer lists the selected session falls
  back to the most recent one, like a purge.
- close() rejects pending requests; onclose ignores a released socket.
- rebuild() keeps failed optimistic prompt rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD

* cmux-next agent pane: open at latest on a height-only shrink and skip no-op registers

The open-at-latest effect also runs when the viewport height changes, so
rows that fit and then overflow after a vertical shrink still jump to the
latest row once. register() returns early when the kind already has that
renderer and measure, so a user renderer registering from an effect does
not loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD

* cmux-next agent pane: rebuild the bundled pane

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RYQHfug1ZVQDp4eWgwVUtD

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant