Skip to content

fix: implement extension-driven session switching (/pr, /work resume) - #582

Closed
tongdu99 wants to merge 1 commit into
agegr:mainfrom
tongdu99:fix/extension-session-switch
Closed

fix: implement extension-driven session switching (/pr, /work resume)#582
tongdu99 wants to merge 1 commit into
agegr:mainfrom
tongdu99:fix/extension-session-switch

Conversation

@tongdu99

Copy link
Copy Markdown

Problem

Extension commands that replace the running session — ctx.switchSession() and ctx.newSession(), used by extensions such as /pr and /work — always failed in Pi Web.

Both command-context actions in lib/rpc-manager.ts were stubbed to return { cancelled: true }, so:

  • answering "Resume it?" immediately printed Resume cancelled
  • the fresh-session path printed New session cancelled

Fix

Pi Web does not use the SDK's AgentSessionRuntime, so rpc-manager.ts now reimplements that replacement flow:

  • emit session_before_switch, so only a real extension guard (e.g. a dirty-worktree prompt) can cancel
  • emit a session_switch event to connected browsers before teardown, since the wrapper's event listeners disappear with it
  • retire any wrapper already holding the target session, abort() + emit session_shutdown for the current one, then start the replacement with sessionStartEvent.reason new/resume so session_start handlers (branch checkout, etc.) run exactly like they do in the TUI
  • run the extension's withSession() against the replacement's createReplacedSessionContext() after waitUntilReady()

startRpcSession() gained two options for this: sessionManager (adopt an already-open manager) and sessionStartEvent (report the switch reason). Sessions created via ctx.newSession() are flushed to disk immediately (persistSessionFileIfMissing) because pi otherwise delays the first flush until an assistant message exists, while the browser navigates to the new session right away.

Client side, useAgentSession handles the new session_switch event and follows it via onSessionSwitched -> AppShell's existing select / hydrate / URL-replace path (the same navigation used after a fork).

Files

  • lib/rpc-manager.ts — replacement flow: emitBeforeSwitch, replaceWithNewSession, replaceWithExistingSession, finishSessionReplacement, teardownForReplacement, persistSessionFileIfMissing, new start options
  • lib/pi-types.ts — widened ExtensionRunnerLike.emit, added hasHandlers and createReplacedSessionContext
  • hooks/useAgentSession.ts, components/ChatWindow.tsx, components/AppShell.tsxsession_switch handling / onSessionSwitched plumbing
  • lib/rpc-manager-session-switch.test.mjs — new source-assertion tests
  • AGENTS.md — documents the flow

Verification

  • tsc --noEmit, npm run lint, npm test (593 tests) all pass
  • Manually verified: /pr <number> -> "Resume" now switches the browser to the existing PR session and checks out its branch

Extension commands that replace the running session (`ctx.switchSession()`,
`ctx.newSession()` — used by /pr, /work, etc.) always failed in Pi Web: both
command-context actions were stubbed to return `{ cancelled: true }`, so
answering "Resume it?" immediately reported "Resume cancelled" and the fresh
session path reported "New session cancelled".

Pi Web has no AgentSessionRuntime, so rpc-manager now reimplements that flow:

- emit `session_before_switch` so only a real extension guard (e.g. the
  dirty-worktree prompt) can cancel
- emit a `session_switch` event to connected browsers BEFORE teardown, since
  the wrapper's listeners disappear with it
- retire any wrapper already holding the target, abort + `session_shutdown`
  the current one, then start the replacement with `sessionStartEvent.reason`
  `new`/`resume` so `session_start` handlers (branch checkout) run like in the TUI
- run the extension's `withSession()` against the replacement's
  `createReplacedSessionContext()` after `waitUntilReady()`

Sessions created by `ctx.newSession()` are flushed to disk immediately, because
pi delays the first flush until an assistant message exists while the browser
navigates to the new session right away.

Client side, `useAgentSession` handles `session_switch` and follows it through
`onSessionSwitched` -> AppShell's existing select/hydrate/URL-replace path.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1120d1d619

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/rpc-manager.ts
Comment on lines +1485 to +1486
const openTarget = getRegistry().get(sessionId);
if (openTarget && openTarget !== this && openTarget.isAlive()) await openTarget.shutdown();

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 Badge Refuse to retire a busy switch target

When /work resumes a session whose wrapper is currently running in another tab, this unconditionally calls shutdown(), which destroys that wrapper without waiting for its active prompt to finish. The in-flight target run and its event stream can therefore be cut off merely by switching into it; check openTarget.isRunning() and cancel/reject the switch, or explicitly coordinate with the active run, rather than retiring a busy target.

Useful? React with 👍 / 👎.

Comment thread hooks/useAgentSession.ts
Comment on lines +1041 to +1042
if (typeof targetSessionId === "string" && targetSessionId !== sessionIdRef.current) {
onSessionSwitched?.(targetSessionId);

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 Badge Reconnect when a switch reuses the same session ID

When an extension calls ctx.switchSession() with the active session's own file, the server still destroys and recreates its wrapper, but this guard suppresses the navigation/remount because the ID is unchanged. The existing SSE remains attached to the destroyed wrapper, so a withSession run will not stream live events and may leave the UI stale until reconciliation; follow the switch even when the target ID equals the current ID so the client reconnects.

AGENTS.md reference: AGENTS.md:L129-L129

Useful? React with 👍 / 👎.

@tongdu99 tongdu99 closed this Aug 25, 2026
@tongdu99
tongdu99 deleted the fix/extension-session-switch branch August 25, 2026 01:42
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