Skip to content

fix: new-session button + sidebar resume don't actually switch Pi sessions (#52, #55, #57) - #69

Open
Elompenta wants to merge 11 commits into
deflating:mainfrom
Elompenta:fix-new-session-button
Open

fix: new-session button + sidebar resume don't actually switch Pi sessions (#52, #55, #57)#69
Elompenta wants to merge 11 commits into
deflating:mainfrom
Elompenta:fix-new-session-button

Conversation

@Elompenta

@Elompenta Elompenta commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

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-btn click 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 separate newSession() function in the same file that does the right thing (calls switchSession(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 — /new is a built-in TUI command, and expandPromptTemplates on sendUserMessage only dispatches extension-registered commands and prompt templates, not core ones (confirmed by reading pi-coding-agent's source: agent-session.js's prompt()/_tryExecuteExtensionCommand()). The real API is ctx.newSession() on ExtensionCommandContext, only available inside a registered command's handler.

So this PR:

  1. Registers an internal tau-new-session command whose handler calls ctx.newSession(), and has the WS new_session case dispatch it via sendUserMessage("/tau-new-session", { expandPromptTemplates: true }) — the same pattern used elsewhere in the Pi extension ecosystem for triggering extension-owned commands programmatically.
  2. Removes the dead newSession() function, replaced by the above.
  3. Fixes a pre-existing "pipe mode" filter bug in serveSessionsList()/parseSessionFile(): it hides sessions with userMessageCount <= 1 && lineCount <= 8 to avoid cluttering the list with non-interactive pi -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.
  4. Refreshes the sidebar when a turn ends (only for a session not yet listed — see point 3) and on WS reconnect, since a session file is only written to disk once it has its first assistant reply.
  5. Blocks repeat clicks on the button while a restart cycle is in flight: every session replacement tears down and restarts the whole mirror server (session_shutdownsession_start), dropping and re-establishing the WS connection; clicking again before that finishes raced the restart.
  6. Removes the live-session dot's pulsing animation. Confirmed via devtools that the animation property 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) on ExtensionCommandContext, 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 as tau-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 resulting session_shutdownsession_start cycle drops and re-establishes the WS connection exactly like the new-session flow, and the existing connected/mirrorSync handling 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):

  • New session: clicked "+" three times in a row, each with a message sent in between, confirming each new session appears in the sidebar immediately after its first reply and stays there without disturbing the live indicator on subsequent turns in the same session.
  • Resume: created two sessions, clicked the older one in the sidebar, confirmed the live dot moved to it, the session became writable again, and a new message sent afterward landed in the correct (resumed) session file. Repeated across a fresh page load and a brand-new tab to rule out stale client state.

Elompenta and others added 9 commits September 9, 2026 11:08
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>
…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>
@Elompenta Elompenta changed the title fix: new-session button doesn't actually start a new session (#52) fix: new-session button + sidebar resume don't actually switch Pi sessions (#52, #55, #57) Sep 9, 2026
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>
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.

every session is read-only Create new session button works unexpected

1 participant