@link17528/pi-btw

Pi side-chat extension with flexible layouts, theme-aware colors, independent models and a lightweight input card

Packages

Package details

extension

Install @link17528/pi-btw from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@link17528/pi-btw
Package
@link17528/pi-btw
Version
1.0.0
Published
Oct 27, 2026
Downloads
333/mo · 333/wk
Author
link17528
License
MIT
Types
extension
Size
429.6 KB
Dependencies
0 dependencies · 5 peers
Pi manifest JSON
{
  "extensions": [
    "./srcs/index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

PiBtw

A Pi side-chat extension with flexible layouts, theme-aware colors and a lightweight input card. Based on yceachan/pi-better-btw. See UPSTREAM.md for project background and development setup.

English | 简体中文

[!note]

Built on yceachan's pi-better-btw, which grew from Nico Bailon's pi-side-chat.

TL;DR

Fork the current conversation into a side chat (btw) while the main agent keeps working.

License: MIT

Requires Pi 1.4 or later.

pi install npm:@link17528/pi-btw

Run /reload in Pi, then use /btw or Alt+W to open the side chat. To try it without installing:

pi -e npm:@link17528/pi-btw

You're in the middle of a longer task and want to ask something small without derailing the main thread — check an API detail, sanity-check an approach, search something, or peek at what the main agent is doing. Open the btw TUI overlay, ask, close it. The main thread never gets interrupted.

Feat

[!note]

That's Why It's Called Better-Btw

The author tried nicobailon/pi-side-chat and dbachelder/pi-btw — both are simple forks from the main lane: when the agent is on turn, both tend to try to advance the main line. See feat request: btw aside-session self-cognition — the side chat must not continue the main session's work · Issue #5 · nicobailon/pi-side-chat.

Hence the following features were carefully developed:

  • Aside-Agent self-Cognition: injects the main-lane context so you can ask a btw question about the project's main line. Careful context engineering strengthens the side agent's cognition and keeps the full main-lane context's tool-call traces from polluting it or competing with the main lane to advance the project. The shared main-lane prefix is preserved for good cache hits.

  • Prompt pack: every injected prompt is documented, with three-level overrides — bundle / $PI_HOME / $CWD.

  • TUI scroll, select, copy: subscribes to mouse/hotkey events in the TUI overlay for scrolling, text selection, and Ctrl+C copy.

  • Readonly/Edit Mode: read-only by default to answer btw questions; if you want the agent to make small edits along the way, Ctrl+T switches to edit mode.

    • ToolAllowList: bundle + config.json custom
    Mode Tools
    Read-only read, grep, find, ls; peek_main; config.json.readOnlyExtensionAllowlist
    Edit read, bash, edit, write

Usage

Open the side chat with /btw (alias /side) or Alt+W (which also toggles background/display). Ask a question and press Enter.

Esc opens a close-confirmation card (Keep is selected by default). Confirming aborts generation and discards the side-chat history; Esc inside a picker, confirmation or help page just returns to the chat. Use Alt+W to hide/restore without stopping generation; /btw also restores it. History, draft and scroll position remain intact while hidden. /btw-close requests confirmed close from the main editor. The old Ctrl+Alt+W shortcut is removed.

The default main footer shows [● side chat] beside its model: yellow while thinking/generating, green when idle, including while hidden. It disappears on close. Custom footers are never replaced: they receive a standard keyed status fallback and control its placement.

Inside the side chat, Ctrl+P cycles models in Pi's configured scope (or all available authenticated models), Ctrl+Shift+P cycles backward (Alt+P is a Windows-friendly alternative), and Shift+Tab cycles supported thinking levels. These actions follow your Pi keybindings; hints show the effective keys. Alt+M searches models and Alt+T opens the thinking picker. All changes affect only the side agent. Initial settings are inherited once, then independent and remembered for this Pi process, never written to main settings. Models without reasoning automatically use off. Stop a running turn with Alt+S before switching.

The borderless panel places the title and model details on one row, with a compact editor and hints below. Its background follows your main theme and terminal colors, slightly lighter in dark mode and darker in light mode. Only user messages have padded native message cards (without a [You] prefix); AI replies share the panel background. The editor is an inset, low-contrast card: an empty draft takes three rows including vertical padding, growing to at most five content rows while keeping the cursor visible. The faint › and “问点什么…” placeholder are presentation only, never draft text. A single hint row sits outside the card; narrow windows drop secondary actions before clipping keys, with the full list still available under ?. Keys and descriptions use the theme's text/muted colors rather than fixed white/gray. The full shortcut page has extra top, bottom and horizontal padding. Type ? on an empty draft for the full responsive shortcut page; Esc returns. In a nonempty draft, ? remains ordinary text. Assistant responses reuse Pi's main message renderer for Markdown, highlighted code, tables and thinking, including streamed output. The screenshot above shows the original interface.

Shortcut Action
Alt+W Open (when closed) / background (when visible) / restore (when hidden)
Ctrl+T Toggle read-only / edit mode
Alt+R Re-fork from the latest main context
Alt+N Start an empty conversation
Alt+E Export the transcript to $CWD/.agents/eval/pi-better-btw-<timestamp>.md

In Read-only Mode (default), the read-only lane is enforced: attempting an out-of-lane tool call is hard-blocked with a prompt injection; a second violation escalates the wording and aborts the turn (a 🚧 lane blocked status line). Executed-but-failed read-only calls are re-grounded by an afterToolCall note. Edit mode (Ctrl+T) is unaffected.

Peek at the main agent — the peek_main tool reads recent activity from the main session.

What is the main agent doing right now?
What changed since I opened this side chat?

Non-capturing overlay + backgrounding — the overlay opens centered, at 90% terminal width and up to 92% height (with outer margins). It stays focused while open; Alt+W backgrounds it (hidden, the agent keeps streaming) to hand the keyboard back, and Alt+W restores it.

Live layouts — Ctrl+Alt+← fills the terminal, Ctrl+Alt+↑ restores the original smaller window (85% width, top-center, original adaptive height), Ctrl+Alt+↓ opens the larger centered floating window (90% width, up to 92% height), and Ctrl+Alt+→ uses the right half at full height. The larger floating layout remains the default. These keys also work inside help/pickers. Switching preserves the agent, history, streaming output and draft; only the active text selection is cleared as line wrapping changes. Layouts resize with the terminal, reserving the actual multiline editor height instead of clipping its controls. Fullscreen here changes only the overlay's size, not Pi's terminal mode.

Jump between messages — Ctrl+↑/Ctrl+↓ jump to the previous/next user prompt or final AI response, following Pi's tui.altScreen.previousPrompt/nextPrompt bindings (including custom remaps). Tool calls/results are skipped; streamed AI text is included. Jumping preserves the draft and running agent.

Scroll the history — PgUp/PgDn scroll by a page, Shift+↑/Shift+↓ by a few lines, and the mouse wheel scrolls when the pointer is over the chat. When scrolled away from the latest message, a [↑N] indicator appears in the header. While streaming, the viewport follows the bottom until you scroll away, then freezes content-anchored (new lines grow the scroll offset instead of sliding the visible content); it resumes following once you're back at the bottom or a new message arrives.

Mouse select + hotkey copy — drag to select chat text (inverse-video highlight); double-click selects the whole rendered line. Copying is hotkey-only: Ctrl+C / Ctrl+Shift+C copies the retained selection through the native clipboard cascade (wl-copy/xclip, OSC 52 fallback); the selection stays highlighted so repeated presses re-copy. Dragging never touches the clipboard, so mouse interaction stays off the event loop. In Pi 1.0 fullscreen mode, the overlay uses Pi's normalized mouse dispatch and drag capture; clicking the input area positions its cursor. Backgrounding or closing the chat leaves Pi's main-screen mouse controls intact. In regular mode, extension-owned mouse reporting follows overlay visibility, so backgrounding restores terminal-native selection.

Transcript export — Alt+E dumps the btw history (forked context, framing block, conversation, in-flight stream) to $CWD/.agents/eval/pi-better-btw-<timestamp>.md as a markdown diagnostic artifact, useful for debugging feature work.

Shortcuts

Key Action
Alt+W Open (when closed) / background (when visible) / restore (when hidden)
Enter Send message
Esc Request confirmed close; inside a picker/confirmation/help page, return to chat
? Open the full shortcut page (empty draft only)
Ctrl+Alt+← Full-terminal overlay
Ctrl+Alt+↑ Original smaller window, top-center
Ctrl+Alt+↓ Larger centered floating overlay
Ctrl+Alt+→ Right-half overlay, full height
Ctrl+↑ / Ctrl+↓ Previous / next message (Pi bindings)
Ctrl+P / Ctrl+Shift+P Cycle side-chat model forward / backward (Pi bindings)
Alt+P Cycle model backward (Windows-friendly alternative)
Shift+Tab Cycle supported side-chat thinking levels (Pi binding)
Alt+S Stop the current turn, keeping the chat
Alt+M Pick the side-chat model (searchable)
Alt+T Pick the side-chat thinking level
Alt+R Re-fork from latest main context
Alt+N Start empty conversation
Alt+E Export the btw chat history to $CWD/.agents/eval/pi-better-btw-<timestamp>.md
Ctrl+T Toggle read-only / edit mode
PgUp / PgDn Scroll history by a page
Shift+↑ / Shift+↓ Scroll by a few lines
Mouse wheel Scroll when the pointer is over the chat
Mouse drag Select text in the chat area (inverse-video highlight); no copy on release
Double-click Select the whole rendered line
Ctrl+C / Ctrl+Shift+C Copy the active mouse selection (hotkey-only; the selection is kept until you click elsewhere, so repeated presses re-copy)

Command Reference

/btw

Opens the side chat overlay. Alias for /side.

/side

Opens the side chat overlay (upstream name kept as a compatibility alias).

/btw-close

Opens close confirmation, restoring the chat first if hidden. Does nothing without an active side chat.

peek_main

Available to the side agent only.

Param Type Description
lines integer Max items to inspect (default: 20, max: 50)
since_fork boolean Only show activity after the side chat was opened

Configuration

pi-better-btw reads config.json from three locations, layered in increasing precedence — a layer only overrides the keys it actually defines:

Layer Location
Bundle (defaults) config.json next to the extension — git-tracked, ships with the published package
User ~/.pi/agent/pi-better-btw/config.json
Project <project>/.pi/pi-better-btw/config.json

Keys:

  • readOnlyExtensionAllowlist — extension tool names allowed in the read-only lane (the lane always includes the builtin read tools read/grep/find/ls and peek_main). Lists are unioned across layers in bundle → user → project order (deduped, first occurrence wins): a user/project layer adds tools, it never drops the defaults shipped below it.
  • readOnlyExtensionAllowlistExclude — tool names removed from the final list, e.g. to drop a bundled default.
  • promptPack — prompt-pack manifest (see below); merges per key with the higher layer winning. Relative paths resolve against the layer's own directory, so a user-level manifest can live next to the user config; absolute paths work too.

Example (user or project layer):

{
  "readOnlyExtensionAllowlist": ["pi-vision-helper", "lens_diagnostics"],
  "readOnlyExtensionAllowlistExclude": ["web_search"]
}

Prompt pack manifest

promptPack maps each injected prompt to a markdown file (relative to the layer's directory, or absolute). All keys are optional — an absent or unreadable key falls back to the bundled prompts/ default (with a UI warning):

Key Bundled default Injected
promptPack.framing prompts/btw-framing.md after the forked context (never rendered as a chat bubble) — frames the cite as reference-only
promptPack.focusAnchor prompts/btw-focus-anchor.md every turn — "answer only the latest btw message"
promptPack.laneReminders.base prompts/lane-reminder-base.md first read-only-lane violation ({{tool}} / {{count}})
promptPack.laneReminders.escalated prompts/lane-reminder-escalated.md second violation, before the turn abort
promptPack.laneReminders.failedNote prompts/lane-failed-note.md after an executed-but-failed read-only call
promptPack.laneReminders.preamble prompts/lane-preamble.md read-only lane preamble

The bundled config.json ships the official pi tool set in the read-only allowlist (web_search, source_check, fetch_content, get_search_content); third-party tools (pi-lens, context7, vision, …) are added through the user layer.

How It Works

The extension clones the current session context, creates a separate agent instance with all extension-registered tools, and renders it in a TUI overlay. Hiding (Alt+W) preserves the agent and draft while streaming continues. Confirmed close destroys the chat; the next opening forks fresh main context.

Pi has no model-row status slot. The default-footer indicator adapts the exported FooterComponent renderer without editing installed Pi files, clears on close, and restores the renderer on extension shutdown. Placement depends on Pi footer internals (tested against the current Pi 1.4 API). A keyed setStatus is always available, including when footers are switched at runtime. The default renderer relocates only its own status from the status row to the model row, without taking over setFooter.

Side-chat requests use modelRegistry.streamSimple, letting Pi's model runtime handle authentication, request headers and virtual-model routing.

The btw context keeps the main lane's system prompt in the system slot and injects the fork snapshot verbatim, so the btw request head is a token prefix of the main request (gateway prefix-cache hits). forkSurgery (srcs/fork-surgery.ts) makes the trailing tool exchange gateway-legal on the snapshot; the prompt pack supplies all injected prompt text; lane enforcement wraps beforeToolCall/afterToolCall (srcs/side-chat-overlay.ts) in the read-only lane only.

Main-agent tool execution events are tracked to maintain a set of written file paths (srcs/file-activity-tracker.ts); write-capable tools are wrapped to warn before touching those paths (srcs/tool-wrapper.ts).

In fullscreen mode (Pi 1.0's default), SideChatOverlay.handleMouse() receives component-local events from Pi. It handles wheel scrolling, captures chat selection drags, and forwards input-area clicks to the editor. Clipboard copying stays hotkey-only. Pi owns terminal mouse reporting throughout the overlay lifecycle. In regular mode, a raw SGR listener enables reporting only while the overlay is visible and consumes mouse sequences before they reach the editor.

peek_main reads the current session branch on demand and returns a compact summary.

Development

Structure:

.
├── srcs/                  # TypeScript implementation (pi loads TS directly, no build step)
│   ├── index.ts           # extension entry: commands, shortcut, overlay lifecycle
│   ├── config.ts          # layered config resolution (bundle / user / project)
│   ├── prompt-pack.ts     # prompt-pack manifest loader + template substitution
│   ├── fork-surgery.ts    # shared-prefix fork snapshot surgery (gateway-legal tails)
│   ├── side-chat-overlay.ts  # TUI overlay, agent lifecycle, lane enforcement, mouse routing
│   ├── side-chat-messages.ts # message rendering, wrapping, selection, scrolling
│   ├── side-chat-mouse.ts    # minimal SGR mouse parsing
│   ├── side-chat-export.ts   # Alt+E transcript export
│   ├── side-chat-status.ts   # main-footer indicator and status fallback
│   ├── side-chat-dialog.ts   # model/thinking/close confirmation cards
│   ├── side-chat-input.ts    # lightweight input card, native cursor and mouse-coordinate mapping
│   ├── side-chat-panel.ts    # theme/terminal-derived panel, input and user-card surfaces
│   ├── side-chat-layout.ts   # fullscreen / original small / large floating / right-half presets
│   ├── tool-wrapper.ts       # write-path overlap warnings
│   └── file-activity-tracker.ts
├── prompts/               # bundled prompt-pack defaults (framing, focus anchor, lane reminders)
├── test/                  # bun test suites (config resolution, mouse select)
├── config.json            # bundled defaults (promptPack manifest + read-only allowlist)
├── banner.png
└── README.md

Commands:

bun install         # install dependencies
bun run typecheck   # tsc --noEmit -p tsconfig.json
bun test            # bun test test/  (run serially, see bunfig.toml)

The published package ships srcs/, prompts/, config.json and the docs; tests stay out of the tarball. To load the extension in pi during development, point pi's extension loader at ./srcs/index.ts and /reload after edits (no build step — pi loads TypeScript directly).

Limitations

  • One side chat at a time
  • Won't open on top of another visible overlay
  • Does not merge messages back into the main thread
  • Bash overlap detection is heuristic — catches common write patterns, not all
  • peek_main is on-demand, not live
  • Fullscreen mouse support uses Pi 1.0's component mouse API; the regular-mode SGR path remains available

License

MIT.