@narumitw/pi-btw 0.57.0 β 0.57.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -141
- package/dist/index.ts +34 -5
- package/dist/index.ts.map +2 -2
- package/docs/workflows.md +73 -0
- package/package.json +2 -1
- package/src/btw.ts +8 -0
- package/src/side-thread.ts +57 -5
package/README.md
CHANGED
|
@@ -43,117 +43,16 @@ The side thread stays separate until you explicitly bring context to the main ed
|
|
|
43
43
|
|
|
44
44
|
## π¬ Commands
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
| Command | Purpose |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `/btw` | Choose context, start or resume a side thread, or change settings. |
|
|
49
|
+
| `/btw <question>` | Start a new side thread immediately with the supplied question. |
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
Examples:
|
|
55
|
-
|
|
56
|
-
```text
|
|
57
|
-
/btw
|
|
58
|
-
/btw what does this TypeScript error mean?
|
|
59
|
-
/btw summarize the current implementation before we continue
|
|
60
|
-
/btw is this API name idiomatic?
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### Choose context or resume a thread
|
|
64
|
-
|
|
65
|
-
Running `/btw` opens a manager with **Start side thread** selected first.
|
|
66
|
-
**Start from main thread treeβ¦** opens Pi's session tree and uses the root-to-selected-entry path, including the selected entry, as context.
|
|
67
|
-
This choice preserves the main editor draft and does not navigate, fork, append to, or switch the main conversation.
|
|
68
|
-
The tree is a snapshot of persisted entries, and the side thread keeps immutable context even if the main conversation later changes.
|
|
69
|
-
Escape returns to the manager, and Ctrl+C closes the flow.
|
|
70
|
-
Native tree copying reports success or failure.
|
|
71
|
-
An explicit `Shift+L` label edit is the only main-session mutation available from this selector and persists through Pi.
|
|
72
|
-
|
|
73
|
-
When non-empty threads exist in memory, **Resume side thread** opens a bounded searchable list.
|
|
74
|
-
Search matches the first question and question count while retaining each raw thread ID.
|
|
75
|
-
Each row uses the first question as its fixed title and shows the question count.
|
|
76
|
-
Rows are ordered by the newest recorded answer or visible error; opening and closing without a new result does not reorder them.
|
|
77
|
-
**Settings** controls the starting thinking level, fixed-level shortcut memory, and automatic selection copying.
|
|
78
|
-
`/btw <question>` bypasses the manager and always starts a new thread.
|
|
79
|
-
|
|
80
|
-
### Use the side-thread workspace
|
|
81
|
-
|
|
82
|
-
The side thread uses a dedicated full-screen terminal view with answers above the editor.
|
|
83
|
-
The main agent can continue running, but main-screen rendering stays suspended until `/btw` closes so new output cannot move a mouse selection.
|
|
84
|
-
Returning to Pi redraws everything produced while the main view was hidden.
|
|
85
|
-
|
|
86
|
-
A fixed `btw Β· side thread` header identifies the workspace while scrolling.
|
|
87
|
-
Messages use Pi's normal user and assistant presentation without turn numbers or role labels.
|
|
88
|
-
Type a question and press Enter for each turn.
|
|
89
|
-
Previous successful questions and answers remain visible and available to the side model.
|
|
90
|
-
|
|
91
|
-
Drag the primary mouse button across transcript text to select it.
|
|
92
|
-
Automatic selection copying is on by default and immediately requests a copy through Pi's host clipboard helper.
|
|
93
|
-
When **Copy selection automatically** is off, the selection stays highlighted and Pi's effective `app.message.copy` binding copies it.
|
|
94
|
-
Manual mode requires Pi's current fullscreen selection APIs.
|
|
95
|
-
If those APIs are unavailable, pi-btw restores the main TUI and asks you to update Pi or re-enable automatic copying.
|
|
96
|
-
The view reports `No selection to copy` when that binding is used without an active selection.
|
|
97
|
-
It reports `Copied!` when Pi accepts a clipboard request and `Copy failed` when Pi rejects it.
|
|
98
|
-
Actual clipboard access still depends on the operating system and terminal.
|
|
99
|
-
Ctrl+C always cancels the side flow, even when `app.message.copy` is also mapped to Ctrl+C.
|
|
100
|
-
|
|
101
|
-
Press `Ctrl+Shift+F` to search completed or in-progress transcript content.
|
|
102
|
-
Press Enter or `Ctrl+G` for the next match, `Shift+Enter` or `Ctrl+Shift+G` for the previous match, and Escape to close search.
|
|
103
|
-
Search uses Pi's active theme, excludes fixed header and footer text, and returns focus to the composer when closed.
|
|
104
|
-
|
|
105
|
-
### Change thinking level or queue steering
|
|
106
|
-
|
|
107
|
-
The header shows the side thread's current thinking level.
|
|
108
|
-
In the composer, use Pi's configured `app.thinking.cycle` shortcut (`Shift+Tab` by default) to cycle levels supported by the side model.
|
|
109
|
-
Later questions use the displayed level until it changes again.
|
|
110
|
-
For a fixed starting level, shortcut changes are saved to `pi-btw.json` by default.
|
|
111
|
-
Turn **Remember thinking level changes** off to keep them local to the thread.
|
|
112
|
-
With **Same as main thread**, shortcut changes are always local.
|
|
113
|
-
Neither setting changes the main session's thinking level.
|
|
114
|
-
|
|
115
|
-
During a response, the transcript and composer remain visible above `Answeringβ¦`.
|
|
116
|
-
Submit another question to queue it as `Steering`.
|
|
117
|
-
Queued questions appear in submission order and run one at a time after the active response.
|
|
118
|
-
Each queued question uses the side thread's thinking level when its turn starts.
|
|
119
|
-
A failed response remains visible and does not discard later queued questions.
|
|
120
|
-
Steering never appends to the main conversation or editor.
|
|
121
|
-
|
|
122
|
-
Use the mouse wheel, trackpad, or `PgUp`/`PgDn` to scroll transcript history.
|
|
123
|
-
On Pi 0.85 or newer, scrolling away from a following transcript shows **Jump to latest message** over its final visible row.
|
|
124
|
-
Click the control or use Pi's effective `tui.altScreen.bottom` binding (`End` by default) to resume at the latest content.
|
|
125
|
-
The control disappears after the transcript returns to follow-end mode.
|
|
126
|
-
The footer shows history keys only when scrolling is available.
|
|
127
|
-
Ctrl+C cancels the active response and discards the current draft and steering queue.
|
|
128
|
-
Completed questions, answers, and visible errors remain available through Resume until the extension instance ends.
|
|
129
|
-
|
|
130
|
-
### Bring context to the main editor
|
|
131
|
-
|
|
132
|
-
After a successful answer, press `Ctrl+R` to choose context for the main editor.
|
|
133
|
-
The scope menu shows the size of the latest question and answer and the full thread.
|
|
134
|
-
Choose the latest question and answer, everything from one question onward, an exact range, or the full thread.
|
|
135
|
-
Question-suffix, exact-range, and full-thread choices preview the editable context block before the side thread closes.
|
|
136
|
-
Escape returns, while Ctrl+C closes without bringing context back.
|
|
137
|
-
|
|
138
|
-
The exact-range selector supports whole-line and editor-style character selection.
|
|
139
|
-
It reports selected line, message, and approximate token counts.
|
|
140
|
-
Press Space to select the current raw source line, use Up or Down to extend by lines, and press Space again to clear.
|
|
141
|
-
Alternatively, move with arrow keys and extend a character selection with Shift plus an arrow key.
|
|
142
|
-
Starting a Shift selection replaces an active line selection.
|
|
143
|
-
Selected lines show a `β` marker as well as highlighting.
|
|
144
|
-
Pi's configured keys control vertical navigation, bringing, and going back, with Up, Down, Enter, and Escape as defaults.
|
|
145
|
-
Selection follows raw source text rather than terminal-wrapped rows.
|
|
146
|
-
|
|
147
|
-
Bringing context closes the side thread and loads a deterministic, editable block into Pi's main editor without sending it.
|
|
148
|
-
If a draft already exists, append is the recommended default.
|
|
149
|
-
Replace is marked destructive and requires a second confirmation.
|
|
150
|
-
Cancel returns to the side thread without changing either draft, and concurrent editor updates are preserved.
|
|
151
|
-
A success message reports whether context was loaded, appended, or replaced and gives its approximate size.
|
|
152
|
-
|
|
153
|
-
Without an explicit bring action, closing `/btw` never changes the main conversation.
|
|
154
|
-
Non-empty threads remain in memory only for Resume during the current extension instance.
|
|
155
|
-
`/new`, Pi `/resume`, `/reload`, extension replacement, and process restart discard retained threads.
|
|
156
|
-
Unsent drafts, steering queues, interrupted answers, and model credentials are not retained.
|
|
51
|
+
Both routes require TUI mode and a model with usable credentials; see [Settings](#-settings).
|
|
52
|
+
Side questions and selected conversation context are sent to that model's provider.
|
|
53
|
+
Bringing context back fills the main editor without submitting; replacing an existing draft requires confirmation.
|
|
54
|
+
Ctrl+C cancels the response and discards the current draft and queued questions, but completed exchanges remain resumable in memory.
|
|
55
|
+
Read the [workflow guide](./docs/workflows.md) for context selection, copying, search, steering, and draft recovery; `/new`, `/resume`, `/reload`, and restart discard retained threads.
|
|
157
56
|
|
|
158
57
|
## βοΈ Settings
|
|
159
58
|
|
|
@@ -223,40 +122,18 @@ The file is read for every `/btw` invocation, so edits apply without `/reload`.
|
|
|
223
122
|
|
|
224
123
|
## ποΈ Package layout
|
|
225
124
|
|
|
226
|
-
```
|
|
125
|
+
```text
|
|
227
126
|
packages/pi-btw/
|
|
228
|
-
βββ
|
|
229
|
-
βββ
|
|
230
|
-
β βββ
|
|
231
|
-
βββ
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
β βββ fullscreen-ui.ts
|
|
236
|
-
β βββ main-tree-picker.ts
|
|
237
|
-
β βββ menu.ts
|
|
238
|
-
β βββ settings.ts
|
|
239
|
-
β βββ side-thread.ts
|
|
240
|
-
β βββ text.ts
|
|
241
|
-
β βββ transcript-pager.ts
|
|
242
|
-
βββ test/
|
|
243
|
-
βββ README.md
|
|
244
|
-
βββ LICENSE
|
|
245
|
-
βββ tsconfig.json
|
|
246
|
-
βββ package.json
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
The package exposes its Pi extension through `package.json`:
|
|
250
|
-
|
|
251
|
-
```json
|
|
252
|
-
{
|
|
253
|
-
"pi": {
|
|
254
|
-
"extensions": ["./dist/index.ts"]
|
|
255
|
-
}
|
|
256
|
-
}
|
|
127
|
+
βββ src/ # Authoritative implementation and helpers
|
|
128
|
+
β βββ index.ts # Thin Pi entrypoint
|
|
129
|
+
β βββ btw.ts # Side-thread lifecycle and command
|
|
130
|
+
βββ dist/ # Generated Jiti runtime
|
|
131
|
+
βββ docs/ # Side-thread workflows and controls
|
|
132
|
+
βββ scripts/build-runtime.mjs # Runtime builder
|
|
133
|
+
βββ test/ # Behavior and lifecycle coverage
|
|
257
134
|
```
|
|
258
135
|
|
|
259
|
-
The generated runtime is built from
|
|
136
|
+
The generated runtime is built from `src/index.ts` and does not import back into `src`.
|
|
260
137
|
|
|
261
138
|
## π Keywords
|
|
262
139
|
|
package/dist/index.ts
CHANGED
|
@@ -1125,14 +1125,15 @@ async function completeSideThreadTurn({
|
|
|
1125
1125
|
thinkingLevel,
|
|
1126
1126
|
auth,
|
|
1127
1127
|
signal,
|
|
1128
|
-
completeSimple
|
|
1128
|
+
completeSimple,
|
|
1129
|
+
sessionId
|
|
1129
1130
|
}) {
|
|
1130
1131
|
if (signal?.aborted) return { kind: "aborted" };
|
|
1131
1132
|
try {
|
|
1132
1133
|
const response = await completeSimple(
|
|
1133
1134
|
model,
|
|
1134
1135
|
{ systemPrompt: SYSTEM_PROMPT, messages: buildSideThreadMessages(thread, question) },
|
|
1135
|
-
buildStreamOptions(auth, thinkingLevel, signal)
|
|
1136
|
+
buildStreamOptions(auth, { thinkingLevel, signal, model, sessionId })
|
|
1136
1137
|
);
|
|
1137
1138
|
if (signal?.aborted || response?.stopReason === "aborted") return { kind: "aborted" };
|
|
1138
1139
|
if (!isAssistantMessage(response)) {
|
|
@@ -1191,10 +1192,31 @@ function createUserMessage(text) {
|
|
|
1191
1192
|
timestamp: Date.now()
|
|
1192
1193
|
};
|
|
1193
1194
|
}
|
|
1194
|
-
|
|
1195
|
+
var OPENCODE_HOST = "opencode.ai";
|
|
1196
|
+
function matchesOpencodeHost(baseUrl) {
|
|
1197
|
+
if (!baseUrl) return false;
|
|
1198
|
+
try {
|
|
1199
|
+
return new URL(baseUrl).hostname === OPENCODE_HOST;
|
|
1200
|
+
} catch {
|
|
1201
|
+
return false;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
function getOpencodeSessionHeaders(model, sessionId) {
|
|
1205
|
+
if (!sessionId) return void 0;
|
|
1206
|
+
if (model.provider !== "opencode" && model.provider !== "opencode-go" && !matchesOpencodeHost(model.baseUrl)) {
|
|
1207
|
+
return void 0;
|
|
1208
|
+
}
|
|
1209
|
+
return { "x-opencode-session": sessionId, "x-opencode-client": "pi" };
|
|
1210
|
+
}
|
|
1211
|
+
function mergeSessionHeaders(authHeaders, sessionHeaders) {
|
|
1212
|
+
if (!sessionHeaders && !authHeaders) return void 0;
|
|
1213
|
+
return { ...sessionHeaders, ...authHeaders };
|
|
1214
|
+
}
|
|
1215
|
+
function buildStreamOptions(auth, { thinkingLevel, signal, model, sessionId }) {
|
|
1216
|
+
const sessionHeaders = model ? getOpencodeSessionHeaders(model, sessionId) : void 0;
|
|
1195
1217
|
const options = {
|
|
1196
1218
|
apiKey: auth.apiKey,
|
|
1197
|
-
headers: auth.headers,
|
|
1219
|
+
headers: mergeSessionHeaders(auth.headers, sessionHeaders),
|
|
1198
1220
|
env: auth.env,
|
|
1199
1221
|
signal
|
|
1200
1222
|
};
|
|
@@ -2615,6 +2637,12 @@ function providerHeadersHaveValue(headers) {
|
|
|
2615
2637
|
function formatError5(error) {
|
|
2616
2638
|
return error instanceof Error ? error.message : String(error);
|
|
2617
2639
|
}
|
|
2640
|
+
function readBtwSessionId(ctx) {
|
|
2641
|
+
const getSessionId = ctx.sessionManager.getSessionId;
|
|
2642
|
+
if (typeof getSessionId !== "function") return void 0;
|
|
2643
|
+
const sessionId = getSessionId.call(ctx.sessionManager);
|
|
2644
|
+
return sessionId.length > 0 ? sessionId : void 0;
|
|
2645
|
+
}
|
|
2618
2646
|
function notifySafely3(ctx, message, level) {
|
|
2619
2647
|
try {
|
|
2620
2648
|
ctx.ui.notify(sanitizeSingleLine(message), level);
|
|
@@ -3149,7 +3177,8 @@ async function askThreadQuestion(thread, question, selected, thinkingLevel, ctx,
|
|
|
3149
3177
|
thinkingLevel,
|
|
3150
3178
|
auth: selected.auth,
|
|
3151
3179
|
signal: view.signal,
|
|
3152
|
-
completeSimple: createModelRegistryCompleteSimple(ctx.modelRegistry)
|
|
3180
|
+
completeSimple: createModelRegistryCompleteSimple(ctx.modelRegistry),
|
|
3181
|
+
sessionId: readBtwSessionId(ctx)
|
|
3153
3182
|
}).then((result) => {
|
|
3154
3183
|
if (settled) return;
|
|
3155
3184
|
settled = true;
|