@narumitw/pi-btw 0.50.0 → 0.52.0

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 CHANGED
@@ -18,7 +18,7 @@ Use it when you want to ask a temporary question, inspect context, or get a shor
18
18
  - Optionally brings the latest answer, a question-to-end suffix, an exact line range, or the entire side thread into the main editor.
19
19
  - Uses the current session branch as context.
20
20
  - Uses Pi's current model or an independent model selected in `pi-btw.json`.
21
- - Uses a pi-btw thinking level that can be changed with Pi's configured thinking shortcut and remembered for next time.
21
+ - Uses a pi-btw thinking level that can either start from the main thread or use a fixed remembered value.
22
22
  - Does not append the side question or answer to the main conversation.
23
23
  - Works as an independently installable npm Pi extension package.
24
24
 
@@ -58,14 +58,15 @@ Examples:
58
58
  /btw is this API name idiomatic?
59
59
  ```
60
60
 
61
- Running `/btw` alone opens a menu with **Start side thread** selected first. When the current Pi
62
- session has non-empty side threads in memory, **Resume side thread** opens a bounded choice list;
63
- **Settings** changes the starting thinking level and whether shortcut changes are remembered.
64
- Each Resume row keeps the first question as its fixed title, shows its question count, and the list
65
- is ordered by the newest recorded answer or visible error. Opening and closing a thread without a
66
- new result does not reorder it. `/btw <question>` bypasses this menu and always starts a fresh side
67
- thread. Its answer opens above the side-thread editor. The side thread uses a dedicated full-screen
68
- terminal view.
61
+ Running `/btw` alone opens a menu with **Start side thread** selected first.
62
+ When the current Pi session has non-empty side threads in memory, **Resume side thread** opens a bounded searchable choice list.
63
+ Search matches the displayed first question and question count while returning the thread's raw in-memory ID.
64
+ **Settings** changes the starting thinking level and whether shortcut changes for fixed levels are remembered.
65
+ Each Resume row keeps the first question as its fixed title, shows its question count, and the list is ordered by the newest recorded answer or visible error.
66
+ Opening and closing a thread without a new result does not reorder it.
67
+ `/btw <question>` bypasses this menu and always starts a fresh side thread.
68
+ Its answer opens above the side-thread editor.
69
+ The side thread uses a dedicated full-screen terminal view.
69
70
  The main agent continues running in the background, but its screen rendering stays suspended until
70
71
  `/btw` closes, so new main-thread output cannot move a mouse selection inside the side thread.
71
72
  Drag the primary mouse button across side-thread text to select and copy it through Pi's terminal
@@ -78,9 +79,11 @@ Previous side questions and answers remain available to the model and visible fo
78
79
  invocation. The side-thread header shows its current thinking level. Press Pi's configured
79
80
  `app.thinking.cycle` shortcut (`Shift+Tab` by default) in the composer to cycle the levels
80
81
  supported by the side-thread model; every later question uses the displayed level until it is
81
- changed again. By default, each shortcut change is also written to `pi-btw.json` for the next
82
- invocation. Turn **Remember thinking level changes** off in Settings to keep changes local to the
83
- current side thread. Neither path changes the main session's thinking level.
82
+ changed again. When a fixed thinking level is selected, each shortcut change is also written to
83
+ `pi-btw.json` for the next invocation by default. Turn **Remember thinking level changes** off in
84
+ Settings to keep fixed-level changes local to the current side thread. When **Same as main thread** is
85
+ selected, shortcut changes are always local to the current side thread. Neither path changes the main
86
+ session's thinking level.
84
87
  While a response is running, the transcript and composer remain visible above an `Answering…`
85
88
  status.
86
89
  Type another question and press `Enter` to queue it as `Steering`; queued questions are shown in
@@ -147,18 +150,23 @@ cannot be found or authenticated, pi-btw warns and falls back to the current ses
147
150
  If neither model is available, `/btw` reports an error and stops. This selection affects only
148
151
  `/btw`; it does not change the main session model.
149
152
 
150
- Pi calls its reasoning setting the **thinking level**. `thinkingLevel` sets pi-btw's starting
151
- level; accepted values are `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. When the
152
- field is absent for backward compatibility, the next invocation starts from the current session
153
- level. The initial value and shortcut cycle are clamped to the selected side model's capabilities
154
- using Pi's model rules. Pi-btw does not read, write, or change the main session's
155
- `defaultThinkingLevel`.
156
-
157
- `rememberThinkingLevelChanges` controls only persistence and defaults to `true` when omitted. A
158
- side-thread shortcut always changes that side thread immediately. When remembering is on, the
159
- concrete level is written for the next invocation; when off, `pi-btw.json` stays unchanged. If a
160
- shortcut write fails, the local change remains active and pi-btw warns that it was not remembered.
161
- A failed Settings-screen save instead restores the previous displayed value.
153
+ Pi calls its reasoning setting the **thinking level**. In Settings, choose **Same as main thread**
154
+ to start each new side thread from the main thread's current thinking level. This is stored by
155
+ omitting `thinkingLevel` from `pi-btw.json`.
156
+
157
+ Set `thinkingLevel` only when you want a fixed pi-btw starting level. Accepted fixed values are
158
+ `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. The initial value and shortcut cycle
159
+ are clamped to the selected side model's capabilities using Pi's model rules. Resumed side threads
160
+ keep their own local thinking level instead of re-syncing with the main thread. Pi-btw does not read,
161
+ write, or change the main session's `defaultThinkingLevel`.
162
+
163
+ `rememberThinkingLevelChanges` controls only persistence for fixed thinking levels and defaults to
164
+ `true` when omitted. A side-thread shortcut always changes that side thread immediately. When a fixed
165
+ thinking level is selected and remembering is on, the concrete level is written for the next
166
+ invocation; when off, `pi-btw.json` stays unchanged. When **Same as main thread** is selected,
167
+ shortcut changes stay local even when remembering is on. If a shortcut write fails, the local change
168
+ remains active and pi-btw warns that it was not remembered. A failed Settings-screen save instead
169
+ restores the previous displayed value.
162
170
 
163
171
  A missing settings file is a side-effect-free read: pi-btw creates it only after a Settings change
164
172
  or a remembered shortcut change. Saves are ordered within the Pi process and published atomically
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-btw",
3
- "version": "0.50.0",
3
+ "version": "0.52.0",
4
4
  "description": "Pi extension that adds a /btw side-question command.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -31,10 +31,10 @@
31
31
  "typecheck": "tsc --noEmit"
32
32
  },
33
33
  "devDependencies": {
34
- "@biomejs/biome": "2.5.7",
35
- "@earendil-works/pi-ai": "0.84.1",
36
- "@earendil-works/pi-coding-agent": "0.84.1",
37
- "@earendil-works/pi-tui": "0.84.1",
34
+ "@biomejs/biome": "2.5.8",
35
+ "@earendil-works/pi-ai": "0.84.2",
36
+ "@earendil-works/pi-coding-agent": "0.84.2",
37
+ "@earendil-works/pi-tui": "0.84.2",
38
38
  "typescript": "7.0.2"
39
39
  },
40
40
  "repository": {
@@ -43,7 +43,7 @@
43
43
  "directory": "packages/pi-btw"
44
44
  },
45
45
  "dependencies": {
46
- "@narumitw/pi-tui-kit": "^0.49.1"
46
+ "@narumitw/pi-tui-kit": "^0.54.0"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "@earendil-works/pi-ai": "*",
package/src/btw.ts CHANGED
@@ -194,7 +194,7 @@ export async function loadBtwThinkingLevel(
194
194
 
195
195
  options.warn?.(
196
196
  sanitizeSingleLine(
197
- `pi-btw settings ignored: ${settings.reason}; expected optional model "provider/model-id", thinkingLevel "${BTW_THINKING_LEVELS.join('" | "')}", and boolean rememberThinkingLevelChanges. Using current Pi thinking level.`,
197
+ `pi-btw settings ignored: ${settings.reason}; expected optional model "provider/model-id", omitted thinkingLevel for Same as main thread or thinkingLevel "${BTW_THINKING_LEVELS.join('" | "')}", and boolean rememberThinkingLevelChanges. Using current Pi thinking level.`,
198
198
  ),
199
199
  );
200
200
  return currentThinkingLevel;
@@ -265,6 +265,7 @@ export default function btw(pi: ExtensionAPI, dependencies: BtwExtensionDependen
265
265
  }
266
266
 
267
267
  const settings = await loadSettings(ctx);
268
+ const sameAsMainThinkingLevel = settings.thinkingLevel === undefined;
268
269
  const resolution = await resolveModel(settings, ctx);
269
270
  if (resolution.kind === "cancelled") {
270
271
  notifySafely(ctx, "Cancelled", "info");
@@ -302,7 +303,8 @@ export default function btw(pi: ExtensionAPI, dependencies: BtwExtensionDependen
302
303
  initialQuestion: question || undefined,
303
304
  selected: resolution.selected,
304
305
  thinkingLevel: state.thinkingLevel,
305
- rememberThinkingLevelChanges: effectiveRememberThinkingLevelChanges(settings),
306
+ rememberThinkingLevelChanges:
307
+ !sameAsMainThinkingLevel && effectiveRememberThinkingLevelChanges(settings),
306
308
  state,
307
309
  ctx: fullscreenCtx,
308
310
  });
package/src/menu.ts CHANGED
@@ -7,6 +7,7 @@ import type { Component, TUI } from "@earendil-works/pi-tui";
7
7
  import type { MenuContext, RunMenuResult } from "@narumitw/pi-tui-kit";
8
8
  import {
9
9
  type BtwSettings,
10
+ type BtwSettingsPatch,
10
11
  btwSettingsPath,
11
12
  effectiveRememberThinkingLevelChanges,
12
13
  readBtwSettings,
@@ -35,7 +36,7 @@ export interface ShowBtwCommandMenuOptions {
35
36
  settingsPath?: string;
36
37
  readSettings?: typeof readBtwSettings;
37
38
  updateSettings?: (
38
- patch: Partial<Pick<BtwSettings, "thinkingLevel" | "rememberThinkingLevelChanges">>,
39
+ patch: BtwSettingsPatch,
39
40
  options: UpdateBtwSettingsOptions,
40
41
  ) => Promise<BtwSettings>;
41
42
  }
@@ -44,6 +45,7 @@ export type BtwCommandMenuResult = "start" | "closed" | { kind: "resume"; thread
44
45
 
45
46
  type BtwMenuScreen = "main" | "resume" | "settings" | "invalid";
46
47
  type BtwMenuAction = "start" | "resume" | "set-thinking" | "set-remember";
48
+ const SAME_AS_MAIN_THREAD = "Same as main thread";
47
49
  type BtwCustomOptions = Parameters<ExtensionCommandContext["ui"]["custom"]>[1];
48
50
 
49
51
  type BtwCustomFactory<T> = (
@@ -79,8 +81,22 @@ export async function showBtwCommandMenu(
79
81
  }
80
82
  return { kind: "valid", settings: loaded.kind === "loaded" ? loaded.settings : {} };
81
83
  };
82
- const displayThinkingLevel = (settings: BtwSettings): BtwThinkingLevel =>
83
- clampToAvailableThinkingLevel(settings.thinkingLevel ?? options.currentThinkingLevel, levels);
84
+ const currentMainThinkingLevel = clampToAvailableThinkingLevel(
85
+ options.currentThinkingLevel,
86
+ levels,
87
+ );
88
+ const displayThinkingLevel = (settings: BtwSettings): string =>
89
+ settings.thinkingLevel === undefined
90
+ ? SAME_AS_MAIN_THREAD
91
+ : clampToAvailableThinkingLevel(settings.thinkingLevel, levels);
92
+ const displayThinkingSummary = (settings: BtwSettings): string =>
93
+ settings.thinkingLevel === undefined
94
+ ? `${SAME_AS_MAIN_THREAD} (currently ${currentMainThinkingLevel})`
95
+ : displayThinkingLevel(settings);
96
+ const displayRememberSummary = (settings: BtwSettings): string => {
97
+ const value = effectiveRememberThinkingLevelChanges(settings) ? "On" : "Off";
98
+ return settings.thinkingLevel === undefined ? `${value} (fixed levels only)` : value;
99
+ };
84
100
 
85
101
  const menu = defineMenu<BtwMenuState, BtwMenuScreen, BtwMenuAction, MenuContext>({
86
102
  start: "main",
@@ -89,7 +105,7 @@ export async function showBtwCommandMenu(
89
105
  kind: "actions",
90
106
  title: "Pi BTW",
91
107
  lines: [
92
- `Thinking: ${displayThinkingLevel(state.settings)} · Remember changes: ${effectiveRememberThinkingLevelChanges(state.settings) ? "On" : "Off"}`,
108
+ `Thinking: ${displayThinkingSummary(state.settings)} · Remember changes: ${displayRememberSummary(state.settings)}`,
93
109
  ],
94
110
  items: [
95
111
  {
@@ -111,7 +127,7 @@ export async function showBtwCommandMenu(
111
127
  {
112
128
  id: "settings",
113
129
  label: "Settings",
114
- description: "Choose pi-btw thinking and whether shortcut changes are remembered",
130
+ description: "Choose pi-btw thinking level and fixed-level shortcut memory",
115
131
  to: state.kind === "invalid" ? "invalid" : "settings",
116
132
  },
117
133
  ],
@@ -120,6 +136,7 @@ export async function showBtwCommandMenu(
120
136
  resume: () => ({
121
137
  kind: "choice",
122
138
  title: "Resume BTW side thread",
139
+ enableSearch: true,
123
140
  items: resumeThreads.map((thread) => ({
124
141
  id: thread.id,
125
142
  label: thread.title,
@@ -137,15 +154,15 @@ export async function showBtwCommandMenu(
137
154
  {
138
155
  id: "thinkingLevel",
139
156
  label: "Thinking level",
140
- description: "Set the starting level for future pi-btw side threads.",
157
+ description: `Set the starting level for future pi-btw side threads. Currently ${currentMainThinkingLevel}.`,
141
158
  currentValue: displayThinkingLevel(state.settings),
142
- values: levels,
159
+ values: [SAME_AS_MAIN_THREAD, ...levels],
143
160
  action: "set-thinking",
144
161
  },
145
162
  {
146
163
  id: "rememberThinkingLevelChanges",
147
164
  label: "Remember thinking level changes",
148
- description: "Save side-thread shortcut changes to pi-btw.json for next time.",
165
+ description: "Save shortcut changes for fixed thinking levels to pi-btw.json.",
149
166
  currentValue: effectiveRememberThinkingLevelChanges(state.settings) ? "On" : "Off",
150
167
  values: ["On", "Off"],
151
168
  action: "set-remember",
@@ -175,12 +192,16 @@ export async function showBtwCommandMenu(
175
192
  return { kind: "close" } as const;
176
193
  },
177
194
  "set-thinking": async ({ value, signal }) => {
178
- if (!value || !levels.includes(value as BtwThinkingLevel)) return { kind: "rejected" };
195
+ if (!value) return { kind: "rejected" };
196
+ const patch =
197
+ value === SAME_AS_MAIN_THREAD
198
+ ? ({ thinkingLevel: undefined } satisfies BtwSettingsPatch)
199
+ : levels.includes(value as BtwThinkingLevel)
200
+ ? ({ thinkingLevel: value as BtwThinkingLevel } satisfies BtwSettingsPatch)
201
+ : undefined;
202
+ if (!patch) return { kind: "rejected" };
179
203
  try {
180
- await updateSettings(
181
- { thinkingLevel: value as BtwThinkingLevel },
182
- { settingsPath, signal },
183
- );
204
+ await updateSettings(patch, { settingsPath, signal });
184
205
  if (signal.aborted) return { kind: "rejected" };
185
206
  notifySafely(ctx, `Pi BTW thinking level: ${value}.`, "info");
186
207
  return { kind: "stay" };
package/src/settings.ts CHANGED
@@ -20,6 +20,11 @@ export type BtwSettingsLoadResult =
20
20
  | { kind: "invalid"; reason: string }
21
21
  | { kind: "loaded"; settings: BtwSettings };
22
22
 
23
+ export interface BtwSettingsPatch {
24
+ thinkingLevel?: BtwThinkingLevel;
25
+ rememberThinkingLevelChanges?: boolean;
26
+ }
27
+
23
28
  export interface UpdateBtwSettingsOptions {
24
29
  settingsPath?: string;
25
30
  signal?: AbortSignal;
@@ -77,14 +82,14 @@ export async function readBtwSettings(
77
82
  }
78
83
 
79
84
  export function updateBtwSettings(
80
- patch: Partial<Pick<BtwSettings, "thinkingLevel" | "rememberThinkingLevelChanges">>,
85
+ patch: BtwSettingsPatch,
81
86
  options: UpdateBtwSettingsOptions = {},
82
87
  ): Promise<BtwSettings> {
83
88
  const settingsPath = options.settingsPath ?? btwSettingsPath();
84
89
  return enqueueMutation(settingsPath, async () => {
85
90
  options.signal?.throwIfAborted();
86
91
  const current = await readSettingsDocumentForUpdate(settingsPath);
87
- const updated: SettingsDocument = { ...current, ...patch };
92
+ const updated = applyBtwSettingsPatch(current, patch);
88
93
  const settings = normalizeBtwSettings(updated);
89
94
  if (!settings) throw invalidSettingsError(settingsPath, "invalid settings shape");
90
95
  await publishSettings(settingsPath, updated, options.signal, options.beforeRename);
@@ -216,6 +221,21 @@ async function publishSettings(
216
221
  }
217
222
  }
218
223
 
224
+ function applyBtwSettingsPatch(
225
+ current: SettingsDocument,
226
+ patch: BtwSettingsPatch,
227
+ ): SettingsDocument {
228
+ const updated: SettingsDocument = { ...current };
229
+ if (Object.hasOwn(patch, "thinkingLevel")) {
230
+ if (patch.thinkingLevel === undefined) delete updated.thinkingLevel;
231
+ else updated.thinkingLevel = patch.thinkingLevel;
232
+ }
233
+ if (Object.hasOwn(patch, "rememberThinkingLevelChanges")) {
234
+ updated.rememberThinkingLevelChanges = patch.rememberThinkingLevelChanges;
235
+ }
236
+ return updated;
237
+ }
238
+
219
239
  function isSettingsDocument(value: unknown): value is SettingsDocument {
220
240
  return typeof value === "object" && value !== null && !Array.isArray(value);
221
241
  }