pi-better-btw-plus 1.0.2 → 1.1.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 +13 -11
- package/README.zh-CN.md +13 -11
- package/package.json +1 -1
- package/prompts/lane-preamble.md +1 -1
- package/prompts/lane-reminder-base.md +1 -1
- package/srcs/clipboard-read.ts +16 -13
- package/srcs/config.ts +46 -51
- package/srcs/fork-turn.ts +376 -0
- package/srcs/index.ts +11 -32
- package/srcs/overlay-layout.ts +302 -0
- package/srcs/pointer-gesture.ts +261 -0
- package/srcs/provider-retry.ts +73 -0
- package/srcs/retry.ts +9 -0
- package/srcs/shortcuts.ts +38 -1
- package/srcs/side-chat-overlay.ts +358 -647
- package/srcs/status-channel.ts +170 -0
- package/srcs/tool-wrapper.ts +8 -184
- package/srcs/write-paths.ts +235 -0
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ You're in the middle of a longer task and want to ask something small without de
|
|
|
32
32
|
Everything in [`@yceachan/pi-better-btw`](https://www.npmjs.com/package/@yceachan/pi-better-btw) is here, plus:
|
|
33
33
|
|
|
34
34
|
- **Right-click copy & paste** — drag-select chat text and right-click to copy (Windows Terminal muscle memory); right-click inside the input editor pastes the system clipboard through the editor's own normalization and `[paste #N …]` markers for large pastes. No more hotkey-only copying.
|
|
35
|
-
- **Fork model switching (`
|
|
35
|
+
- **Fork model switching (`Ctrl+L`)** — pick any authenticated model for the side chat without rebuilding the fork; fork-local only (ADR 0002), thinking level auto-clamped to the new model's capabilities.
|
|
36
36
|
- **Turn-level auto-retry** — shares the main session's `settings.retry` budget: transient provider errors back off and retry with a live countdown; `Esc` cancels.
|
|
37
37
|
- **Feature kill switches** — the layered config's `features` block turns any of the above off (`rightClickCopyPaste` / `modelSwitch` / `retry`), plus `readOnlyExtensionAllowlistExclude` to drop bundled allowlist defaults.
|
|
38
38
|
|
|
@@ -54,7 +54,7 @@ See [Feat](#feat) for the full feature set.
|
|
|
54
54
|
|
|
55
55
|
- `TUI scroll, select, copy`: subscribes to mouse/hotkey events in the TUI overlay for scrolling, text selection, and `Ctrl+C` copy.
|
|
56
56
|
|
|
57
|
-
- `Readonly/Edit Mode`: read-only by default to answer btw questions; if you want the agent to make small edits along the way, `
|
|
57
|
+
- `Readonly/Edit Mode`: read-only by default to answer btw questions; if you want the agent to make small edits along the way, `Alt+T` switches to edit mode.
|
|
58
58
|
|
|
59
59
|
- ToolAllowList: bundle + config.json custom
|
|
60
60
|
|
|
@@ -72,13 +72,13 @@ Press `Esc` to close it. Reopen with `/btw` or `Alt+W` to continue where you lef
|
|
|
72
72
|
| Shortcut | Action |
|
|
73
73
|
| -------- | ------ |
|
|
74
74
|
| `Alt+W` | Open (when closed) / background (when visible) / restore (when hidden) |
|
|
75
|
-
| `
|
|
75
|
+
| `Alt+T` | Toggle read-only / edit mode |
|
|
76
76
|
| `Alt+R` | Re-fork from the latest main context |
|
|
77
77
|
| `Alt+N` | Start an empty conversation |
|
|
78
78
|
| `Alt+E` | Export the transcript to `$CWD/.agents/eval/pi-better-btw-<timestamp>.md` |
|
|
79
|
-
| `
|
|
79
|
+
| `Ctrl+L` | Open the fork model picker (scoped + authenticated models; `↑/↓` select, `Enter` confirm, `Esc` cancel) |
|
|
80
80
|
|
|
81
|
-
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 (`
|
|
81
|
+
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 (`Alt+T`) is unaffected.
|
|
82
82
|
|
|
83
83
|
**Peek at the main agent** — the `peek_main` tool reads recent activity from the main session.
|
|
84
84
|
|
|
@@ -98,7 +98,7 @@ What changed since I opened this side chat?
|
|
|
98
98
|
**Right-click paste in the input box** — right-click inside the editor pastes the system clipboard at the cursor through the editor's built-in paste entry: line endings/tabs are normalized (`\r`→`\n`, `\t`→4 spaces), large pastes (>10 lines or >1000 chars) collapse to a `[paste #N +X lines]` / `[paste #N X chars]` marker that expands back to full text on submit, and the paste is a single undo step. When the clipboard can't be read (platform channel + OSC 52 fallback both unavailable) or holds no text, a one-line hint appears and the editor is left untouched.
|
|
99
99
|
|
|
100
100
|
**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.
|
|
101
|
-
**Fork model switching** — `
|
|
101
|
+
**Fork model switching** — `Ctrl+L` opens a model picker inside the overlay (`↑/↓` move, `Enter` confirm, `Esc` cancel). It lists the session's scoped models first (`--models` / `enabledModels`), falling back to the available catalogue, and only shows models with configured auth. Confirming swaps the fork agent's runtime model — the next turn uses it without rebuilding the fork — and clamps the thinking level to the new model's capabilities (no-reasoning models go to `off`). The choice is fork-local (ADR 0002): the main session's model is never touched. It survives backgrounding (`Alt+W`) and resets on `Alt+R`/`Alt+N`/`Esc` close. The header shows the current fork model; opening is rejected while streaming.
|
|
102
102
|
**Auto-retry (turn-level)** — reads pi's `settings.retry` budget (`enabled` / `maxRetries` / `baseDelayMs`, same defaults as the main session). Transient provider errors (overloaded / rate limit / 5xx) auto-retry with exponential backoff — the status area shows `Retrying (n/m) in Xs…` with a live countdown — and the failed assistant message is stripped before the retry so it never re-enters the next request. Context overflow and aborts never retry. `Esc` during the backoff cancels the wait and surfaces the last error as the final result; budget exhaustion does the same. With `enabled: false` (e.g. local-model debugging) errors surface immediately with zero overhead.
|
|
103
103
|
|
|
104
104
|
|
|
@@ -112,14 +112,16 @@ What changed since I opened this side chat?
|
|
|
112
112
|
| `Alt+R` | Re-fork from latest main context |
|
|
113
113
|
| `Alt+N` | Start empty conversation |
|
|
114
114
|
| `Alt+E` | Export the btw chat history to `$CWD/.agents/eval/pi-better-btw-<timestamp>.md` |
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
115
|
+
| `Ctrl+L` | Open the fork model picker (`↑/↓` select, `Enter` confirm, `Esc` cancel) |
|
|
116
|
+
| `Alt+T` | Toggle read-only / edit mode |
|
|
117
117
|
| `PgUp` / `PgDn` | Scroll history by a page |
|
|
118
118
|
| `Shift+↑` / `Shift+↓` | Scroll by a few lines |
|
|
119
119
|
| Mouse wheel | Scroll when the pointer is over the chat |
|
|
120
120
|
| Mouse drag | Select text in the chat area (inverse-video highlight); no copy on release |
|
|
121
121
|
| Double-click | Select the whole rendered line |
|
|
122
122
|
| `Ctrl+C` / `Ctrl+Shift+C` | Copy the active mouse selection (the selection is kept until you click elsewhere, so repeated presses re-copy) |
|
|
123
|
+
| `Ctrl+X` | Copy the last assistant message |
|
|
124
|
+
| `Ctrl+V` / `Alt+V` | Paste the system clipboard at the cursor (same normalization + `[paste #N …]` markers as right-click paste) |
|
|
123
125
|
| Mouse right-click (chat area) | Copy the retained mouse selection (fires on release, keeps the highlight) |
|
|
124
126
|
| Mouse right-click (input editor) | Paste the system clipboard at the cursor (editor normalization + `[paste #N …]` markers for large pastes) |
|
|
125
127
|
## Command Reference
|
|
@@ -161,7 +163,7 @@ Keys:
|
|
|
161
163
|
| Switch | Behavior when `false` |
|
|
162
164
|
|--------|------------------------|
|
|
163
165
|
| `rightClickCopyPaste` | right-click copy (chat) / paste (editor) is inert — the hotkeys still work |
|
|
164
|
-
| `modelSwitch` | `
|
|
166
|
+
| `modelSwitch` | `Ctrl+L` does nothing |
|
|
165
167
|
| `retry` | fork turns run a single attempt with zero backoff, even if pi's `settings.retry` is enabled |
|
|
166
168
|
|
|
167
169
|
Example (user or project layer):
|
|
@@ -215,8 +217,8 @@ Structure:
|
|
|
215
217
|
│ ├── side-chat-mouse.ts # minimal SGR mouse parsing
|
|
216
218
|
│ ├── clipboard-read.ts # platform clipboard read (win32 / darwin / linux + OSC 52 fallback)
|
|
217
219
|
│ ├── retry.ts # turn-level retry: classifyRetryable + runWithRetry
|
|
218
|
-
│ ├── shortcuts.ts # hotkey bindings (Alt+W /
|
|
219
|
-
│ ├── model-switch.ts #
|
|
220
|
+
│ ├── shortcuts.ts # hotkey bindings (Alt+W / Alt+T)
|
|
221
|
+
│ ├── model-switch.ts # Ctrl+L fork model picker: list building + thinking clamp
|
|
220
222
|
│ ├── side-chat-export.ts # Alt+E transcript export
|
|
221
223
|
│ ├── tool-wrapper.ts # write-path overlap warnings
|
|
222
224
|
│ └── file-activity-tracker.ts
|
package/README.zh-CN.md
CHANGED
|
@@ -32,7 +32,7 @@ pi install npm:pi-better-btw-plus
|
|
|
32
32
|
[`@yceachan/pi-better-btw`](https://www.npmjs.com/package/@yceachan/pi-better-btw) 的全部功能都在,另新增:
|
|
33
33
|
|
|
34
34
|
- **右键复制与粘贴** —— 聊天区拖选后右键复制(延续 Windows Terminal 肌肉记忆);输入框右键从系统剪贴板粘贴,走编辑器内置归一化与大段 `[paste #N …]` 折叠标记。不再是 hotkey-only。
|
|
35
|
-
- **Fork 模型切换(`
|
|
35
|
+
- **Fork 模型切换(`Ctrl+L`)** —— 侧聊内选择任意已认证模型,无需重建 fork;仅作用于 fork 本身(ADR 0002),thinking level 按新模型能力自动钳制。
|
|
36
36
|
- **Turn 级自动重试** —— 与主会话共用 `settings.retry` 预算:瞬时 provider 错误指数退避重试,带实时倒计时;`Esc` 取消。
|
|
37
37
|
- **功能开关** —— 分层配置的 `features` 块可关闭以上任意功能(`rightClickCopyPaste` / `modelSwitch` / `retry`),另有 `readOnlyExtensionAllowlistExclude` 移除内置 allowlist 默认项。
|
|
38
38
|
|
|
@@ -54,7 +54,7 @@ pi install npm:pi-better-btw-plus
|
|
|
54
54
|
|
|
55
55
|
- `TUI scroll,select,copy` :在TUI-overlay 自订阅鼠标/hotkey事件,实现滚屏 ,text选中 , Ctrl +C 复制功能
|
|
56
56
|
|
|
57
|
-
- `Readonly/Edit Mode` : 默认只读来回应btw question,如果你希望Agent顺手做些小修改,
|
|
57
|
+
- `Readonly/Edit Mode` : 默认只读来回应btw question,如果你希望Agent顺手做些小修改,Alt + T To Edit Mode.
|
|
58
58
|
|
|
59
59
|
- ToolAllowList : bundle + config.json custom
|
|
60
60
|
|
|
@@ -74,13 +74,13 @@ pi install npm:pi-better-btw-plus
|
|
|
74
74
|
| 快捷键 | 作用 |
|
|
75
75
|
| ------ | ---- |
|
|
76
76
|
| `Alt+W` | 打开(关闭时)/ 后台化(显示时)/ 恢复(隐藏时) |
|
|
77
|
-
| `
|
|
77
|
+
| `Alt+T` | 切换只读 / 编辑模式 |
|
|
78
78
|
| `Alt+R` | 从最新主线上下文重新 fork |
|
|
79
79
|
| `Alt+N` | 开始空白对话 |
|
|
80
80
|
| `Alt+E` | 导出对话记录到 `$CWD/.agents/eval/pi-better-btw-<timestamp>.md` |
|
|
81
|
-
| `
|
|
81
|
+
| `Ctrl+L` | 打开 fork 模型选择器(scoped + 已认证模型;`↑/↓` 选择,`Enter` 确认,`Esc` 取消) |
|
|
82
82
|
|
|
83
|
-
在Readonly Mode(default),只读车道是**强制的**:越权调用工具会被硬阻断并注入prompt;第二次违规会升级措辞并中止该轮,提示(`🚧 lane blocked` 状态行)。已执行但失败的只读调用会被 `afterToolCall` 备注再次归位。编辑模式(`
|
|
83
|
+
在Readonly Mode(default),只读车道是**强制的**:越权调用工具会被硬阻断并注入prompt;第二次违规会升级措辞并中止该轮,提示(`🚧 lane blocked` 状态行)。已执行但失败的只读调用会被 `afterToolCall` 备注再次归位。编辑模式(`Alt+T`)不受影响。
|
|
84
84
|
|
|
85
85
|
**窥视主线 agent** —— `peek_main` 工具读取主线会话的近期活动。
|
|
86
86
|
|
|
@@ -101,7 +101,7 @@ What changed since I opened this side chat?
|
|
|
101
101
|
|
|
102
102
|
**对话导出** —— `Alt+E` 把 btw 历史(fork 上下文、framing 块、对话、流式中内容)导出为 `$CWD/.agents/eval/pi-better-btw-<timestamp>.md` 的 markdown 诊断产物,便于调试功能开发。
|
|
103
103
|
|
|
104
|
-
**Fork 模型切换** —— `
|
|
104
|
+
**Fork 模型切换** —— `Ctrl+L` 在浮层内打开模型选择器(`↑/↓` 移动,`Enter` 确认,`Esc` 取消)。列表优先展示会话作用域模型(`--models` / `enabledModels`),为空时回退可用模型目录,且只显示已配置认证的模型。确认后直接替换 fork agent 的运行时模型——下一 turn 生效,无需重建 fork——并按新模型能力钳制 thinking level(无 reasoning 的模型钳制为 `off`)。选择为 fork 局部状态(ADR 0002):主会话模型不受影响。后台化(`Alt+W`)保留选择;`Alt+R` / `Alt+N` / `Esc` 关闭后随实例重置。头部显示当前 fork 模型;流式期间拒绝打开。
|
|
105
105
|
|
|
106
106
|
## 快捷键
|
|
107
107
|
|
|
@@ -113,14 +113,16 @@ What changed since I opened this side chat?
|
|
|
113
113
|
| `Alt+R` | 从最新主线上下文重新 fork |
|
|
114
114
|
| `Alt+N` | 开始空白对话 |
|
|
115
115
|
| `Alt+E` | 导出 btw 对话历史到 `$CWD/.agents/eval/pi-better-btw-<timestamp>.md` |
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
116
|
+
| `Ctrl+L` | 打开 fork 模型选择器(`↑/↓` 选择,`Enter` 确认,`Esc` 取消) |
|
|
117
|
+
| `Alt+T` | 切换只读 / 编辑模式 |
|
|
118
118
|
| `PgUp` / `PgDn` | 整页滚动历史 |
|
|
119
119
|
| `Shift+↑` / `Shift+↓` | 按行滚动 |
|
|
120
120
|
| 鼠标滚轮 | 指针位于聊天区域时滚动 |
|
|
121
121
|
| 鼠标拖拽 | 选择聊天文本(反色高亮);松开不自动复制 |
|
|
122
122
|
| 双击 | 选择整行 |
|
|
123
123
|
| `Ctrl+C` / `Ctrl+Shift+C` | 复制当前鼠标选择(选择保留到下次点击,可重复复制) |
|
|
124
|
+
| `Ctrl+X` | 复制最后一条 assistant 消息 |
|
|
125
|
+
| `Ctrl+V` / `Alt+V` | 把系统剪贴板粘贴到光标处(归一化 + `[paste #N …]` 标记,同右键粘贴) |
|
|
124
126
|
| 鼠标右键(聊天区) | 复制保留的鼠标选择(松开时触发,保持高亮) |
|
|
125
127
|
| 鼠标右键(输入框) | 把系统剪贴板粘贴到光标处(编辑器归一化 + 大段 `[paste #N …]` 标记) |
|
|
126
128
|
## 命令参考
|
|
@@ -162,7 +164,7 @@ pi-better-btw 从三个位置按优先级递增读取 `config.json` —— 每
|
|
|
162
164
|
| 开关 | 为 `false` 时的行为 |
|
|
163
165
|
| ---- | ---- |
|
|
164
166
|
| `rightClickCopyPaste` | 右键复制(聊天区)/ 粘贴(输入框)失效——快捷键不受影响 |
|
|
165
|
-
| `modelSwitch` | `
|
|
167
|
+
| `modelSwitch` | `Ctrl+L` 无反应 |
|
|
166
168
|
| `retry` | fork 每轮只跑一次尝试、零退避,即使 pi 的 `settings.retry` 开启 |
|
|
167
169
|
|
|
168
170
|
示例(用户或项目层):
|
|
@@ -216,8 +218,8 @@ btw 上下文保留主线的 system prompt 于 system 槽位,并逐字注入 f
|
|
|
216
218
|
│ ├── side-chat-mouse.ts # 最小 SGR 鼠标解析
|
|
217
219
|
│ ├── clipboard-read.ts # 平台剪贴板读取(win32 / darwin / linux + OSC 52 兜底)
|
|
218
220
|
│ ├── retry.ts # turn 级重试引擎:classifyRetryable + runWithRetry
|
|
219
|
-
│ ├── model-switch.ts #
|
|
220
|
-
│ ├── shortcuts.ts # 快捷键绑定(Alt+W /
|
|
221
|
+
│ ├── model-switch.ts # Ctrl+L fork 模型选择器:列表构建 + thinking 钳制
|
|
222
|
+
│ ├── shortcuts.ts # 快捷键绑定(Alt+W / Alt+T)
|
|
221
223
|
│ ├── side-chat-export.ts # Alt+E 对话导出
|
|
222
224
|
│ ├── tool-wrapper.ts # 写路径重叠警告
|
|
223
225
|
│ └── file-activity-tracker.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-better-btw-plus",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "pi extension: /btw side-chat overlay — maintained fork of @yceachan/pi-better-btw (+ right-click copy/paste, fork model switch, turn-level retry)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
package/prompts/lane-preamble.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
You are in the read-only lane: only read/grep/find/ls, allowlisted tools and peek_main are available here. You cannot write files, edit, or execute commands — that is the main lane's job. If a write is genuinely needed, tell the user to switch to edit mode (
|
|
1
|
+
You are in the read-only lane: only read/grep/find/ls, allowlisted tools and peek_main are available here. You cannot write files, edit, or execute commands — that is the main lane's job. If a write is genuinely needed, tell the user to switch to edit mode (Alt+T) or ask the main agent.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
🚧 Lane blocked: `{{tool}}` is not available in this side chat. You are the btw side chat — a quick-question lane parallel to the main agent; the main line is the main agent's job. Answer the latest user message only, with read-only tools (read/grep/find/ls + allowlisted tools). If a write is genuinely needed, tell the user to switch to edit mode (
|
|
1
|
+
🚧 Lane blocked: `{{tool}}` is not available in this side chat. You are the btw side chat — a quick-question lane parallel to the main agent; the main line is the main agent's job. Answer the latest user message only, with read-only tools (read/grep/find/ls + allowlisted tools). If a write is genuinely needed, tell the user to switch to edit mode (Alt+T).
|
package/srcs/clipboard-read.ts
CHANGED
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
*
|
|
5
5
|
* The write side is pi's own `copyToClipboard` (public export); the read side
|
|
6
6
|
* is self-built because `readClipboardText` is not re-exported from the
|
|
7
|
-
* package entry.
|
|
7
|
+
* package entry. The PRIMARY channel mirrors pi's own `readClipboardText` (the
|
|
8
|
+
* `@mariozechner/clipboard` native addon); the remaining channels — PowerShell
|
|
9
|
+
* `Get-Clipboard -Raw` (win32), `pbpaste` (darwin), and an OSC 52 query — are
|
|
10
|
+
* extension-only fallbacks pi's read side does not have:
|
|
8
11
|
*
|
|
9
|
-
* - win32 → native addon `getText
|
|
10
|
-
*
|
|
12
|
+
* - win32 → native addon `getText`, then PowerShell `Get-Clipboard -Raw`,
|
|
13
|
+
* then an OSC 52 query;
|
|
11
14
|
* - darwin → native addon `getText`, then `pbpaste`, then OSC 52 query;
|
|
12
15
|
* - linux → OSC 52 query (`\x1b]52;c;?\x07`, read the reply).
|
|
13
16
|
*
|
|
@@ -124,11 +127,11 @@ export interface CommandChannelOptions {
|
|
|
124
127
|
}
|
|
125
128
|
|
|
126
129
|
/**
|
|
127
|
-
* PowerShell
|
|
128
|
-
*
|
|
129
|
-
* `-Raw` flag preserves the exact text (no extra newline), and the
|
|
130
|
-
* encoding is pinned to UTF-8 because the text travels over the console
|
|
131
|
-
* pipe
|
|
130
|
+
* PowerShell read fallback (extension-only; pi's readClipboardText has no
|
|
131
|
+
* PowerShell channel): `powershell.exe -NoProfile -Command "Get-Clipboard
|
|
132
|
+
* -Raw"`. The `-Raw` flag preserves the exact text (no extra newline), and the
|
|
133
|
+
* output encoding is pinned to UTF-8 because the text travels over the console
|
|
134
|
+
* pipe.
|
|
132
135
|
*/
|
|
133
136
|
const PS_GET_CLIPBOARD_SCRIPT =
|
|
134
137
|
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Clipboard -Raw";
|
|
@@ -244,11 +247,11 @@ export function makeNativeChannel(
|
|
|
244
247
|
}
|
|
245
248
|
|
|
246
249
|
/**
|
|
247
|
-
* PowerShell
|
|
248
|
-
*
|
|
249
|
-
* `-Raw` flag preserves the exact text (no extra newline), and the
|
|
250
|
-
* encoding is pinned to UTF-8 because the text travels over the console
|
|
251
|
-
* pipe
|
|
250
|
+
* PowerShell read fallback (extension-only; pi's readClipboardText has no
|
|
251
|
+
* PowerShell channel): `powershell.exe -NoProfile -Command "Get-Clipboard
|
|
252
|
+
* -Raw"`. The `-Raw` flag preserves the exact text (no extra newline), and the
|
|
253
|
+
* output encoding is pinned to UTF-8 because the text travels over the console
|
|
254
|
+
* pipe.
|
|
252
255
|
*/
|
|
253
256
|
export function makePowerShellChannel(
|
|
254
257
|
options: CommandChannelOptions = {},
|
package/srcs/config.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { isAbsolute, join } from "node:path";
|
|
4
|
+
import { SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
4
5
|
import type { PromptPackManifest } from "./prompt-pack.ts";
|
|
6
|
+
import type { ProviderRetrySettings } from "./provider-retry.ts";
|
|
5
7
|
import type { RetryPolicy } from "./retry.ts";
|
|
6
8
|
/**
|
|
7
9
|
* Layered config resolution for pi-better-btw.
|
|
@@ -36,7 +38,7 @@ import type { RetryPolicy } from "./retry.ts";
|
|
|
36
38
|
export interface SideChatFeatures {
|
|
37
39
|
/** Right-click copy (chat selection) / paste (editor). Default: true. */
|
|
38
40
|
rightClickCopyPaste: boolean;
|
|
39
|
-
/**
|
|
41
|
+
/** Ctrl+L fork model picker. Default: true. */
|
|
40
42
|
modelSwitch: boolean;
|
|
41
43
|
/** Turn-level auto-retry of transient provider errors. Default: true. */
|
|
42
44
|
retry: boolean;
|
|
@@ -65,13 +67,15 @@ export const USER_CONFIG_DIR = join(homedir(), ".pi", "agent", CONFIG_SUBDIR);
|
|
|
65
67
|
export const AGENT_CONFIG_DIR = join(homedir(), ".pi", "agent");
|
|
66
68
|
|
|
67
69
|
/**
|
|
68
|
-
* Read pi's `settings.retry` budget (D8)
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* (
|
|
73
|
-
*
|
|
74
|
-
*
|
|
70
|
+
* Read pi's `settings.retry` budget (D8) via pi's own `SettingsManager` —
|
|
71
|
+
* file reading, deep merge (project wins per key), legacy migration
|
|
72
|
+
* (`retry.maxDelayMs` → `retry.provider.maxRetryDelayMs`) and the canonical
|
|
73
|
+
* defaults (enabled=true, maxRetries=3, baseDelayMs=2000) all come from pi; no
|
|
74
|
+
* hand-rolled duplicate. The `retry.provider` block (spec #20 D4: timeoutMs /
|
|
75
|
+
* maxRetries / maxRetryDelayMs) is forwarded only when a provider block is
|
|
76
|
+
* actually configured, and only number keys; it is consumed solely by the
|
|
77
|
+
* overlay's stream assembly, never by the turn loop. A present-but-unreadable
|
|
78
|
+
* settings file warns and contributes nothing (pi's own fallback).
|
|
75
79
|
*/
|
|
76
80
|
export interface LoadRetryPolicyOptions {
|
|
77
81
|
/** Agent config dir holding pi's global settings.json (~/.pi/agent). */
|
|
@@ -85,53 +89,44 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
|
85
89
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
86
90
|
}
|
|
87
91
|
|
|
88
|
-
/** Deep merge like pi's deepMergeSettings: nested plain objects merge, arrays/others replace. */
|
|
89
|
-
function deepMergeSettings(
|
|
90
|
-
base: Record<string, unknown>,
|
|
91
|
-
overrides: Record<string, unknown>,
|
|
92
|
-
): Record<string, unknown> {
|
|
93
|
-
const result = { ...base };
|
|
94
|
-
for (const [key, value] of Object.entries(overrides)) {
|
|
95
|
-
const baseValue = result[key];
|
|
96
|
-
result[key] =
|
|
97
|
-
isPlainRecord(baseValue) && isPlainRecord(value)
|
|
98
|
-
? deepMergeSettings(baseValue, value)
|
|
99
|
-
: value;
|
|
100
|
-
}
|
|
101
|
-
return result;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function readSettingsFile(
|
|
105
|
-
path: string,
|
|
106
|
-
onWarning?: (message: string) => void,
|
|
107
|
-
): Record<string, unknown> {
|
|
108
|
-
try {
|
|
109
|
-
const raw: unknown = JSON.parse(readFileSync(path, "utf-8"));
|
|
110
|
-
return isPlainRecord(raw) ? raw : {};
|
|
111
|
-
} catch {
|
|
112
|
-
if (existsSync(path) && onWarning) {
|
|
113
|
-
onWarning(`pi-better-btw: ignoring invalid settings ${path}`);
|
|
114
|
-
}
|
|
115
|
-
return {};
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
92
|
|
|
119
93
|
export function loadRetryPolicy(options: LoadRetryPolicyOptions = {}): RetryPolicy {
|
|
120
94
|
const agentConfigDir = options.agentConfigDir ?? AGENT_CONFIG_DIR;
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
)
|
|
127
|
-
|
|
95
|
+
const settings = SettingsManager.create(options.cwd ?? process.cwd(), agentConfigDir, {
|
|
96
|
+
projectTrusted: true,
|
|
97
|
+
});
|
|
98
|
+
// Surface load errors (invalid JSON etc.) through the caller's warning hook,
|
|
99
|
+
// mirroring the old loader's present-but-unreadable → warn behavior.
|
|
100
|
+
for (const err of settings.drainErrors()) {
|
|
101
|
+
options.onWarning?.(`pi-better-btw: ignoring invalid settings (${err.scope})`);
|
|
102
|
+
}
|
|
103
|
+
const retry = settings.getRetrySettings();
|
|
104
|
+
// Provider block (spec #20): forward only number keys from an actually
|
|
105
|
+
// configured `retry.provider`. getProviderRetrySettings() defaults
|
|
106
|
+
// maxRetryDelayMs to 60000 even when no block exists, so configured-ness is
|
|
107
|
+
// detected from the (already-migrated) per-scope settings; an absent block
|
|
108
|
+
// keeps `provider` undefined so the overlay's stream assembly stays identity
|
|
109
|
+
// (D3: unconfigured users keep the bare streamSimple, zero overhead).
|
|
110
|
+
const providerRetry = settings.getProviderRetrySettings();
|
|
111
|
+
const providerConfigured = [
|
|
112
|
+
settings.getGlobalSettings(),
|
|
113
|
+
settings.getProjectSettings(),
|
|
114
|
+
].some((scope) => isPlainRecord(scope.retry) && isPlainRecord(scope.retry.provider));
|
|
115
|
+
const providerBlock: ProviderRetrySettings = {};
|
|
116
|
+
if (providerConfigured) {
|
|
117
|
+
// Number filter kept (vs. blindly forwarding getProviderRetrySettings) to
|
|
118
|
+
// keep the D3 identity contract stable: non-number keys never reach
|
|
119
|
+
// injectProviderRetry, and an unconfigured provider stays undefined.
|
|
120
|
+
if (typeof providerRetry.timeoutMs === "number") providerBlock.timeoutMs = providerRetry.timeoutMs;
|
|
121
|
+
if (typeof providerRetry.maxRetries === "number") providerBlock.maxRetries = providerRetry.maxRetries;
|
|
122
|
+
if (typeof providerRetry.maxRetryDelayMs === "number")
|
|
123
|
+
providerBlock.maxRetryDelayMs = providerRetry.maxRetryDelayMs;
|
|
124
|
+
}
|
|
128
125
|
return {
|
|
129
|
-
enabled:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
baseDelayMs:
|
|
134
|
-
typeof retry.baseDelayMs === "number" ? retry.baseDelayMs : 2000,
|
|
126
|
+
enabled: retry.enabled,
|
|
127
|
+
maxRetries: retry.maxRetries,
|
|
128
|
+
baseDelayMs: retry.baseDelayMs,
|
|
129
|
+
...(Object.keys(providerBlock).length > 0 ? { provider: providerBlock } : {}),
|
|
135
130
|
};
|
|
136
131
|
}
|
|
137
132
|
|