pi-btw 0.5.0 → 0.6.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
@@ -11,7 +11,9 @@ A small [pi](https://github.com/earendil-works/pi-mono) extension that adds a `/
11
11
  - opens a parallel side conversation without interrupting the main run
12
12
  - runs that side conversation as a real pi sub-session with `read` / `bash` / `edit` / `write` tool access
13
13
  - keeps a continuous BTW thread by default
14
+ - accepts `/side` as an alias for the `/btw` entry command
14
15
  - supports `/btw:tangent` for a contextless side thread that does not inherit the current main-session conversation
16
+ - supports `/btw:ask` for a read-only side thread that inherits main-session context but exposes only `read` / `grep` / `find` / `ls`
15
17
  - opens a focused BTW modal shell with its own composer and transcript
16
18
  - keeps the BTW overlay open while you switch focus back to the main editor with `Alt+/`, `Super+/`, or `Ctrl+Alt+W` (all remappable)
17
19
  - keeps BTW thread entries out of the main agent's future context
@@ -51,10 +53,13 @@ pi install /absolute/path/to/pi-btw
51
53
 
52
54
  ```text
53
55
  /btw what file defines this route?
56
+ /side what file defines this route?
54
57
  /btw how would you refactor this parser?
55
58
  /btw --save summarize the last error in one sentence
56
59
  /btw:new let's start a fresh thread about auth
57
60
  /btw:tangent brainstorm from first principles without using the current chat context
61
+ /btw:ask what does this module do?
62
+ /btw:ask --save explain the latest test failure
58
63
  /btw:model openai gpt-5-mini openai-responses
59
64
  /btw:thinking low
60
65
  /btw:inject implement the plan we just discussed
@@ -77,6 +82,12 @@ pi install /absolute/path/to/pi-btw
77
82
  - persists the BTW exchange as hidden thread state
78
83
  - with `--save`, also saves that single exchange as a visible session note
79
84
 
85
+ ### `/side [--save] <question>`
86
+
87
+ - alias for `/btw`, matching the equivalent command in Codex
88
+ - shares the same thread, overlay, persistence, model, and thinking settings as `/btw`
89
+ - `/btw` stays canonical; lifecycle commands remain under the `/btw:*` namespace, so there is no `/side:new` or `/side:clear`
90
+
80
91
  ## Overlay controls
81
92
 
82
93
  - `Alt+w` toggles the overlay between the framed window layout (inset from the terminal edges) and a full-width layout
@@ -107,6 +118,17 @@ pi install /absolute/path/to/pi-btw
107
118
  - opens or refreshes the same focused BTW modal shell
108
119
  - with `--save`, also saves that single exchange as a visible session note
109
120
 
121
+ ### `/btw:ask [--save] <question>`
122
+
123
+ - starts or continues an enforced read-only side thread
124
+ - inherits the current main-session conversation, exactly like `/btw`
125
+ - exposes only pi's built-in read-only tools (`read`, `grep`, `find`, `ls`); `bash`, `edit`, and `write` are never available to it
126
+ - follows up read-only for the lifetime of the thread
127
+ - identifies the thread as read-only in the overlay title
128
+ - if you switch between `/btw`, `/btw:tangent`, and `/btw:ask`, the previous side thread is cleared and the child session is recreated so the capability boundary stays unambiguous
129
+ - opens or refreshes the same focused BTW modal shell
130
+ - with `--save`, also saves that single exchange as a visible session note
131
+
110
132
  ### `/btw:clear`
111
133
 
112
134
  - dismisses the BTW modal/widget
@@ -148,6 +170,7 @@ BTW is implemented as an actual pi sub-session with its own in-memory session st
148
170
 
149
171
  - contextual `/btw` threads seed that sub-session from the current main-session branch while filtering out BTW-visible notes from the parent context
150
172
  - `/btw:tangent` starts the same BTW UI in a contextless mode with no inherited main-session conversation
173
+ - `/btw:ask` seeds the same main-session context as `/btw` but restricts the child session's tool surface to pi's read-only tools, so the boundary is structural rather than prompt-based
151
174
  - BTW can inherit the main thread model/thinking settings or use BTW-only overrides via `/btw:model` and `/btw:thinking`
152
175
  - `/btw:summarize` uses the current effective BTW model but keeps thinking off
153
176
  - the overlay transcript/status line is driven from sub-session events, so tool activity, streaming deltas, failures, and recovery are all visible without scraping rendered output
@@ -157,7 +180,7 @@ BTW is implemented as an actual pi sub-session with its own in-memory session st
157
180
 
158
181
  Inside the BTW modal composer, slash handling is split at the BTW/session boundary:
159
182
 
160
- - `/btw:new`, `/btw:tangent`, `/btw:clear`, `/btw:model`, `/btw:thinking`, `/btw:inject`, and `/btw:summarize` stay owned by BTW because they control BTW lifecycle, configuration, or handoff behavior
183
+ - `/btw:new`, `/btw:tangent`, `/btw:ask`, `/btw:clear`, `/btw:model`, `/btw:thinking`, `/btw:inject`, and `/btw:summarize` stay owned by BTW because they control BTW lifecycle, configuration, or handoff behavior
161
184
  - any other slash-prefixed input is routed through the BTW sub-session's normal `prompt()` path
162
185
  - this means ordinary pi slash commands like `/help` are handled by the sub-session instead of being rejected by a modal-only fallback
163
186
  - if the sub-session cannot handle a slash command, BTW surfaces the real sub-session failure through the transcript/status state instead of inventing an "unsupported slash input" warning
@@ -172,7 +195,7 @@ BTW exchanges are persisted in the session as hidden custom entries so they:
172
195
 
173
196
  - survive reloads and restarts
174
197
  - rehydrate the BTW modal shell for the current branch
175
- - preserve whether the current side thread is a normal `/btw` thread or a contextless `/btw:tangent`
198
+ - preserve whether the current side thread is a normal `/btw` thread, a contextless `/btw:tangent`, or a read-only `/btw:ask` thread
176
199
  - preserve the current BTW-only model and thinking overrides for that session history
177
200
  - stay out of the main agent's LLM context
178
201
 
@@ -210,6 +233,27 @@ To use it without installing:
210
233
  pi -e /path/to/pi-btw
211
234
  ```
212
235
 
236
+ ## DeepSeek Harness
237
+
238
+ pi-btw also runs unmodified on [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) through the [pi2dsh](https://github.com/weijiafu14/pi2dsh) compatibility bridge.
239
+
240
+ For DSH Web, install the **dsh-work-x** suite, which includes pi-btw, pi2dsh, a browser side-chat window, and other extensions:
241
+
242
+ ```bash
243
+ dsh plugin --profile web add dsh-work-x
244
+ ```
245
+
246
+ To install just the bridge and this extension instead:
247
+
248
+ ```bash
249
+ dsh plugin --profile web add pi2dsh
250
+ dsh plugin --profile web add pi-btw
251
+ ```
252
+
253
+ Restart DSH after installation, then use `/btw <question>` to start a side conversation. The suite presents it in a browser side-chat window backed by a native DSH child session. DSH uses hyphens for the command family: for example, `/btw:inject` becomes `/btw-inject`.
254
+
255
+ See the [DSH side-conversation guide](https://github.com/weijiafu14/pi2dsh/tree/main/examples/side-conversation) for CLI-only installation, usage, and screenshots, and the [versioned validation results](https://github.com/weijiafu14/pi2dsh/tree/main/community/release-0.25.1) for the tested releases. Report DSH integration problems to [pi2dsh](https://github.com/weijiafu14/pi2dsh/issues).
256
+
213
257
  ## License
214
258
 
215
259
  MIT
package/extensions/btw.ts CHANGED
@@ -176,7 +176,7 @@ const BTW_CONTINUE_THREAD_USER_TEXT = "[The following is a separate side convers
176
176
  const BTW_CONTINUE_THREAD_ASSISTANT_TEXT = "Understood, continuing our side conversation.";
177
177
 
178
178
  type SessionThinkingLevel = "off" | AiThinkingLevel;
179
- type BtwThreadMode = "contextual" | "tangent";
179
+ type BtwThreadMode = "contextual" | "tangent" | "readonly";
180
180
  type SessionModel = NonNullable<ExtensionCommandContext["model"]>;
181
181
  /**
182
182
  * Loose model reference parsed from `/btw:model <provider> <id> <api>` and persisted to
@@ -443,6 +443,17 @@ function formatModelRef(model: Pick<SessionModel, "provider" | "id" | "api">): s
443
443
  return `${model.provider}/${model.id} (${model.api})`;
444
444
  }
445
445
 
446
+ /**
447
+ * Tool surfaces keyed by BTW mode. Read-only mode exposes only pi's built-in
448
+ * read-only tools so the child session cannot mutate the workspace; every other
449
+ * mode matches pi's default coding-agent toolset (read/bash/edit/write).
450
+ */
451
+ const BTW_TOOLS_BY_MODE: Record<BtwThreadMode, readonly string[]> = {
452
+ contextual: ["read", "bash", "edit", "write"],
453
+ tangent: ["read", "bash", "edit", "write"],
454
+ readonly: ["read", "grep", "find", "ls"],
455
+ };
456
+
446
457
  function buildBtwSeedState(
447
458
  ctx: ExtensionCommandContext,
448
459
  thread: BtwDetails[],
@@ -451,7 +462,7 @@ function buildBtwSeedState(
451
462
  ): { messages: Message[]; sideThreadStartIndex: number } {
452
463
  const messages: Message[] = [];
453
464
 
454
- if (mode === "contextual") {
465
+ if (mode === "contextual" || mode === "readonly") {
455
466
  try {
456
467
  messages.push(
457
468
  ...(buildSessionContext(ctx.sessionManager.getEntries(), ctx.sessionManager.getLeafId()).messages as Message[]).filter(
@@ -1217,7 +1228,7 @@ function canRenderBtwOverlay(ctx: ExtensionContext | ExtensionCommandContext): b
1217
1228
 
1218
1229
  function notifyInlineQuestionRequired(
1219
1230
  ctx: ExtensionCommandContext,
1220
- command: "/btw" | "/btw:tangent" | "/btw:new",
1231
+ command: "/btw" | "/btw:tangent" | "/btw:new" | "/btw:ask",
1221
1232
  ): void {
1222
1233
  notify(ctx, `${command} cannot open its composer outside Pi's TUI. Pass the question inline instead.`, "warning");
1223
1234
  }
@@ -1234,7 +1245,13 @@ const BTW_OVERLAY_CHROME_LINES = 9;
1234
1245
  const BTW_BLOCK_INDENT = " ";
1235
1246
 
1236
1247
  function getOverlayTitle(mode: BtwThreadMode): string {
1237
- return mode === "tangent" ? "BTW tangent" : "BTW";
1248
+ if (mode === "tangent") {
1249
+ return "BTW tangent";
1250
+ }
1251
+ if (mode === "readonly") {
1252
+ return "BTW ask · read-only";
1253
+ }
1254
+ return "BTW";
1238
1255
  }
1239
1256
 
1240
1257
  function buildTranscriptBadge(
@@ -1964,8 +1981,8 @@ export default function (pi: ExtensionAPI) {
1964
1981
  model: settings.model,
1965
1982
  ...modelRuntimeOptions,
1966
1983
  thinkingLevel: settings.thinkingLevel,
1967
- // Match pi's default coding-agent toolset (read/bash/edit/write).
1968
- tools: ["read", "bash", "edit", "write"],
1984
+ // Read-only mode narrows this to pi's built-in read-only toolset.
1985
+ tools: [...BTW_TOOLS_BY_MODE[mode]],
1969
1986
  resourceLoader: createBtwResourceLoader(ctx),
1970
1987
  };
1971
1988
  const { session } = await createAgentSession(sessionOptions);
@@ -2138,6 +2155,29 @@ export default function (pi: ExtensionAPI) {
2138
2155
  return true;
2139
2156
  }
2140
2157
 
2158
+ if (name === "btw:ask") {
2159
+ const { question, save } = parseBtwArgs(trimmedArgs);
2160
+ if (!question && !canRenderBtwOverlay(ctx)) {
2161
+ notifyInlineQuestionRequired(ctx, "/btw:ask");
2162
+ return true;
2163
+ }
2164
+
2165
+ // Read-only mode is a distinct capability boundary, so switching into it
2166
+ // resets the thread and lets ensureBtwSession recreate the child session.
2167
+ if (pendingMode !== "readonly") {
2168
+ await resetThread(ctx, true, "readonly");
2169
+ }
2170
+
2171
+ if (!question) {
2172
+ await ensureBtwSession(ctx, "readonly");
2173
+ await ensureOverlay(ctx);
2174
+ return true;
2175
+ }
2176
+
2177
+ await runBtw(ctx, question, save, "readonly");
2178
+ return true;
2179
+ }
2180
+
2141
2181
  if (name === "btw:new") {
2142
2182
  const { question, save } = parseBtwArgs(trimmedArgs);
2143
2183
  if (!question && !canRenderBtwOverlay(ctx)) {
@@ -2274,7 +2314,7 @@ export default function (pi: ExtensionAPI) {
2274
2314
 
2275
2315
  function parseOverlayBtwCommand(value: string): { name: string; args: string } | null {
2276
2316
  const trimmed = value.trim();
2277
- const match = trimmed.match(/^\/(btw:(?:new|tangent|clear|inject|summarize|model|thinking))(?:\s+(.*))?$/);
2317
+ const match = trimmed.match(/^\/(btw:(?:new|ask|tangent|clear|inject|summarize|model|thinking))(?:\s+(.*))?$/);
2278
2318
  if (!match) {
2279
2319
  return null;
2280
2320
  }
@@ -2755,6 +2795,13 @@ export default function (pi: ExtensionAPI) {
2755
2795
  },
2756
2796
  });
2757
2797
 
2798
+ pi.registerCommand("side", {
2799
+ description: "Alias for /btw: continue a side conversation in a focused BTW modal.",
2800
+ handler: async (args, ctx) => {
2801
+ await dispatchBtwCommand("btw", args, ctx);
2802
+ },
2803
+ });
2804
+
2758
2805
  pi.registerCommand("btw:tangent", {
2759
2806
  description: "Start or continue a contextless BTW tangent in the focused BTW modal.",
2760
2807
  handler: async (args, ctx) => {
@@ -2762,6 +2809,13 @@ export default function (pi: ExtensionAPI) {
2762
2809
  },
2763
2810
  });
2764
2811
 
2812
+ pi.registerCommand("btw:ask", {
2813
+ description: "Ask a read-only side question: inherits main-session context but exposes only read/grep/find/ls tools.",
2814
+ handler: async (args, ctx) => {
2815
+ await dispatchBtwCommand("btw:ask", args, ctx);
2816
+ },
2817
+ });
2818
+
2765
2819
  pi.registerCommand("btw:new", {
2766
2820
  description: "Start a fresh BTW thread with main-session context. Optionally ask the first question immediately.",
2767
2821
  handler: async (args, ctx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-btw",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "A pi extension for parallel side conversations with /btw",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -22,10 +22,13 @@ Use these commands in your guidance to the user:
22
22
 
23
23
  ```text
24
24
  /btw <question>
25
+ /side <question>
25
26
  /btw --save <question>
26
27
  /btw:new [question]
27
28
  /btw:tangent <question>
28
29
  /btw:tangent --save <question>
30
+ /btw:ask <question>
31
+ /btw:ask --save <question>
29
32
  /btw:clear
30
33
  /btw:model [<provider> <model> <api> | clear]
31
34
  /btw:thinking [<level> | clear]
@@ -33,6 +36,8 @@ Use these commands in your guidance to the user:
33
36
  /btw:summarize [instructions]
34
37
  ```
35
38
 
39
+ `/side` is an alias for `/btw` and shares the same thread, overlay, and settings. `/btw` is canonical; the lifecycle commands stay in the `/btw:*` namespace.
40
+
36
41
  ## How to guide the user
37
42
 
38
43
  ### For a quick side question
@@ -45,6 +50,8 @@ Recommend:
45
50
 
46
51
  Use this when the user wants an immediate aside and does not need a visible saved note.
47
52
 
53
+ Users coming from Codex may reach for `/side` instead; it is an alias for `/btw` and can be used anywhere `/btw` is used.
54
+
48
55
  ### For a saved one-off note
49
56
 
50
57
  Recommend:
@@ -87,6 +94,22 @@ or
87
94
 
88
95
  Use this when the user wants a side conversation that does not include the current main-session context.
89
96
 
97
+ ### For an enforced read-only side question
98
+
99
+ Recommend:
100
+
101
+ ```text
102
+ /btw:ask <question>
103
+ ```
104
+
105
+ or
106
+
107
+ ```text
108
+ /btw:ask --save <question>
109
+ ```
110
+
111
+ Use this when the user wants a side question that inherits the current main-session context but must not be able to change anything. The read-only thread only has `read`, `grep`, `find`, and `ls`; it never has `bash`, `edit`, or `write`.
112
+
90
113
  ### To hand the full thread back to the main agent
91
114
 
92
115
  Recommend:
@@ -122,6 +145,7 @@ Use these when the main thread should keep its current model or thinking level,
122
145
 
123
146
  - Prefer `/btw` over normal chat when the user explicitly wants a side conversation.
124
147
  - Prefer `/btw:tangent` when the user wants that side conversation to be contextless.
148
+ - Prefer `/btw:ask` when the user wants a side conversation that cannot modify the workspace.
125
149
  - Prefer `/btw:summarize` over `/btw:inject` for long exploratory threads.
126
150
  - Prefer `/btw:inject` when precise wording, detailed tradeoffs, or a full plan matters.
127
151
  - Suggest `/btw:new` before starting a totally unrelated side topic when main-session context is still useful.
@@ -156,6 +180,12 @@ When helping the user use BTW:
156
180
  /btw:tangent think through this from first principles without using the current chat context
157
181
  ```
158
182
 
183
+ ### Example: ask a read-only side question
184
+
185
+ ```text
186
+ /btw:ask what does the token refresh path do without changing anything?
187
+ ```
188
+
159
189
  ### Example: send the result back
160
190
 
161
191
  ```text