fix: new-session button + sidebar resume don't actually switch Pi sessions (#52, #55, #57) - #69
Open
Elompenta wants to merge 11 commits into
Open
fix: new-session button + sidebar resume don't actually switch Pi sessions (#52, #55, #57)#69Elompenta wants to merge 11 commits into
Elompenta wants to merge 11 commits into
Conversation
The button only reset local browser UI state and never told the Pi backend to start a new session (the working newSession() function that called switchSession(null) was dead code, and the /api/sessions/switch endpoint it hits is a mirror-mode no-op anyway). Subsequent messages kept appending to the previous session. Add a new_session WebSocket command that sends /new to Pi, and broadcast a session_started event on session_start so the sidebar refreshes to show the new session. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sending literal "/new" via sendUserMessage never worked: it's a built-in TUI command, and sendUserMessage's expandPromptTemplates only dispatches extension-registered commands and prompt templates, not core commands - so it landed as plain chat text instead. Register an internal tau-new-session command whose handler calls ctx.newSession() (the real session-reset API, only available on ExtensionCommandContext), and invoke it via sendUserMessage with expandPromptTemplates, mirroring the pattern already used by sync-pi-settings's reload command. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…d broadcast session_start always follows session_shutdown, which tears down and restarts the whole mirror server - any client connected at that point is mid-reconnect and misses a broadcast sent right as session_start fires. Trigger the sidebar refresh from the client's own 'connected' event instead, which fires reliably on every (re)connect. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Each new session tears down and restarts the whole mirror server (session_shutdown -> session_start), dropping and re-establishing the WS connection. Clicking again before that finishes races the restart: the sidebar refresh can land mid-restart and show nothing, including losing the live-session indicator. Disable the button for the duration of one restart cycle. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A session file is only written once it has its first assistant reply (session-manager's _persist defers writing until hasAssistant), so a freshly created session stays invisible in /api/sessions - and thus in the Tau sidebar - until that first reply lands. The sidebar previously only refreshed on WS reconnect (i.e. the next new-session click), so a just-created session's own first reply never surfaced it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
serveSessionsList()'s pipe-mode heuristic (userMessageCount <= 1 && lineCount <= 8) meant to exclude non-interactive `pi -p "..."` runs from the sidebar, but a genuine interactive session's own bookkeeping entries (model_change x2, thinking_level_change, startup-doc) push a brand-new session with just its first exchange to exactly that same shape - so every new session stayed invisible until a second message was sent in it. Verified end-to-end: this was the actual cause behind every "new session doesn't show in the sidebar" report in this thread, including the "works once, then stops" pattern - each newly created session only ever had one exchange, so all of them hit this filter. Skip the exclusion when the file has entries only a real interactive run produces. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
handleAgentEnd() reloaded the whole sidebar on every turn to surface a freshly created session, but loadSessions() rebuilds the entire DOM - including the live session's dot, restarting its CSS pulse animation each time. In an active back-and-forth this fired every turn, making the dot flicker continuously. Only reload once, on the turn that actually first makes the session appear in the list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed via devtools: disabling the animation property alone stops the flickering the dot causes, independent of how often the sidebar re-renders. Static dot (color + glow) still communicates "live" without the distracting flash. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Never called anywhere - the incomplete predecessor of the new-session fix in this branch (it correctly called switchSession(null), but the button never invoked it). Left behind after b71ab87, it read as the "real" fix while sitting unused, which is exactly what made this bug hard to spot. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This was referenced Sep 9, 2026
…only Selecting a historical session only ever showed a read-only preview (deflating#57) - there was no way to make Pi actually switch to it, since ctx.switchSession() is only reachable from a registered command handler, same constraint as the new-session fix in deflating#52/deflating#55. Register an internal tau-switch-session command that calls ctx.switchSession(path), dispatched the same way as tau-new-session. Add a "Resume in Pi" context-menu item (hidden for the already-live session) that sends it and loads the session's history for immediate feedback; the resulting session_shutdown -> session_start cycle drops and re-establishes the WS connection, and the existing 'connected'/mirrorSync handling picks up the switch and moves the live indicator there. Verified end-to-end: created two sessions, resumed the older one from the sidebar, confirmed the live dot moved, the session became writable again, and a new message landed in it correctly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A right-click "Resume in Pi" item was extra friction for what should be the default behavior of selecting a session. Selecting a session now sends the switch_session command directly (skipped when it's already the live one, to avoid a pointless restart), same as clicking "Resume in Pi" did. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6 tasks
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.
Summary
Fixes #52 (and the duplicate report in #55): clicking the new-session ("+") button cleared the chat visually but never actually started a new Pi session — the next message kept appending to the old one, and nothing new showed up in the sidebar.
Also fixes #57: selecting a session from the sidebar only ever opened a read-only preview, with no way to actually make Pi switch to it — see the added section below.
Why the previous fix (b71ab87) wasn't enough
That commit added the
new-session-btnclick handler, but it only resets local browser UI state (cost display, message renderer, sidebar highlight). It never calls the backend at all. There's a separatenewSession()function in the same file that does the right thing (callsswitchSession(null)) — but nothing ever calls it. It's dead code that looks like the real fix while sitting unused, which is exactly what made this hard to spot in review.Even fixing just that wouldn't have been enough:
switchSession(null)posts to/api/sessions/switch, and that endpoint is a mirror-mode no-op stub ({success: true, mirror: true, note: "Session switching is controlled by the TUI in mirror mode"}) — it never touches the actual Pi session either way.What's actually going on, and the fix
Starting a new session from an extension isn't something
sendUserMessage("/new", …)can do —/newis a built-in TUI command, andexpandPromptTemplatesonsendUserMessageonly dispatches extension-registered commands and prompt templates, not core ones (confirmed by readingpi-coding-agent's source:agent-session.js'sprompt()/_tryExecuteExtensionCommand()). The real API isctx.newSession()onExtensionCommandContext, only available inside a registered command's handler.So this PR:
tau-new-sessioncommand whose handler callsctx.newSession(), and has the WSnew_sessioncase dispatch it viasendUserMessage("/tau-new-session", { expandPromptTemplates: true })— the same pattern used elsewhere in the Pi extension ecosystem for triggering extension-owned commands programmatically.newSession()function, replaced by the above.serveSessionsList()/parseSessionFile(): it hides sessions withuserMessageCount <= 1 && lineCount <= 8to avoid cluttering the list with non-interactivepi -p "..."one-shots — but a genuine fresh interactive session's own bookkeeping entries (model_change ×2, thinking_level_change, the startup-doc welcome message) push it to exactly that same shape with just one exchange. Every brand-new session was hitting this and staying invisible until a second message was sent in it — this is what made the bug look like "works once, then stops" when testing: each new session only ever had one exchange, so all of them tripped the filter. Now the filter is skipped when the file has entries only a real interactive/RPC run produces.session_shutdown→session_start), dropping and re-establishing the WS connection; clicking again before that finishes raced the restart.animationproperty alone (independent of how often the sidebar re-renders) made the dot flicker distractingly. Static dot (color + glow) still reads as "live".#57: resuming a session from the sidebar
Same underlying constraint as the new-session fix: switching sessions from an extension needs
ctx.switchSession(path)onExtensionCommandContext, which is only reachable from inside a registered command's handler — there was no path from the sidebar to that at all, so every non-live session was permanently read-only.Adds a second internal command,
tau-switch-session, dispatched the same way astau-new-session(with the session path as its argument). Selecting a session from the sidebar now sends it (skipped when that session is already the live one, to avoid a pointless restart) alongside loading its history for immediate feedback — no extra step needed, since browsing the sidebar to a session is already the expression of intent to switch to it. The resultingsession_shutdown→session_startcycle drops and re-establishes the WS connection exactly like the new-session flow, and the existingconnected/mirrorSynchandling this PR already added picks up the switch and moves the live indicator there — no separate plumbing needed.Testing
Reproduced and verified end-to-end against a real running Pi instance + browser (not just unit-level):