feat(btw): browse mode, token tracking, inline commands, and SDK fix#41
feat(btw): browse mode, token tracking, inline commands, and SDK fix#41Fatih0234 wants to merge 2 commits into
Conversation
- Fix SDK API: replaceMessages -> state.messages assignment - Add Tab toggle for transcript browse mode (↑↓ navigate, PgUp/PgDn) - Add multi-select: v toggles, s toggles line, i injects into main chat - Add copy to clipboard (c) in browse mode - Add model/provider badge in overlay title - Add per-message and total token tracking - Add error recovery: empty Enter retries failed prompt - Add inline slash commands: /clear, /inject - Remove hardcoded 6-message transcript limit - Fix overlay runtime cleanup after inject close
There was a problem hiding this comment.
Pull request overview
This PR enhances the BTW side-chat overlay by adding transcript browse/selection capabilities, token/model visibility, inline commands, improved error retry behavior, and an SDK compatibility fix for seeding messages.
Changes:
- Added transcript browse mode (Tab), navigation (↑/↓, PgUp/PgDn), multi-select, copy, and inject-to-main-chat behavior.
- Displayed per-response model/token info plus a running token total in the overlay status/title.
- Fixed SDK usage by replacing
session.agent.replaceMessages(...)withsession.agent.state.messages = ..., and improved overlay lifecycle cleanup.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (!question) { | ||
| if (pendingError && pendingQuestion) { | ||
| pendingError = null; | ||
| setOverlayDraft(""); |
There was a problem hiding this comment.
The retry path calls runBtwPrompt(ctx, pendingQuestion) even though ctx is typed as ExtensionContext | ExtensionCommandContext, while runBtwPrompt requires an ExtensionCommandContext. This will either fail type-checking or allow calling runBtwPrompt with a non-command context (which you already guard against later via "waitForIdle" in ctx). Add the same guard here or widen runBtwPrompt to accept the union and handle the non-command case.
| setOverlayDraft(""); | |
| setOverlayDraft(""); | |
| if (!("waitForIdle" in ctx)) { | |
| setOverlayStatus("BTW submit requires command context. Re-open with /btw."); | |
| return; | |
| } |
| overlayRuntime = null; | ||
| } | ||
| if (nextPrompt && ctx.hasUI) { | ||
| ctx.ui.setEditorText(nextPrompt); |
There was a problem hiding this comment.
Injecting selected text uses ctx.ui.setEditorText(nextPrompt), which overwrites whatever the user already has in the main editor. Other extensions append to the existing editor text via ctx.ui.getEditorText() first. Consider appending (or at least preserving existing content) so injection doesn’t unintentionally discard a draft prompt.
| ctx.ui.setEditorText(nextPrompt); | |
| const existingPrompt = ctx.ui.getEditorText(); | |
| const combinedPrompt = | |
| existingPrompt.length > 0 | |
| ? `${existingPrompt}${existingPrompt.endsWith("\n") ? "\n" : "\n\n"}${nextPrompt}` | |
| : nextPrompt; | |
| ctx.ui.setEditorText(combinedPrompt); |
| @@ -406,6 +569,13 @@ export default function (pi: ExtensionAPI) { | |||
| // Assistant message rendered as markdown | |||
| const mdLines = renderMarkdownLines(item.answer, width); | |||
| lines.push(...mdLines); | |||
|
|
|||
| // Model & token info | |||
| if (item.usage) { | |||
| const modelName = `${item.provider}/${item.model}`; | |||
| const tk = formatTokens(item.usage.totalTokens); | |||
| lines.push(theme.fg("dim", ` ${modelName} · ${tk} tokens`)); | |||
| } | |||
| lines.push(""); | |||
There was a problem hiding this comment.
getTranscriptLinesInner now renders the entire thread on every refresh (including Markdown rendering per item). For long side threads this can become noticeably slow and increase CPU usage during streaming (since renders are frequent). Consider caching rendered lines per thread entry and/or only rendering the visible window (based on scroll offset + height) instead of re-rendering the full transcript each time.
| const kb = this.keybindings; | ||
|
|
||
| if (kb.matches(data, "selectCancel")) { | ||
| if (this.selectMode) { | ||
| this.selectMode = false; | ||
| this.selectedLines.clear(); | ||
| this.tui.requestRender(); | ||
| return; | ||
| } | ||
| this.onDismissCallback(); | ||
| return; |
There was a problem hiding this comment.
handleInput checks kb.matches(data, "selectCancel"), but the rest of the codebase uses the standard cancel binding key (tui.select.cancel). If selectCancel isn't a real binding, Esc won’t work in browse/select mode (since Input.onEscape is bypassed there). Use the same cancel binding key used elsewhere (e.g., tui.select.cancel) so Esc reliably exits select/browse or closes the overlay.
- Fix cancel binding from nonexistent selectCancel to tui.select.cancel - Add ExtensionCommandContext guard before retrying failed prompts - Append injected BTW text to existing editor draft instead of overwriting - Cache rendered Markdown lines per thread entry to avoid re-rendering the full transcript on every refresh
Summary
This PR improves the BTW side-chat overlay with transcript navigation, token visibility, inline commands, and an SDK compatibility fix.
Bug Fixes
session.agent.replaceMessages()was not a valid method. Changed tosession.agent.state.messages = ...which matches the current SDK./btwfrom reopening until the agent was restarted.New Features
Tab Browse Mode
Multi-select & Inject
Token & Model Visibility
[provider/model]provider/model · X tokensError Recovery
Inline Slash Commands
/clear— resets the side thread/inject— summarizes the thread and injects into main chatOther
Testing
/btw, ask questions, press Tab to browse/btwcan be reopened after inject/btw→ "Continue previous conversation" works after overlay close