# pi-btw-extension

A [pi](https://github.com/badlogic/pi-mono) extension that adds a `/btw` side channel for
questions you do not want in the main transcript. It requires Pi 0.84.2–0.84.x and Node 22.19.0+
(the package peer range is `>=0.84.2 <0.85.0`).

`/btw` answers in an overlay using the context the main session already has. Nothing reaches the
main conversation unless you confirm a share.

## Install

```
pi install npm:pi-btw-extension
pi update --extension npm:pi-btw-extension
pi remove npm:pi-btw-extension
```

Pi bundles the packages this extension imports; there is no separate dependency to install.

## Usage

Ask while the main agent is mid-task:

```
/btw what does this error actually mean?
/btw --history retryPolicy
```

The side agent decides whether its read-only tools are needed for each question. History browsing
and search are model-free; Continue needs a currently selected model because it opens a writable
thread. `/btw --settings` is a settings UI, while `/btw --stats` shows an active-lineage report as
an interactive notification.

| Key or command | Action |
|---|---|
| `/btw <question>` | Ask; opens the overlay |
| `/btw` | Reopen the active thread |
| `/btw --history` | Browse archived threads |
| `/btw --history <query>` | Browse archived threads filtered by query |
| `/btw --settings` | Open settings UI |
| `/btw --stats` | Show an active-lineage statistics notification |
| configured shortcut (default `Ctrl+Alt+B`) | Open the overlay |
| `Enter` | Submit the draft |
| `Shift+Enter` or `\` then `Enter` | Insert a newline in the draft |
| `Esc` | Close the overlay, or cancel sharing selection, preview, or refine |
| `←` `→` | Previous / next question when the input is empty |
| `↑` `↓` | Scroll a long card when the input is empty |
| `PgUp` `PgDn` | Previous / next question regardless of input |
| `Ctrl+R` | Regenerate the displayed question as a new immutable attempt |
| `Shift+←` `Shift+→` | Previous / next attempt for the displayed question |
| `Ctrl+P` | Open sharing controls |

The overlay is a fixed-height pager (60% of the terminal, minimum 12 rows). It shows one
question/answer card at a time; long cards scroll within it. On a tiny terminal, a tall multiline
draft or refine loader can exceed that target so the input and footer remain visible. `Ctrl+Alt+B` is Ctrl+Option+B on
macOS. A terminal that does not send Option as Meta (Terminal.app by default, for example) cannot
deliver that shortcut; use `/btw` instead.

### Sharing

`Ctrl+P` selects answers that have not been shared:

| Key | Action |
|---|---|
| `←` `→` | Pick a card |
| `Shift+←` `Shift+→` | Inspect attempts on the selected question |
| `↑` `↓` | Scroll the selected card |
| `Enter` | Share the selected attempt |
| `a` | Share one newest eligible unshared attempt per question in one note |
| `r` | Rewrite as a short summary, then share |
| `Esc` | Cancel |

Single, all, and refined sharing **always** open an editable preview. Its first
`[/btw note: ...]` line must remain exactly unchanged. Canceling, submitting an empty preview, or
letting the target become stale sends nothing. A confirmed preview queues one `nextTurn` note;
sharing never starts a model turn itself. A durable `shared to main` badge records
successful queueing, but cannot tell whether the main agent has consumed the note.
Closing the overlay discards an in-flight ask, retry, or refine preview unless its attempt has
already been persisted.

`/btw --history` is read-only. Use `c` to explicitly Continue the reviewed snapshot into the
current session/branch. Continue leaves the archive untouched and makes a new writable thread,
cloning its attempts, promotion state, summary, and summary meter into that snapshot. If the
session or branch changes while browsing, reopen history. Legacy v1/v2 threads remain read-only
history until Continue.

## Configuration

Environment variables take precedence over the user-global `~/.pi/agent/btw.json`, which takes
precedence over defaults. Environment rows are read-only in the settings UI. Invalid individual
values fail closed to the lower-precedence value; malformed JSON or UTF-8 is left byte-for-byte
unchanged. Unknown JSON keys are preserved when settings are saved.

| Setting | Default | Environment variable | Notes |
|---|---|---|---|
| `answerMaxTokens` | `4096` | `BTW_ANSWER_MAX_TOKENS` | Caps every answer |
| `refineMaxTokens` | `1024` | `BTW_REFINE_MAX_TOKENS` | Caps the `r` summary only |
| `toolCallBudget` | `8` | `BTW_TOOL_BUDGET` | Tool calls per ask before it wraps up |
| `toolAllowlist` | `read,grep,find,ls` | `BTW_TOOL_ALLOWLIST` | Comma-separated allowed tools; an empty value permits none and it can only narrow the built-in set |
| `summaryEnabled` | `true` | `BTW_SUMMARY_ENABLED` | Enables automatic rolling side-thread maintenance |
| `summaryTriggerTokens` | `12000` | `BTW_SUMMARY_TRIGGER_TOKENS` | Estimated side Q/A size before maintenance is considered |
| `summaryRetainTokens` | `8000` | `BTW_SUMMARY_RETAIN_TOKENS` | Recent raw Q/A retained outside each summary |
| `summaryMaxTokens` | `1024` | `BTW_SUMMARY_MAX_TOKENS` | Token cap for the separate summary request |
| `shortcut` | `ctrl+alt+b` | `BTW_SHORTCUT` | Portable modified-key grammar only; unsafe or malformed values fall back |

`/btw --settings` writes user-global settings only, never environment values. Shortcut changes take
effect after Pi reloads extensions. Pi reports shortcut conflicts during loading, but the final host
conflict outcome is not introspectable by this extension.

## Storage, migration, and privacy

Threads are stored per project at:

```
~/.pi/agent/btw/threads-<hash-of-cwd>.json
```

The current format is strict v5. v1/v2 legacy data is read losslessly but stays read-only until
Continue; v3 scoped data retains its writable scope; v4 attempt-based data migrates to v5. Empty
session/tree scope boundaries are preserved. Future-version and corrupt files are preserved as
exact bytes and are never overwritten; BTW instead continues in memory with one persistence
warning. This means mixed old/new installations have a limit: older binaries may refuse v5, so do
not run a pre-v5 writer concurrently against shared project data. There is no automatic delete UI.

The file can contain side questions and answers, immutable attempts, model and grounding metadata,
read-only tool names and errors, rolling summaries, summary usage and cost meters, durable share
state, and session IDs/tree leaf IDs. To remove it manually, stop Pi first and back it up if needed;
deleting a project hash loses that project archive. Do not delete it while Pi is running. Writes use a lock, latest-state reload, idempotent scoped operation, and atomic replace so
same-cwd processes preserve each other’s creates and answers. Power-loss `fsync` durability and
broader platform stale-lock recovery remain deferred.

Threads are isolated by Pi session and each real `/tree` navigation. The provider-visible grounding
cache is keyed by session and tree leaf, and is cleared on session start, `/tree` navigation, and
shutdown. Capture failures rebuild from current persisted context only, or return an empty prefix;
they never expose a prior transcript. Every answer is an immutable attempt; retry streams to a
temporary preview and appends a new attempt rather than replacing the old one. Attempt navigation
is local, including read-only history. The side agent’s tool boundary is read-only (`read`, `grep`,
`find`, `ls`): no `write`, `edit`, or `bash` is exposed.

## Summary and statistics

Defaults use the active session context and separate provider requests for rolling summaries.
Summary usage is separate from answer usage; `/btw --stats` reports only the active continued
lineage. It cannot provide an exact global aggregate because Continue clones a cumulative meter.
Raw Q/A is authoritative over generated summaries. Summary calls receive no tools and no main
prefix; failures are silent and nonblocking. There is no cross-process call deduplication.

The UTF-8 token estimator is approximate, not an exact tokenizer. Summary maintenance never delays
an ask. Its separate provider call preserves normal provider configuration but should be accounted
for separately.

## Development

```
npm run typecheck
npm test
```

The deterministic offline package-load test uses Pi 0.84.2’s `DefaultResourceLoader` and an
in-memory `SettingsManager`. It loads both the working directory and a locally packed, locally
installed tarball in bounded child processes with `PI_OFFLINE=1`, a minimal credential-free
environment, and a `fetch` override that fails any network attempt. It also invokes the pinned
local Pi CLI through Node with an isolated temporary agent directory to install, list, and remove
the locally installed artifact. It never sends a provider, model, or session request, and enforces
exactly 22 runtime package files. Provider-required smoke prompts are not a release gate.

Release checklist (automated gates are required; manual checks are pending until performed):

- [x] Run `npm run typecheck`, `npm test`, and `npm pack --dry-run --json`; confirm 22 files.
- [x] Run the credential-free isolated Pi CLI install/list/remove smoke against the locally packed artifact.
- [ ] Confirm real macOS and Windows CI runs (Linux, macOS, Windows; Node 22.19 and 24).
- [ ] Test Korean, Japanese, and Chinese IME input on macOS and on a Linux or Windows terminal.
- [ ] Test narrow and fullscreen TUI layouts.
- [ ] Run two Pi processes in the same cwd.
- [ ] Load a real v0.4.1 store copy and verify migration behavior.
- [ ] Reload a changed shortcut and check a Pi shortcut conflict.
- [ ] Check preview cancel, edit, and confirm flows.

## Limitations

There is no web search or page fetching. Pi has no public API for one extension to reuse another
extension’s tools. Terminal Option/Meta delivery can prevent the default macOS shortcut. The
extension cannot introspect Pi’s ultimate shortcut-conflict outcome. Power-loss `fsync` and broader
platform stale-recovery policy are deferred.

## License

MIT
