fix(tui): isolate subagent prompt routing#20108
fix(tui): isolate subagent prompt routing#20108diamondplated wants to merge 1 commit intoanomalyco:devfrom
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Fixes nested subagent navigation and prompt routing in the TUI by separating “direct child” navigation from sibling cycling and only rendering the prompt when it should be active (root session with no pending permissions/questions).
Changes:
- Introduces session navigation helper utilities for direct-child and sibling session selection plus prompt render gating.
- Updates the TUI session route to use the new navigation helpers and change prompt mounting behavior.
- Adds unit tests for the new session navigation helpers.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/opencode/test/cli/tui/session-navigation.test.ts | Adds tests covering direct-child selection, sibling selection, and prompt render gating. |
| packages/opencode/src/cli/cmd/tui/routes/session/navigation.ts | Adds helper functions to compute direct children, siblings, and whether the session prompt should render. |
| packages/opencode/src/cli/cmd/tui/routes/session/index.tsx | Refactors navigation logic to use helpers and conditionally mounts/unmounts the prompt to prevent misrouted input. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const directChildren = createMemo(() => getDirectChildSessions(sync.data.session, session()?.id)) | ||
| const siblingSessions = createMemo(() => getSiblingSessions(sync.data.session, session())) | ||
| const messages = createMemo(() => sync.data.message[route.sessionID] ?? []) | ||
| const permissions = createMemo(() => { | ||
| if (session()?.parentID) return [] | ||
| return children().flatMap((x) => sync.data.permission[x.id] ?? []) | ||
| return directChildren().flatMap((x) => sync.data.permission[x.id] ?? []) | ||
| }) | ||
| const questions = createMemo(() => { | ||
| if (session()?.parentID) return [] | ||
| return children().flatMap((x) => sync.data.question[x.id] ?? []) | ||
| return directChildren().flatMap((x) => sync.data.question[x.id] ?? []) |
There was a problem hiding this comment.
permissions/questions now aggregate only over directChildren(), which excludes the current root session. That means permission/question requests originating from the root session itself won’t be surfaced here, and shouldRenderSessionPrompt may incorrectly render the prompt while root-level requests exist. Include the current session in the aggregation (e.g., combine session()?.id with direct children) or otherwise ensure root session requests are counted and displayed.
| createEffect(() => { | ||
| if ( | ||
| shouldRenderSessionPrompt({ | ||
| session: session(), | ||
| permissionCount: permissions().length, | ||
| questionCount: questions().length, | ||
| }) | ||
| ) { | ||
| return | ||
| } | ||
| promptRef.set(undefined) | ||
| }) |
There was a problem hiding this comment.
Prompt is now conditionally unmounted, but this effect only clears promptRef and leaves the local prompt variable intact. Many command handlers in this component call prompt.set(...) without guarding, which can now throw (or target a stale ref) when the prompt isn’t rendered (subagent view / permission prompt / question prompt). Consider making prompt optional and clearing it alongside promptRef, and update call sites to use promptRef.current (or optional chaining) so commands don’t crash when the prompt is not mounted.
Issue for this PR
Closes #4422
Type of change
What does this PR do?
This fixes nested subagent navigation and prompt routing in the TUI. It separates direct-child navigation from sibling cycling and only mounts the prompt when it should actually be active, so hidden prompts stop routing input to the wrong session.
How did you verify your code works?
bun test test/cli/tui/session-navigation.test.tsbun run typecheckinpackages/opencodeScreenshots / recordings
N/A
Checklist